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
214 changes: 108 additions & 106 deletions plots/dashboard-metrics-tiles/implementations/python/highcharts.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
""" pyplots.ai
""" anyplot.ai
dashboard-metrics-tiles: Real-Time Dashboard Tiles
Library: highcharts unknown | Python 3.13.11
Quality: 92/100 | Created: 2026-01-19
Library: highcharts unknown | Python 3.13.13
Quality: 90/100 | Updated: 2026-05-21
"""

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

import numpy as np
from PIL import Image
from selenium import webdriver
from selenium.webdriver.chrome.options import Options


# Data - 6 dashboard metrics with history and change
# Theme tokens
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"

CANVAS_W = 3200
CANVAS_H = 1800
SHADOW = "0 2px 8px rgba(0,0,0,0.12)" if THEME == "light" else "0 2px 8px rgba(0,0,0,0.40)"

# Data
np.random.seed(42)

metrics = [
Expand Down Expand Up @@ -52,11 +65,11 @@
},
{
"name": "Error Rate",
"value": 2.3,
"value": 8.7,
"unit": "%",
"history": (1.5 + np.cumsum(np.random.randn(20) * 0.3)).clip(0.5, 5).tolist(),
"change": 0.8,
"status": "warning",
"history": (2.0 + np.cumsum(np.random.randn(20) * 0.5)).clip(0.5, 10).tolist(),
"change": 4.2,
"status": "critical",
},
{
"name": "Disk I/O",
Expand All @@ -68,183 +81,160 @@
},
]

# Status colors
status_colors = {"good": "#059669", "warning": "#D97706", "critical": "#DC2626"}

# Change colors (green for down is good for CPU/Memory/Error, up is good for Requests)
# Metrics where lower is better
lower_is_better = {"CPU Usage", "Memory", "Response Time", "Error Rate"}

# Okabe-Ito position 1 for sparklines
SPARK_COLOR = "#009E73"
# Theme-adaptive gradient alpha: more opaque in dark mode so fill stays visible
GRAD_TOP = f"{SPARK_COLOR}55" if THEME == "light" else f"{SPARK_COLOR}88"
GRAD_BOT = f"{SPARK_COLOR}08" if THEME == "light" else f"{SPARK_COLOR}18"

def get_change_color(metric_name, change):
if metric_name in lower_is_better:
return "#059669" if change <= 0 else "#DC2626"
else:
return "#059669" if change >= 0 else "#DC2626"
status_icons = {"good": "✓", "warning": "⚠", "critical": "✕"}
status_labels = {"good": "Good", "warning": "Warning", "critical": "Critical"}

# Layout constants
COLS = 3
ROWS = 2
PADDING_H = 60
PADDING_V = 50
GAP_X = 50
GAP_Y = 40
TITLE_H = 130

def get_arrow(change):
return "▲" if change >= 0 else "▼"

TILE_W = (CANVAS_W - 2 * PADDING_H - (COLS - 1) * GAP_X) // COLS
TILE_H = (CANVAS_H - TITLE_H - 2 * PADDING_V - (ROWS - 1) * GAP_Y) // ROWS

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

# Build HTML dashboard with 6 tiles (3x2 grid)
tile_width = 1500
tile_height = 1200
gap = 60
cols = 3
rows = 2

total_width = cols * tile_width + (cols + 1) * gap
total_height = rows * tile_height + (rows + 1) * gap + 180 # Extra for title

# Build tiles HTML and JS
tiles_html = ""
tiles_js = ""

for i, m in enumerate(metrics):
row = i // cols
col = i % cols
left = gap + col * (tile_width + gap)
top = 180 + gap + row * (tile_height + gap) # Offset for main title
row = i // COLS
col = i % COLS
left = PADDING_H + col * (TILE_W + GAP_X)
top = TITLE_H + PADDING_V + row * (TILE_H + GAP_Y)

status_color = status_colors[m["status"]]
change_color = get_change_color(m["name"], m["change"])
arrow = get_arrow(m["change"])
favorable = (m["change"] <= 0) if m["name"] in lower_is_better else (m["change"] >= 0)
change_color = "#059669" if favorable else "#DC2626"
arrow = "▲" if m["change"] >= 0 else "▼"
change_text = f"{arrow} {abs(m['change']):.1f}%"

# Format value
if m["value"] >= 1000:
value_str = f"{m['value']:,.0f}"
elif isinstance(m["value"], float):
value_str = f"{m['value']:.1f}"
else:
value_str = str(m["value"])

tile_id = f"tile_{i}"
chart_id = f"chart_{i}"
sparkline_data = m["history"]

# Tile HTML
tiles_html += f"""
<div id="{tile_id}" style="
<div style="
position: absolute;
left: {left}px;
top: {top}px;
width: {tile_width}px;
height: {tile_height}px;
background: #ffffff;
border-radius: 20px;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
border-left: 8px solid {status_color};
padding: 40px;
width: {TILE_W}px;
height: {TILE_H}px;
background: {ELEVATED_BG};
border-radius: 16px;
border-left: 10px solid {status_color};
padding: 40px 40px 28px 40px;
box-sizing: border-box;
box-shadow: {SHADOW};
">
<div style="font-size: 36px; color: #6B7280; font-weight: 500; margin-bottom: 15px;">
{m["name"]}
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 14px;">
<div style="font-size: 34px; color: {INK_SOFT}; font-weight: 500; letter-spacing: 0.5px;">
{m["name"]}
</div>
<div style="font-size: 22px; font-weight: 700; color: {status_color}; border: 2px solid {status_color}; border-radius: 6px; padding: 4px 14px; white-space: nowrap; line-height: 1.3;">
{status_icons[m["status"]]} {status_labels[m["status"]]}
</div>
</div>
<div style="display: flex; align-items: baseline; margin-bottom: 20px;">
<span style="font-size: 96px; font-weight: 700; color: #1F2937;">
<div style="display: flex; align-items: baseline; margin-bottom: 18px;">
<span style="font-size: 88px; font-weight: 700; color: {INK}; line-height: 1;">
{value_str}
</span>
<span style="font-size: 40px; color: #6B7280; margin-left: 10px;">
<span style="font-size: 40px; color: {INK_SOFT}; margin-left: 12px;">
{m["unit"]}
</span>
</div>
<div style="font-size: 32px; color: {change_color}; font-weight: 600; margin-bottom: 30px;">
<div style="font-size: 30px; color: {change_color}; font-weight: 600; margin-bottom: 24px;">
{change_text}
</div>
<div id="{chart_id}" style="width: 100%; height: 400px;"></div>
<div id="{chart_id}" style="width: 100%; height: 300px;"></div>
</div>
"""

# Sparkline chart JS
sparkline_data = m["history"]
spark_color = "#306998" # Python blue

tiles_js += f"""
Highcharts.chart('{chart_id}', {{
chart: {{
type: 'area',
backgroundColor: 'transparent',
margin: [0, 0, 0, 0],
spacing: [0, 0, 0, 0]
margin: [4, 0, 4, 0],
spacing: [0, 0, 0, 0],
animation: false
}},
title: {{ text: null }},
credits: {{ enabled: false }},
xAxis: {{
visible: false
}},
yAxis: {{
visible: false
}},
xAxis: {{ visible: false }},
yAxis: {{ visible: false }},
legend: {{ enabled: false }},
tooltip: {{ enabled: false }},
plotOptions: {{
area: {{
fillColor: {{
linearGradient: {{ x1: 0, y1: 0, x2: 0, y2: 1 }},
stops: [
[0, '{spark_color}40'],
[1, '{spark_color}10']
[0, '{GRAD_TOP}'],
[1, '{GRAD_BOT}']
]
}},
lineWidth: 4,
color: '{spark_color}',
lineWidth: 7,
color: '{SPARK_COLOR}',
marker: {{ enabled: false }},
states: {{ hover: {{ enabled: false }} }}
}}
}},
series: [{{
data: {sparkline_data}
}}]
series: [{{ data: {sparkline_data} }}]
}});
"""

# Main title
title_html = f"""
<div style="
position: absolute;
left: 0;
top: 40px;
width: {total_width}px;
text-align: center;
font-size: 56px;
font-weight: 700;
color: #1F2937;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
">
dashboard-metrics-tiles · highcharts · pyplots.ai
</div>
"""

# Full HTML
# Full dashboard HTML
html_content = f"""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>{highcharts_js}</script>
<style>
* {{
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}}
* {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }}
</style>
</head>
<body style="margin: 0; background: #F3F4F6;">
<div style="position: relative; width: {total_width}px; height: {total_height}px;">
{title_html}
<body style="margin: 0; padding: 0; background: {PAGE_BG}; width: {CANVAS_W}px; height: {CANVAS_H}px; overflow: hidden;">
<div style="position: relative; width: {CANVAS_W}px; height: {CANVAS_H}px;">
<div style="
position: absolute; left: 0; top: 0;
width: {CANVAS_W}px; height: {TITLE_H}px;
display: flex; align-items: center; justify-content: center;
font-size: 52px; font-weight: 700; color: {INK};
">
dashboard-metrics-tiles · python · highcharts · anyplot.ai
</div>
{tiles_html}
</div>
<script>
{tiles_js}
</script>
<script>{tiles_js}</script>
</body>
</html>"""

# Save HTML
with open("plot.html", "w", encoding="utf-8") as f:
# Save HTML artifact
with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f:
f.write(html_content)

# Screenshot with Selenium
Expand All @@ -253,16 +243,28 @@ def get_arrow(change):
temp_path = f.name

chrome_options = Options()
chrome_options.add_argument("--headless")
chrome_options.add_argument("--headless=new")
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-dev-shm-usage")
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument(f"--window-size={total_width},{total_height}")
chrome_options.add_argument("--hide-scrollbars")
chrome_options.add_argument(f"--window-size={CANVAS_W},{CANVAS_H}")

driver = webdriver.Chrome(options=chrome_options)
driver.execute_cdp_cmd(
"Emulation.setDeviceMetricsOverride",
{"width": CANVAS_W, "height": CANVAS_H, "deviceScaleFactor": 1, "mobile": False},
)
driver.get(f"file://{temp_path}")
time.sleep(5)
driver.save_screenshot("plot.png")
driver.save_screenshot(f"plot-{THEME}.png")
driver.quit()

Path(temp_path).unlink()

# PIL safety net: pin to exact dimensions
_img = Image.open(f"plot-{THEME}.png").convert("RGB")
if _img.size != (CANVAS_W, CANVAS_H):
_norm = Image.new("RGB", (CANVAS_W, CANVAS_H), PAGE_BG)
_norm.paste(_img, ((CANVAS_W - _img.size[0]) // 2, (CANVAS_H - _img.size[1]) // 2))
_norm.save(f"plot-{THEME}.png")
Loading
Loading