Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 108 additions & 55 deletions plots/waterfall-basic/implementations/python/highcharts.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
""" pyplots.ai
""" anyplot.ai
waterfall-basic: Basic Waterfall Chart
Library: highcharts unknown | Python 3.13.11
Quality: 91/100 | Created: 2025-12-24
Library: highcharts unknown | Python 3.13.13
Quality: 83/100 | Updated: 2026-05-06
"""

import json
import os
import tempfile
import time
import urllib.request
from pathlib import Path

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait


# Theme tokens (see prompts/default-style-guide.md "Background" + "Theme-adaptive Chrome")
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)"

# Okabe-Ito palette: positive = brand green, negative = vermillion
POSITIVE_COLOR = "#009E73" # Okabe-Ito position 1 (brand green)
NEGATIVE_COLOR = "#D55E00" # Okabe-Ito position 2 (vermillion)
TOTAL_COLOR = "#0072B2" # Okabe-Ito position 3 (blue) for start/end totals

# Data - Quarterly financial breakdown from revenue to net income
categories = [
"Revenue",
Expand All @@ -27,17 +42,15 @@
]

# Values: positive = increase, negative = decrease
# Revenue (start), costs (negative), income (positive), and final net income (total)
# Using colorblind-safe colors: Python Blue for totals, teal for increases, orange for decreases
values = [
{"y": 500000, "isIntermediateSum": False, "color": "#306998"}, # Revenue - starting total (Python Blue)
{"y": -150000, "color": "#E67300"}, # Product Costs (orange - decrease)
{"y": -80000, "color": "#E67300"}, # Operating Expenses (orange - decrease)
{"y": -45000, "color": "#E67300"}, # Marketing (orange - decrease)
{"y": -35000, "color": "#E67300"}, # R&D (orange - decrease)
{"y": 20000, "color": "#17BECF"}, # Other Income (teal - increase)
{"y": -52000, "color": "#E67300"}, # Taxes (orange - decrease)
{"isSum": True, "color": "#306998"}, # Net Income - ending total (Python Blue)
{"y": 500000, "color": TOTAL_COLOR}, # Revenue - starting total
{"y": -150000, "color": NEGATIVE_COLOR}, # Product Costs
{"y": -80000, "color": NEGATIVE_COLOR}, # Operating Expenses
{"y": -45000, "color": NEGATIVE_COLOR}, # Marketing
{"y": -35000, "color": NEGATIVE_COLOR}, # R&D
{"y": 20000, "color": POSITIVE_COLOR}, # Other Income
{"y": -52000, "color": NEGATIVE_COLOR}, # Taxes
{"isSum": True, "color": TOTAL_COLOR}, # Net Income - ending total
]

# Chart options for Highcharts waterfall
Expand All @@ -46,59 +59,51 @@
"type": "waterfall",
"width": 4800,
"height": 2700,
"backgroundColor": "#ffffff",
"backgroundColor": PAGE_BG,
"marginBottom": 280,
"style": {"fontFamily": "Arial, sans-serif"},
"style": {"fontFamily": "Arial, sans-serif", "color": INK},
},
"title": {
"text": "Quarterly Financial Breakdown · waterfall-basic · highcharts · pyplots.ai",
"style": {"fontSize": "48px", "fontWeight": "bold"},
"text": "waterfall-basic · highcharts · anyplot.ai",
"style": {"fontSize": "28px", "fontWeight": "normal", "color": INK},
},
"xAxis": {
"categories": categories,
"title": {"text": "Category", "style": {"fontSize": "36px"}},
"labels": {"style": {"fontSize": "28px"}, "rotation": -45, "y": 40},
"title": {"text": "", "style": {"fontSize": "22px", "color": INK}},
"labels": {"style": {"fontSize": "18px", "color": INK_SOFT}},
"lineColor": INK_SOFT,
"tickColor": INK_SOFT,
"gridLineColor": GRID,
},
"yAxis": {
"title": {"text": "Amount ($)", "style": {"fontSize": "36px"}},
"labels": {"style": {"fontSize": "28px"}, "formatter": "__FORMATTER_PLACEHOLDER__"},
"gridLineColor": "#e0e0e0",
"title": {"text": "Amount ($)", "style": {"fontSize": "22px", "color": INK}},
"labels": {"style": {"fontSize": "18px", "color": INK_SOFT}, "formatter": "__FORMATTER_PLACEHOLDER__"},
"lineColor": INK_SOFT,
"tickColor": INK_SOFT,
"gridLineColor": GRID,
},
"legend": {"enabled": False},
"tooltip": {"pointFormat": "<b>${point.y:,.0f}</b>", "style": {"fontSize": "24px"}},
"tooltip": {
"pointFormat": "<b>${point.y:,.0f}</b>",
"headerFormat": "<b>{point.key}</b><br>",
"style": {"fontSize": "18px"},
},
"plotOptions": {
"waterfall": {
"lineWidth": 2,
"lineColor": "#333333",
"lineColor": INK_SOFT,
"borderWidth": 0,
"pointPadding": 0.15,
"dataLabels": {
"enabled": True,
"formatter": "__DATALABEL_FORMATTER_PLACEHOLDER__",
"style": {"fontSize": "24px", "fontWeight": "bold", "textOutline": "2px white"},
"style": {"fontSize": "18px", "fontWeight": "bold", "color": INK, "textOutline": "none"},
},
}
},
"series": [
{
"name": "Financial Breakdown",
"data": values,
"upColor": "#17BECF", # Teal for positive changes (colorblind-safe)
"color": "#E67300", # Orange for negative changes (colorblind-safe)
}
],
"series": [{"name": "Financial Breakdown", "data": values}],
}

# Download Highcharts JS for inline embedding
highcharts_url = "https://code.highcharts.com/highcharts.js"
with urllib.request.urlopen(highcharts_url, timeout=30) as response:
highcharts_js = response.read().decode("utf-8")

# Download highcharts-more.js (needed for waterfall chart)
highcharts_more_url = "https://code.highcharts.com/highcharts-more.js"
with urllib.request.urlopen(highcharts_more_url, timeout=30) as response:
highcharts_more_js = response.read().decode("utf-8")

# Generate chart options JSON and add custom formatters
chart_options_json = json.dumps(chart_options)

Expand All @@ -119,15 +124,37 @@
}"""
chart_options_json = chart_options_json.replace('"__DATALABEL_FORMATTER_PLACEHOLDER__"', data_label_formatter)

# Generate HTML with inline scripts
html_content = f"""<!DOCTYPE html>
# Try to download JS, fallback to CDN links if network unavailable
highcharts_js = ""
highcharts_more_js = ""

try:
highcharts_url = "https://code.highcharts.com/highcharts.js"
req = urllib.request.Request(highcharts_url, headers={"User-Agent": "Mozilla/5.0"})
with urllib.request.urlopen(req, timeout=10) as response:
highcharts_js = response.read().decode("utf-8")
except Exception:
highcharts_js = None

try:
highcharts_more_url = "https://code.highcharts.com/highcharts-more.js"
req = urllib.request.Request(highcharts_more_url, headers={"User-Agent": "Mozilla/5.0"})
with urllib.request.urlopen(req, timeout=10) as response:
highcharts_more_js = response.read().decode("utf-8")
except Exception:
highcharts_more_js = None

# Generate HTML content based on whether we downloaded the scripts
if highcharts_js and highcharts_more_js:
# Use inline scripts
html_content = f"""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>{highcharts_js}</script>
<script>{highcharts_more_js}</script>
</head>
<body style="margin:0;">
<body style="margin:0; background:{PAGE_BG};">
<div id="container" style="width: 4800px; height: 2700px;"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {{
Expand All @@ -136,29 +163,55 @@
</script>
</body>
</html>"""
else:
# Fallback to CDN script tags
html_content = f"""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://code.highcharts.com/highcharts.js"></script>
<script src="https://code.highcharts.com/highcharts-more.js"></script>
</head>
<body style="margin:0; background:{PAGE_BG};">
<div id="container" style="width: 4800px; height: 2700px;"></div>
<script>
window.addEventListener('load', function() {{
if (typeof Highcharts !== 'undefined') {{
Highcharts.chart('container', {chart_options_json});
}}
}});
</script>
</body>
</html>"""

# Write temp HTML file
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
# Write HTML artifact for the site (both themes)
with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f:
f.write(html_content)
temp_path = f.name

# Also save the HTML for interactive viewing
with open("plot.html", "w", encoding="utf-8") as f:
# Write temp HTML and take screenshot for the PNG artifact
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
f.write(html_content)
temp_path = f.name

# Take screenshot with headless Chrome
chrome_options = Options()
chrome_options.add_argument("--headless")
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-dev-shm-usage")
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--window-size=4800,2700")
chrome_options.add_argument("--disable-web-resources")

driver = webdriver.Chrome(options=chrome_options)
driver.get(f"file://{temp_path}")
time.sleep(5)
driver.save_screenshot("plot.png")

try:
# Wait for either Highcharts to be available or max timeout
WebDriverWait(driver, 15).until(lambda d: d.execute_script("return typeof Highcharts !== 'undefined'"))
except Exception:
pass

time.sleep(3)
driver.save_screenshot(f"plot-{THEME}.png")
driver.quit()

# Clean up temp file
Path(temp_path).unlink()
Loading
Loading