-
Notifications
You must be signed in to change notification settings - Fork 0
update(arc-basic): highcharts — new implementation #4374
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
200a471
04aeeee
fafe640
3102471
21b7447
a204a73
248226f
338de8b
496cdfd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,237 @@ | ||||||||||
| """ pyplots.ai | ||||||||||
| arc-basic: Basic Arc Diagram | ||||||||||
| Library: highcharts 1.10.3 | Python 3.14.3 | ||||||||||
| Quality: 87/100 | Created: 2026-02-23 | ||||||||||
| """ | ||||||||||
|
|
||||||||||
| import json | ||||||||||
| import tempfile | ||||||||||
| import time | ||||||||||
| import urllib.request | ||||||||||
| from pathlib import Path | ||||||||||
|
|
||||||||||
| from PIL import Image | ||||||||||
| from selenium import webdriver | ||||||||||
| from selenium.webdriver.chrome.options import Options | ||||||||||
|
|
||||||||||
|
|
||||||||||
| # Data: Character interactions in a story chapter | ||||||||||
| nodes = ["Alice", "Bob", "Carol", "David", "Eve", "Frank", "Grace", "Henry", "Iris", "Jack"] | ||||||||||
|
|
||||||||||
| # Edges: (source, target, weight) — dialogue exchange count | ||||||||||
| edges = [ | ||||||||||
| ("Alice", "Bob", 5), | ||||||||||
| ("Alice", "David", 3), | ||||||||||
| ("Bob", "Carol", 3), | ||||||||||
| ("Carol", "Eve", 2), | ||||||||||
| ("David", "Frank", 3), | ||||||||||
| ("Eve", "Grace", 1), | ||||||||||
| ("Alice", "Henry", 2), | ||||||||||
| ("Bob", "Frank", 3), | ||||||||||
| ("Carol", "David", 4), | ||||||||||
| ("Frank", "Iris", 1), | ||||||||||
| ("Grace", "Jack", 2), | ||||||||||
| ("Alice", "Jack", 1), | ||||||||||
| ("David", "Henry", 3), | ||||||||||
| ("Henry", "Iris", 2), | ||||||||||
| ("Iris", "Jack", 3), | ||||||||||
| ] | ||||||||||
|
|
||||||||||
| # Node connection counts for marker sizing | ||||||||||
| degree = dict.fromkeys(nodes, 0) | ||||||||||
| for src, tgt, _ in edges: | ||||||||||
| degree[src] += 1 | ||||||||||
| degree[tgt] += 1 | ||||||||||
|
|
||||||||||
| # Colorblind-safe palette — Python Blue anchor with complementary tones | ||||||||||
| node_colors = { | ||||||||||
| "Alice": "#306998", | ||||||||||
| "Bob": "#E8A317", | ||||||||||
| "Carol": "#17BECF", | ||||||||||
| "David": "#9467BD", | ||||||||||
| "Eve": "#2CA02C", | ||||||||||
| "Frank": "#D4652F", | ||||||||||
| "Grace": "#8C564B", | ||||||||||
| "Henry": "#1F77B4", | ||||||||||
| "Iris": "#E377C2", | ||||||||||
| "Jack": "#5DA88A", | ||||||||||
| } | ||||||||||
|
|
||||||||||
| # Node config with degree-scaled markers (3-4x default for 4800x2700) | ||||||||||
| nodes_data = [] | ||||||||||
| for name in nodes: | ||||||||||
| nodes_data.append( | ||||||||||
| { | ||||||||||
| "id": name, | ||||||||||
| "color": node_colors[name], | ||||||||||
| "marker": {"radius": 95 + degree[name] * 14, "lineWidth": 5, "lineColor": "#ffffff"}, | ||||||||||
| } | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| # Scale weights up for visual node sizing (arc diagram sizes nodes by total weight flow) | ||||||||||
| weight_scale = 14 | ||||||||||
| links_data = [{"from": src, "to": tgt, "weight": w * weight_scale} for src, tgt, w in edges] | ||||||||||
|
|
||||||||||
| # Chart options (raw JS — highcharts_core doesn't support arcdiagram type) | ||||||||||
| chart_options = { | ||||||||||
| "chart": { | ||||||||||
| "width": 4800, | ||||||||||
| "height": 2700, | ||||||||||
| "backgroundColor": "#ffffff", | ||||||||||
| "marginTop": 150, | ||||||||||
| "marginBottom": 10, | ||||||||||
| "marginLeft": 200, | ||||||||||
| "marginRight": 200, | ||||||||||
| "spacingTop": 20, | ||||||||||
| "spacingBottom": 0, | ||||||||||
| }, | ||||||||||
| "title": { | ||||||||||
| "text": "arc-basic \u00b7 highcharts \u00b7 pyplots.ai", | ||||||||||
| "style": {"fontSize": "56px", "fontWeight": "bold", "color": "#333333"}, | ||||||||||
| "margin": 30, | ||||||||||
| }, | ||||||||||
| "subtitle": { | ||||||||||
| "text": "Character interactions — Dialogue exchanges between characters in a story chapter", | ||||||||||
| "style": {"fontSize": "36px", "color": "#666666"}, | ||||||||||
| }, | ||||||||||
| "accessibility": {"enabled": False}, | ||||||||||
| "tooltip": { | ||||||||||
| "style": {"fontSize": "32px"}, | ||||||||||
| "nodeFormat": "{point.name}: {point.sum} exchanges", | ||||||||||
| "pointFormat": "{point.fromNode.name} \u2192 {point.toNode.name}: {point.weight} exchanges", | ||||||||||
| }, | ||||||||||
| "series": [ | ||||||||||
| { | ||||||||||
| "type": "arcdiagram", | ||||||||||
| "name": "Interactions", | ||||||||||
| "keys": ["from", "to", "weight"], | ||||||||||
| "nodes": nodes_data, | ||||||||||
| "data": links_data, | ||||||||||
| "colorByPoint": True, | ||||||||||
| "centeredLinks": True, | ||||||||||
| "linkColorMode": "from", | ||||||||||
| "linkOpacity": 0.5, | ||||||||||
| "linkWeight": 18, | ||||||||||
| "equalNodes": False, | ||||||||||
| "nodeWidth": 110, | ||||||||||
| "minLinkWidth": 8, | ||||||||||
| "marker": {"radius": 110, "lineWidth": 5, "lineColor": "#ffffff"}, | ||||||||||
| "dataLabels": [ | ||||||||||
| { | ||||||||||
| "enabled": True, | ||||||||||
| "rotation": 0, | ||||||||||
| "y": 80, | ||||||||||
| "align": "center", | ||||||||||
| "style": { | ||||||||||
| "fontSize": "48px", | ||||||||||
| "fontWeight": "bold", | ||||||||||
| "textOutline": "3px #ffffff", | ||||||||||
| "color": "#333333", | ||||||||||
| }, | ||||||||||
| } | ||||||||||
| ], | ||||||||||
| } | ||||||||||
| ], | ||||||||||
| "legend": {"enabled": False}, | ||||||||||
| "credits": {"enabled": False}, | ||||||||||
| } | ||||||||||
|
|
||||||||||
| options_json = json.dumps(chart_options) | ||||||||||
|
|
||||||||||
| # Download Highcharts JS, sankey module (dependency), and arc-diagram module | ||||||||||
| cache_dir = Path("/tmp") | ||||||||||
| urls = { | ||||||||||
| "highcharts": ("https://cdn.jsdelivr.net/npm/highcharts@11.4.8/highcharts.js", cache_dir / "highcharts.js"), | ||||||||||
| "sankey": ("https://cdn.jsdelivr.net/npm/highcharts@11.4.8/modules/sankey.js", cache_dir / "hc_sankey.js"), | ||||||||||
| "arcdiagram": ( | ||||||||||
| "https://cdn.jsdelivr.net/npm/highcharts@11.4.8/modules/arc-diagram.js", | ||||||||||
| cache_dir / "hc_arc_diagram.js", | ||||||||||
| ), | ||||||||||
| } | ||||||||||
| js_scripts = {} | ||||||||||
| for name, (url, cache_path) in urls.items(): | ||||||||||
| if cache_path.exists() and cache_path.stat().st_size > 1000: | ||||||||||
| js_scripts[name] = cache_path.read_text(encoding="utf-8") | ||||||||||
| else: | ||||||||||
| for attempt in range(5): | ||||||||||
| try: | ||||||||||
| with urllib.request.urlopen(url, timeout=30) as resp: | ||||||||||
| content = resp.read().decode("utf-8") | ||||||||||
| cache_path.write_text(content, encoding="utf-8") | ||||||||||
| js_scripts[name] = content | ||||||||||
| break | ||||||||||
| except urllib.error.HTTPError: | ||||||||||
| time.sleep(3 * (attempt + 1)) | ||||||||||
|
||||||||||
| time.sleep(3 * (attempt + 1)) | |
| time.sleep(3 * (attempt + 1)) | |
| else: | |
| raise RuntimeError(f"Failed to download '{name}' from {url} after 5 attempts") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tooltip displays scaled weight values (line 100), not the original edge weights. Since weights are multiplied by 8 on line 72, a tooltip showing an edge with original weight 5 will display "40 exchanges" instead of "5 exchanges", which misrepresents the actual data. Either avoid scaling the data or use a tooltip formatter that divides by the scale factor to show true values.