From 8eb34de5b9b351787e305d3964640635a5574675 Mon Sep 17 00:00:00 2001 From: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com> Date: Thu, 11 Jun 2026 00:48:18 +0200 Subject: [PATCH] chore(catalog): remove 13 interactive-first specs These specs' core value lies in click/hover/brush/animation behavior (drilldown navigation, brushing & linking, play controls, animated transitions) that anyplot's static renders cannot capture. Static implementations could only fake the interaction differently per library, and the static fallback always collapses into an existing spec (e.g. one drill level of bar-drilldown = bar-basic, one frame of bar-race-animated = bar-horizontal). None of them had JS implementations yet; all sat at 5-10 of 15 libraries. Removed specs: - bar-drilldown, pie-drilldown, map-drilldown-geographic - pie-portfolio-interactive, hierarchy-toggle-view - scatter-matrix-interactive, scatter-brush-zoom, linked-views-selection - dashboard-synchronized-crosshair, line-navigator - bar-race-animated, scatter-animated-controls, map-animated-temporal Postgres entries are cleaned up automatically by sync_to_postgres on push to main. GCS preview images will be removed separately after merge. Co-Authored-By: Claude Fable 5 --- .../implementations/python/altair.py | 320 -------- .../implementations/python/bokeh.py | 344 --------- .../implementations/python/letsplot.py | 589 -------------- .../implementations/python/matplotlib.py | 127 --- .../implementations/python/plotly.py | 246 ------ .../implementations/python/plotnine.py | 100 --- .../implementations/python/pygal.py | 264 ------- .../implementations/python/seaborn.py | 138 ---- .../bar-drilldown/implementations/r/ggplot2.R | 132 ---- .../bar-drilldown/metadata/python/altair.yaml | 248 ------ .../bar-drilldown/metadata/python/bokeh.yaml | 253 ------ .../metadata/python/letsplot.yaml | 250 ------ .../metadata/python/matplotlib.yaml | 253 ------ .../bar-drilldown/metadata/python/plotly.yaml | 260 ------- .../metadata/python/plotnine.yaml | 239 ------ .../bar-drilldown/metadata/python/pygal.yaml | 263 ------- .../metadata/python/seaborn.yaml | 241 ------ plots/bar-drilldown/metadata/r/ggplot2.yaml | 248 ------ plots/bar-drilldown/specification.md | 31 - plots/bar-drilldown/specification.yaml | 28 - .../implementations/python/altair.py | 179 ----- .../implementations/python/bokeh.py | 200 ----- .../implementations/python/letsplot.py | 94 --- .../implementations/python/matplotlib.py | 146 ---- .../implementations/python/plotly.py | 203 ----- .../implementations/python/plotnine.py | 149 ---- .../implementations/python/pygal.py | 180 ----- .../implementations/python/seaborn.py | 152 ---- .../implementations/r/ggplot2.R | 98 --- .../metadata/python/altair.yaml | 268 ------- .../metadata/python/bokeh.yaml | 257 ------ .../metadata/python/letsplot.yaml | 247 ------ .../metadata/python/matplotlib.yaml | 257 ------ .../metadata/python/plotly.yaml | 242 ------ .../metadata/python/plotnine.yaml | 243 ------ .../metadata/python/pygal.yaml | 251 ------ .../metadata/python/seaborn.yaml | 251 ------ .../bar-race-animated/metadata/r/ggplot2.yaml | 264 ------- plots/bar-race-animated/specification.md | 30 - plots/bar-race-animated/specification.yaml | 29 - .../implementations/julia/makie.jl | 231 ------ .../implementations/python/altair.py | 172 ----- .../implementations/python/bokeh.py | 193 ----- .../implementations/python/letsplot.py | 181 ----- .../implementations/python/matplotlib.py | 130 ---- .../implementations/python/plotly.py | 177 ----- .../implementations/python/plotnine.py | 131 ---- .../implementations/python/pygal.py | 284 ------- .../implementations/python/seaborn.py | 179 ----- .../implementations/r/ggplot2.R | 141 ---- .../metadata/julia/makie.yaml | 263 ------- .../metadata/python/altair.yaml | 270 ------- .../metadata/python/bokeh.yaml | 267 ------- .../metadata/python/letsplot.yaml | 254 ------ .../metadata/python/matplotlib.yaml | 239 ------ .../metadata/python/plotly.yaml | 260 ------- .../metadata/python/plotnine.yaml | 257 ------ .../metadata/python/pygal.yaml | 265 ------- .../metadata/python/seaborn.yaml | 259 ------- .../metadata/r/ggplot2.yaml | 256 ------ .../specification.md | 31 - .../specification.yaml | 28 - .../implementations/python/altair.py | 259 ------- .../implementations/python/bokeh.py | 524 ------------- .../implementations/python/letsplot.py | 505 ------------ .../implementations/python/matplotlib.py | 162 ---- .../implementations/python/plotly.py | 208 ----- .../implementations/python/plotnine.py | 279 ------- .../implementations/python/pygal.py | 306 -------- .../implementations/python/seaborn.py | 303 -------- .../metadata/python/altair.yaml | 251 ------ .../metadata/python/bokeh.yaml | 264 ------- .../metadata/python/letsplot.yaml | 252 ------ .../metadata/python/matplotlib.yaml | 250 ------ .../metadata/python/plotly.yaml | 275 ------- .../metadata/python/plotnine.yaml | 247 ------ .../metadata/python/pygal.yaml | 249 ------ .../metadata/python/seaborn.yaml | 254 ------ plots/hierarchy-toggle-view/specification.md | 28 - .../hierarchy-toggle-view/specification.yaml | 28 - .../implementations/julia/makie.jl | 147 ---- .../implementations/python/altair.py | 145 ---- .../implementations/python/bokeh.py | 150 ---- .../implementations/python/letsplot.py | 185 ----- .../implementations/python/matplotlib.py | 130 ---- .../implementations/python/plotly.py | 136 ---- .../implementations/python/plotnine.py | 150 ---- .../implementations/python/pygal.py | 283 ------- .../implementations/python/seaborn.py | 150 ---- .../implementations/r/ggplot2.R | 125 --- .../line-navigator/metadata/julia/makie.yaml | 267 ------- .../metadata/python/altair.yaml | 266 ------- .../line-navigator/metadata/python/bokeh.yaml | 249 ------ .../metadata/python/letsplot.yaml | 248 ------ .../metadata/python/matplotlib.yaml | 246 ------ .../metadata/python/plotly.yaml | 255 ------ .../metadata/python/plotnine.yaml | 264 ------- .../line-navigator/metadata/python/pygal.yaml | 267 ------- .../metadata/python/seaborn.yaml | 277 ------- plots/line-navigator/metadata/r/ggplot2.yaml | 272 ------- plots/line-navigator/specification.md | 30 - plots/line-navigator/specification.yaml | 28 - .../implementations/python/altair.py | 159 ---- .../implementations/python/bokeh.py | 412 ---------- .../implementations/python/letsplot.py | 210 ----- .../implementations/python/matplotlib.py | 116 --- .../implementations/python/plotly.py | 281 ------- .../implementations/python/plotnine.py | 114 --- .../implementations/python/pygal.py | 262 ------- .../implementations/python/seaborn.py | 146 ---- .../implementations/r/ggplot2.R | 99 --- .../metadata/python/altair.yaml | 246 ------ .../metadata/python/bokeh.yaml | 255 ------ .../metadata/python/letsplot.yaml | 254 ------ .../metadata/python/matplotlib.yaml | 239 ------ .../metadata/python/plotly.yaml | 231 ------ .../metadata/python/plotnine.yaml | 247 ------ .../metadata/python/pygal.yaml | 263 ------- .../metadata/python/seaborn.yaml | 252 ------ .../metadata/r/ggplot2.yaml | 261 ------- plots/linked-views-selection/specification.md | 32 - .../linked-views-selection/specification.yaml | 32 - .../implementations/julia/makie.jl | 154 ---- .../implementations/python/altair.py | 177 ----- .../implementations/python/bokeh.py | 268 ------- .../implementations/python/letsplot.py | 189 ----- .../implementations/python/matplotlib.py | 265 ------- .../implementations/python/plotly.py | 229 ------ .../implementations/python/plotnine.py | 174 ----- .../implementations/python/pygal.py | 329 -------- .../implementations/python/seaborn.py | 198 ----- .../implementations/r/ggplot2.R | 115 --- .../metadata/julia/makie.yaml | 259 ------- .../metadata/python/altair.yaml | 271 ------- .../metadata/python/bokeh.yaml | 257 ------ .../metadata/python/letsplot.yaml | 263 ------- .../metadata/python/matplotlib.yaml | 289 ------- .../metadata/python/plotly.yaml | 255 ------ .../metadata/python/plotnine.yaml | 248 ------ .../metadata/python/pygal.yaml | 279 ------- .../metadata/python/seaborn.yaml | 287 ------- .../metadata/r/ggplot2.yaml | 260 ------- plots/map-animated-temporal/specification.md | 33 - .../map-animated-temporal/specification.yaml | 31 - .../implementations/julia/makie.jl | 195 ----- .../implementations/python/altair.py | 457 ----------- .../implementations/python/bokeh.py | 729 ------------------ .../implementations/python/letsplot.py | 423 ---------- .../implementations/python/matplotlib.py | 354 --------- .../implementations/python/plotly.py | 359 --------- .../implementations/python/plotnine.py | 353 --------- .../implementations/python/pygal.py | 614 --------------- .../implementations/python/seaborn.py | 324 -------- .../metadata/julia/makie.yaml | 291 ------- .../metadata/python/altair.yaml | 269 ------- .../metadata/python/bokeh.yaml | 275 ------- .../metadata/python/letsplot.yaml | 258 ------- .../metadata/python/matplotlib.yaml | 269 ------- .../metadata/python/plotly.yaml | 273 ------- .../metadata/python/plotnine.yaml | 251 ------ .../metadata/python/pygal.yaml | 287 ------- .../metadata/python/seaborn.yaml | 290 ------- .../map-drilldown-geographic/specification.md | 32 - .../specification.yaml | 29 - .../implementations/python/altair.py | 185 ----- .../implementations/python/bokeh.py | 363 --------- .../implementations/python/letsplot.py | 631 --------------- .../implementations/python/plotly.py | 287 ------- .../implementations/python/pygal.py | 480 ------------ .../pie-drilldown/metadata/python/altair.yaml | 235 ------ .../pie-drilldown/metadata/python/bokeh.yaml | 252 ------ .../metadata/python/letsplot.yaml | 247 ------ .../pie-drilldown/metadata/python/plotly.yaml | 263 ------- .../pie-drilldown/metadata/python/pygal.yaml | 234 ------ plots/pie-drilldown/specification.md | 30 - plots/pie-drilldown/specification.yaml | 29 - .../implementations/julia/makie.jl | 117 --- .../implementations/python/altair.py | 178 ----- .../implementations/python/bokeh.py | 387 ---------- .../implementations/python/letsplot.py | 189 ----- .../implementations/python/matplotlib.py | 125 --- .../implementations/python/plotly.py | 194 ----- .../implementations/python/plotnine.py | 242 ------ .../implementations/python/pygal.py | 129 ---- .../implementations/python/seaborn.py | 212 ----- .../implementations/r/ggplot2.R | 158 ---- .../metadata/julia/makie.yaml | 255 ------ .../metadata/python/altair.yaml | 259 ------- .../metadata/python/bokeh.yaml | 285 ------- .../metadata/python/letsplot.yaml | 263 ------- .../metadata/python/matplotlib.yaml | 253 ------ .../metadata/python/plotly.yaml | 262 ------- .../metadata/python/plotnine.yaml | 266 ------- .../metadata/python/pygal.yaml | 242 ------ .../metadata/python/seaborn.yaml | 267 ------- .../metadata/r/ggplot2.yaml | 278 ------- .../specification.md | 29 - .../specification.yaml | 29 - .../implementations/python/altair.py | 168 ---- .../implementations/python/bokeh.py | 318 -------- .../implementations/python/letsplot.py | 126 --- .../implementations/python/matplotlib.py | 76 -- .../implementations/python/plotly.py | 177 ----- .../implementations/python/plotnine.py | 122 --- .../implementations/python/pygal.py | 426 ---------- .../implementations/python/seaborn.py | 134 ---- .../metadata/python/altair.yaml | 240 ------ .../metadata/python/bokeh.yaml | 237 ------ .../metadata/python/letsplot.yaml | 234 ------ .../metadata/python/matplotlib.yaml | 231 ------ .../metadata/python/plotly.yaml | 219 ------ .../metadata/python/plotnine.yaml | 223 ------ .../metadata/python/pygal.yaml | 236 ------ .../metadata/python/seaborn.yaml | 236 ------ .../specification.md | 31 - .../specification.yaml | 29 - .../implementations/python/altair.py | 153 ---- .../implementations/python/bokeh.py | 189 ----- .../implementations/python/letsplot.py | 153 ---- .../implementations/python/plotly.py | 142 ---- .../implementations/python/pygal.py | 115 --- .../metadata/python/altair.yaml | 250 ------ .../metadata/python/bokeh.yaml | 236 ------ .../metadata/python/letsplot.yaml | 243 ------ .../metadata/python/plotly.yaml | 246 ------ .../metadata/python/pygal.yaml | 224 ------ plots/scatter-brush-zoom/specification.md | 31 - plots/scatter-brush-zoom/specification.yaml | 26 - .../implementations/python/altair.py | 281 ------- .../implementations/python/bokeh.py | 223 ------ .../implementations/python/letsplot.py | 222 ------ .../implementations/python/matplotlib.py | 115 --- .../implementations/python/plotly.py | 148 ---- .../implementations/python/plotnine.py | 132 ---- .../implementations/python/pygal.py | 77 -- .../implementations/python/seaborn.py | 122 --- .../metadata/python/altair.yaml | 226 ------ .../metadata/python/bokeh.yaml | 254 ------ .../metadata/python/letsplot.yaml | 243 ------ .../metadata/python/matplotlib.yaml | 273 ------- .../metadata/python/plotly.yaml | 246 ------ .../metadata/python/plotnine.yaml | 229 ------ .../metadata/python/pygal.yaml | 242 ------ .../metadata/python/seaborn.yaml | 222 ------ .../specification.md | 30 - .../specification.yaml | 32 - 246 files changed, 53612 deletions(-) delete mode 100644 plots/bar-drilldown/implementations/python/altair.py delete mode 100644 plots/bar-drilldown/implementations/python/bokeh.py delete mode 100644 plots/bar-drilldown/implementations/python/letsplot.py delete mode 100644 plots/bar-drilldown/implementations/python/matplotlib.py delete mode 100644 plots/bar-drilldown/implementations/python/plotly.py delete mode 100644 plots/bar-drilldown/implementations/python/plotnine.py delete mode 100644 plots/bar-drilldown/implementations/python/pygal.py delete mode 100644 plots/bar-drilldown/implementations/python/seaborn.py delete mode 100644 plots/bar-drilldown/implementations/r/ggplot2.R delete mode 100644 plots/bar-drilldown/metadata/python/altair.yaml delete mode 100644 plots/bar-drilldown/metadata/python/bokeh.yaml delete mode 100644 plots/bar-drilldown/metadata/python/letsplot.yaml delete mode 100644 plots/bar-drilldown/metadata/python/matplotlib.yaml delete mode 100644 plots/bar-drilldown/metadata/python/plotly.yaml delete mode 100644 plots/bar-drilldown/metadata/python/plotnine.yaml delete mode 100644 plots/bar-drilldown/metadata/python/pygal.yaml delete mode 100644 plots/bar-drilldown/metadata/python/seaborn.yaml delete mode 100644 plots/bar-drilldown/metadata/r/ggplot2.yaml delete mode 100644 plots/bar-drilldown/specification.md delete mode 100644 plots/bar-drilldown/specification.yaml delete mode 100644 plots/bar-race-animated/implementations/python/altair.py delete mode 100644 plots/bar-race-animated/implementations/python/bokeh.py delete mode 100644 plots/bar-race-animated/implementations/python/letsplot.py delete mode 100644 plots/bar-race-animated/implementations/python/matplotlib.py delete mode 100644 plots/bar-race-animated/implementations/python/plotly.py delete mode 100644 plots/bar-race-animated/implementations/python/plotnine.py delete mode 100644 plots/bar-race-animated/implementations/python/pygal.py delete mode 100644 plots/bar-race-animated/implementations/python/seaborn.py delete mode 100644 plots/bar-race-animated/implementations/r/ggplot2.R delete mode 100644 plots/bar-race-animated/metadata/python/altair.yaml delete mode 100644 plots/bar-race-animated/metadata/python/bokeh.yaml delete mode 100644 plots/bar-race-animated/metadata/python/letsplot.yaml delete mode 100644 plots/bar-race-animated/metadata/python/matplotlib.yaml delete mode 100644 plots/bar-race-animated/metadata/python/plotly.yaml delete mode 100644 plots/bar-race-animated/metadata/python/plotnine.yaml delete mode 100644 plots/bar-race-animated/metadata/python/pygal.yaml delete mode 100644 plots/bar-race-animated/metadata/python/seaborn.yaml delete mode 100644 plots/bar-race-animated/metadata/r/ggplot2.yaml delete mode 100644 plots/bar-race-animated/specification.md delete mode 100644 plots/bar-race-animated/specification.yaml delete mode 100644 plots/dashboard-synchronized-crosshair/implementations/julia/makie.jl delete mode 100644 plots/dashboard-synchronized-crosshair/implementations/python/altair.py delete mode 100644 plots/dashboard-synchronized-crosshair/implementations/python/bokeh.py delete mode 100644 plots/dashboard-synchronized-crosshair/implementations/python/letsplot.py delete mode 100644 plots/dashboard-synchronized-crosshair/implementations/python/matplotlib.py delete mode 100644 plots/dashboard-synchronized-crosshair/implementations/python/plotly.py delete mode 100644 plots/dashboard-synchronized-crosshair/implementations/python/plotnine.py delete mode 100644 plots/dashboard-synchronized-crosshair/implementations/python/pygal.py delete mode 100644 plots/dashboard-synchronized-crosshair/implementations/python/seaborn.py delete mode 100644 plots/dashboard-synchronized-crosshair/implementations/r/ggplot2.R delete mode 100644 plots/dashboard-synchronized-crosshair/metadata/julia/makie.yaml delete mode 100644 plots/dashboard-synchronized-crosshair/metadata/python/altair.yaml delete mode 100644 plots/dashboard-synchronized-crosshair/metadata/python/bokeh.yaml delete mode 100644 plots/dashboard-synchronized-crosshair/metadata/python/letsplot.yaml delete mode 100644 plots/dashboard-synchronized-crosshair/metadata/python/matplotlib.yaml delete mode 100644 plots/dashboard-synchronized-crosshair/metadata/python/plotly.yaml delete mode 100644 plots/dashboard-synchronized-crosshair/metadata/python/plotnine.yaml delete mode 100644 plots/dashboard-synchronized-crosshair/metadata/python/pygal.yaml delete mode 100644 plots/dashboard-synchronized-crosshair/metadata/python/seaborn.yaml delete mode 100644 plots/dashboard-synchronized-crosshair/metadata/r/ggplot2.yaml delete mode 100644 plots/dashboard-synchronized-crosshair/specification.md delete mode 100644 plots/dashboard-synchronized-crosshair/specification.yaml delete mode 100644 plots/hierarchy-toggle-view/implementations/python/altair.py delete mode 100644 plots/hierarchy-toggle-view/implementations/python/bokeh.py delete mode 100644 plots/hierarchy-toggle-view/implementations/python/letsplot.py delete mode 100644 plots/hierarchy-toggle-view/implementations/python/matplotlib.py delete mode 100644 plots/hierarchy-toggle-view/implementations/python/plotly.py delete mode 100644 plots/hierarchy-toggle-view/implementations/python/plotnine.py delete mode 100644 plots/hierarchy-toggle-view/implementations/python/pygal.py delete mode 100644 plots/hierarchy-toggle-view/implementations/python/seaborn.py delete mode 100644 plots/hierarchy-toggle-view/metadata/python/altair.yaml delete mode 100644 plots/hierarchy-toggle-view/metadata/python/bokeh.yaml delete mode 100644 plots/hierarchy-toggle-view/metadata/python/letsplot.yaml delete mode 100644 plots/hierarchy-toggle-view/metadata/python/matplotlib.yaml delete mode 100644 plots/hierarchy-toggle-view/metadata/python/plotly.yaml delete mode 100644 plots/hierarchy-toggle-view/metadata/python/plotnine.yaml delete mode 100644 plots/hierarchy-toggle-view/metadata/python/pygal.yaml delete mode 100644 plots/hierarchy-toggle-view/metadata/python/seaborn.yaml delete mode 100644 plots/hierarchy-toggle-view/specification.md delete mode 100644 plots/hierarchy-toggle-view/specification.yaml delete mode 100644 plots/line-navigator/implementations/julia/makie.jl delete mode 100644 plots/line-navigator/implementations/python/altair.py delete mode 100644 plots/line-navigator/implementations/python/bokeh.py delete mode 100644 plots/line-navigator/implementations/python/letsplot.py delete mode 100644 plots/line-navigator/implementations/python/matplotlib.py delete mode 100644 plots/line-navigator/implementations/python/plotly.py delete mode 100644 plots/line-navigator/implementations/python/plotnine.py delete mode 100644 plots/line-navigator/implementations/python/pygal.py delete mode 100644 plots/line-navigator/implementations/python/seaborn.py delete mode 100644 plots/line-navigator/implementations/r/ggplot2.R delete mode 100644 plots/line-navigator/metadata/julia/makie.yaml delete mode 100644 plots/line-navigator/metadata/python/altair.yaml delete mode 100644 plots/line-navigator/metadata/python/bokeh.yaml delete mode 100644 plots/line-navigator/metadata/python/letsplot.yaml delete mode 100644 plots/line-navigator/metadata/python/matplotlib.yaml delete mode 100644 plots/line-navigator/metadata/python/plotly.yaml delete mode 100644 plots/line-navigator/metadata/python/plotnine.yaml delete mode 100644 plots/line-navigator/metadata/python/pygal.yaml delete mode 100644 plots/line-navigator/metadata/python/seaborn.yaml delete mode 100644 plots/line-navigator/metadata/r/ggplot2.yaml delete mode 100644 plots/line-navigator/specification.md delete mode 100644 plots/line-navigator/specification.yaml delete mode 100644 plots/linked-views-selection/implementations/python/altair.py delete mode 100644 plots/linked-views-selection/implementations/python/bokeh.py delete mode 100644 plots/linked-views-selection/implementations/python/letsplot.py delete mode 100644 plots/linked-views-selection/implementations/python/matplotlib.py delete mode 100644 plots/linked-views-selection/implementations/python/plotly.py delete mode 100644 plots/linked-views-selection/implementations/python/plotnine.py delete mode 100644 plots/linked-views-selection/implementations/python/pygal.py delete mode 100644 plots/linked-views-selection/implementations/python/seaborn.py delete mode 100644 plots/linked-views-selection/implementations/r/ggplot2.R delete mode 100644 plots/linked-views-selection/metadata/python/altair.yaml delete mode 100644 plots/linked-views-selection/metadata/python/bokeh.yaml delete mode 100644 plots/linked-views-selection/metadata/python/letsplot.yaml delete mode 100644 plots/linked-views-selection/metadata/python/matplotlib.yaml delete mode 100644 plots/linked-views-selection/metadata/python/plotly.yaml delete mode 100644 plots/linked-views-selection/metadata/python/plotnine.yaml delete mode 100644 plots/linked-views-selection/metadata/python/pygal.yaml delete mode 100644 plots/linked-views-selection/metadata/python/seaborn.yaml delete mode 100644 plots/linked-views-selection/metadata/r/ggplot2.yaml delete mode 100644 plots/linked-views-selection/specification.md delete mode 100644 plots/linked-views-selection/specification.yaml delete mode 100644 plots/map-animated-temporal/implementations/julia/makie.jl delete mode 100644 plots/map-animated-temporal/implementations/python/altair.py delete mode 100644 plots/map-animated-temporal/implementations/python/bokeh.py delete mode 100644 plots/map-animated-temporal/implementations/python/letsplot.py delete mode 100644 plots/map-animated-temporal/implementations/python/matplotlib.py delete mode 100644 plots/map-animated-temporal/implementations/python/plotly.py delete mode 100644 plots/map-animated-temporal/implementations/python/plotnine.py delete mode 100644 plots/map-animated-temporal/implementations/python/pygal.py delete mode 100644 plots/map-animated-temporal/implementations/python/seaborn.py delete mode 100644 plots/map-animated-temporal/implementations/r/ggplot2.R delete mode 100644 plots/map-animated-temporal/metadata/julia/makie.yaml delete mode 100644 plots/map-animated-temporal/metadata/python/altair.yaml delete mode 100644 plots/map-animated-temporal/metadata/python/bokeh.yaml delete mode 100644 plots/map-animated-temporal/metadata/python/letsplot.yaml delete mode 100644 plots/map-animated-temporal/metadata/python/matplotlib.yaml delete mode 100644 plots/map-animated-temporal/metadata/python/plotly.yaml delete mode 100644 plots/map-animated-temporal/metadata/python/plotnine.yaml delete mode 100644 plots/map-animated-temporal/metadata/python/pygal.yaml delete mode 100644 plots/map-animated-temporal/metadata/python/seaborn.yaml delete mode 100644 plots/map-animated-temporal/metadata/r/ggplot2.yaml delete mode 100644 plots/map-animated-temporal/specification.md delete mode 100644 plots/map-animated-temporal/specification.yaml delete mode 100644 plots/map-drilldown-geographic/implementations/julia/makie.jl delete mode 100644 plots/map-drilldown-geographic/implementations/python/altair.py delete mode 100644 plots/map-drilldown-geographic/implementations/python/bokeh.py delete mode 100644 plots/map-drilldown-geographic/implementations/python/letsplot.py delete mode 100644 plots/map-drilldown-geographic/implementations/python/matplotlib.py delete mode 100644 plots/map-drilldown-geographic/implementations/python/plotly.py delete mode 100644 plots/map-drilldown-geographic/implementations/python/plotnine.py delete mode 100644 plots/map-drilldown-geographic/implementations/python/pygal.py delete mode 100644 plots/map-drilldown-geographic/implementations/python/seaborn.py delete mode 100644 plots/map-drilldown-geographic/metadata/julia/makie.yaml delete mode 100644 plots/map-drilldown-geographic/metadata/python/altair.yaml delete mode 100644 plots/map-drilldown-geographic/metadata/python/bokeh.yaml delete mode 100644 plots/map-drilldown-geographic/metadata/python/letsplot.yaml delete mode 100644 plots/map-drilldown-geographic/metadata/python/matplotlib.yaml delete mode 100644 plots/map-drilldown-geographic/metadata/python/plotly.yaml delete mode 100644 plots/map-drilldown-geographic/metadata/python/plotnine.yaml delete mode 100644 plots/map-drilldown-geographic/metadata/python/pygal.yaml delete mode 100644 plots/map-drilldown-geographic/metadata/python/seaborn.yaml delete mode 100644 plots/map-drilldown-geographic/specification.md delete mode 100644 plots/map-drilldown-geographic/specification.yaml delete mode 100644 plots/pie-drilldown/implementations/python/altair.py delete mode 100644 plots/pie-drilldown/implementations/python/bokeh.py delete mode 100644 plots/pie-drilldown/implementations/python/letsplot.py delete mode 100644 plots/pie-drilldown/implementations/python/plotly.py delete mode 100644 plots/pie-drilldown/implementations/python/pygal.py delete mode 100644 plots/pie-drilldown/metadata/python/altair.yaml delete mode 100644 plots/pie-drilldown/metadata/python/bokeh.yaml delete mode 100644 plots/pie-drilldown/metadata/python/letsplot.yaml delete mode 100644 plots/pie-drilldown/metadata/python/plotly.yaml delete mode 100644 plots/pie-drilldown/metadata/python/pygal.yaml delete mode 100644 plots/pie-drilldown/specification.md delete mode 100644 plots/pie-drilldown/specification.yaml delete mode 100644 plots/pie-portfolio-interactive/implementations/julia/makie.jl delete mode 100644 plots/pie-portfolio-interactive/implementations/python/altair.py delete mode 100644 plots/pie-portfolio-interactive/implementations/python/bokeh.py delete mode 100644 plots/pie-portfolio-interactive/implementations/python/letsplot.py delete mode 100644 plots/pie-portfolio-interactive/implementations/python/matplotlib.py delete mode 100644 plots/pie-portfolio-interactive/implementations/python/plotly.py delete mode 100644 plots/pie-portfolio-interactive/implementations/python/plotnine.py delete mode 100644 plots/pie-portfolio-interactive/implementations/python/pygal.py delete mode 100644 plots/pie-portfolio-interactive/implementations/python/seaborn.py delete mode 100644 plots/pie-portfolio-interactive/implementations/r/ggplot2.R delete mode 100644 plots/pie-portfolio-interactive/metadata/julia/makie.yaml delete mode 100644 plots/pie-portfolio-interactive/metadata/python/altair.yaml delete mode 100644 plots/pie-portfolio-interactive/metadata/python/bokeh.yaml delete mode 100644 plots/pie-portfolio-interactive/metadata/python/letsplot.yaml delete mode 100644 plots/pie-portfolio-interactive/metadata/python/matplotlib.yaml delete mode 100644 plots/pie-portfolio-interactive/metadata/python/plotly.yaml delete mode 100644 plots/pie-portfolio-interactive/metadata/python/plotnine.yaml delete mode 100644 plots/pie-portfolio-interactive/metadata/python/pygal.yaml delete mode 100644 plots/pie-portfolio-interactive/metadata/python/seaborn.yaml delete mode 100644 plots/pie-portfolio-interactive/metadata/r/ggplot2.yaml delete mode 100644 plots/pie-portfolio-interactive/specification.md delete mode 100644 plots/pie-portfolio-interactive/specification.yaml delete mode 100644 plots/scatter-animated-controls/implementations/python/altair.py delete mode 100644 plots/scatter-animated-controls/implementations/python/bokeh.py delete mode 100644 plots/scatter-animated-controls/implementations/python/letsplot.py delete mode 100644 plots/scatter-animated-controls/implementations/python/matplotlib.py delete mode 100644 plots/scatter-animated-controls/implementations/python/plotly.py delete mode 100644 plots/scatter-animated-controls/implementations/python/plotnine.py delete mode 100644 plots/scatter-animated-controls/implementations/python/pygal.py delete mode 100644 plots/scatter-animated-controls/implementations/python/seaborn.py delete mode 100644 plots/scatter-animated-controls/metadata/python/altair.yaml delete mode 100644 plots/scatter-animated-controls/metadata/python/bokeh.yaml delete mode 100644 plots/scatter-animated-controls/metadata/python/letsplot.yaml delete mode 100644 plots/scatter-animated-controls/metadata/python/matplotlib.yaml delete mode 100644 plots/scatter-animated-controls/metadata/python/plotly.yaml delete mode 100644 plots/scatter-animated-controls/metadata/python/plotnine.yaml delete mode 100644 plots/scatter-animated-controls/metadata/python/pygal.yaml delete mode 100644 plots/scatter-animated-controls/metadata/python/seaborn.yaml delete mode 100644 plots/scatter-animated-controls/specification.md delete mode 100644 plots/scatter-animated-controls/specification.yaml delete mode 100644 plots/scatter-brush-zoom/implementations/python/altair.py delete mode 100644 plots/scatter-brush-zoom/implementations/python/bokeh.py delete mode 100644 plots/scatter-brush-zoom/implementations/python/letsplot.py delete mode 100644 plots/scatter-brush-zoom/implementations/python/plotly.py delete mode 100644 plots/scatter-brush-zoom/implementations/python/pygal.py delete mode 100644 plots/scatter-brush-zoom/metadata/python/altair.yaml delete mode 100644 plots/scatter-brush-zoom/metadata/python/bokeh.yaml delete mode 100644 plots/scatter-brush-zoom/metadata/python/letsplot.yaml delete mode 100644 plots/scatter-brush-zoom/metadata/python/plotly.yaml delete mode 100644 plots/scatter-brush-zoom/metadata/python/pygal.yaml delete mode 100644 plots/scatter-brush-zoom/specification.md delete mode 100644 plots/scatter-brush-zoom/specification.yaml delete mode 100644 plots/scatter-matrix-interactive/implementations/python/altair.py delete mode 100644 plots/scatter-matrix-interactive/implementations/python/bokeh.py delete mode 100644 plots/scatter-matrix-interactive/implementations/python/letsplot.py delete mode 100644 plots/scatter-matrix-interactive/implementations/python/matplotlib.py delete mode 100644 plots/scatter-matrix-interactive/implementations/python/plotly.py delete mode 100644 plots/scatter-matrix-interactive/implementations/python/plotnine.py delete mode 100644 plots/scatter-matrix-interactive/implementations/python/pygal.py delete mode 100644 plots/scatter-matrix-interactive/implementations/python/seaborn.py delete mode 100644 plots/scatter-matrix-interactive/metadata/python/altair.yaml delete mode 100644 plots/scatter-matrix-interactive/metadata/python/bokeh.yaml delete mode 100644 plots/scatter-matrix-interactive/metadata/python/letsplot.yaml delete mode 100644 plots/scatter-matrix-interactive/metadata/python/matplotlib.yaml delete mode 100644 plots/scatter-matrix-interactive/metadata/python/plotly.yaml delete mode 100644 plots/scatter-matrix-interactive/metadata/python/plotnine.yaml delete mode 100644 plots/scatter-matrix-interactive/metadata/python/pygal.yaml delete mode 100644 plots/scatter-matrix-interactive/metadata/python/seaborn.yaml delete mode 100644 plots/scatter-matrix-interactive/specification.md delete mode 100644 plots/scatter-matrix-interactive/specification.yaml diff --git a/plots/bar-drilldown/implementations/python/altair.py b/plots/bar-drilldown/implementations/python/altair.py deleted file mode 100644 index 6dd3fb4fa9..0000000000 --- a/plots/bar-drilldown/implementations/python/altair.py +++ /dev/null @@ -1,320 +0,0 @@ -""" anyplot.ai -bar-drilldown: Column Chart with Hierarchical Drilling -Library: altair 6.1.0 | Python 3.13.13 -Quality: 90/100 | Updated: 2026-05-20 -""" - -import json -import os - -import altair as alt -import pandas as pd - - -# 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" - -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477"] - -# Hierarchical data: Sales by Region -> Country -> City -# Children always sum exactly to their parent value -data = [ - # Root level - {"id": "asia", "name": "Asia Pacific", "value": 520, "parent": None}, - {"id": "americas", "name": "Americas", "value": 450, "parent": None}, - {"id": "europe", "name": "Europe", "value": 380, "parent": None}, - {"id": "africa", "name": "Africa", "value": 180, "parent": None}, - # Americas: 280 + 95 + 75 = 450 - {"id": "usa", "name": "USA", "value": 280, "parent": "americas"}, - {"id": "canada", "name": "Canada", "value": 95, "parent": "americas"}, - {"id": "brazil", "name": "Brazil", "value": 75, "parent": "americas"}, - # Europe: 145 + 120 + 115 = 380 - {"id": "germany", "name": "Germany", "value": 145, "parent": "europe"}, - {"id": "uk", "name": "UK", "value": 120, "parent": "europe"}, - {"id": "france", "name": "France", "value": 115, "parent": "europe"}, - # Asia Pacific: 220 + 165 + 135 = 520 - {"id": "china", "name": "China", "value": 220, "parent": "asia"}, - {"id": "japan", "name": "Japan", "value": 165, "parent": "asia"}, - {"id": "india", "name": "India", "value": 135, "parent": "asia"}, - # Africa: 65 + 60 + 55 = 180 - {"id": "nigeria", "name": "Nigeria", "value": 65, "parent": "africa"}, - {"id": "egypt", "name": "Egypt", "value": 60, "parent": "africa"}, - {"id": "south_africa", "name": "South Africa", "value": 55, "parent": "africa"}, - # USA: 120 + 90 + 70 = 280 - {"id": "nyc", "name": "New York", "value": 120, "parent": "usa"}, - {"id": "la", "name": "Los Angeles", "value": 90, "parent": "usa"}, - {"id": "chicago", "name": "Chicago", "value": 70, "parent": "usa"}, - # UK: 75 + 25 + 20 = 120 - {"id": "london", "name": "London", "value": 75, "parent": "uk"}, - {"id": "manchester", "name": "Manchester", "value": 25, "parent": "uk"}, - {"id": "birmingham", "name": "Birmingham", "value": 20, "parent": "uk"}, - # China: 95 + 80 + 45 = 220 - {"id": "shanghai", "name": "Shanghai", "value": 95, "parent": "china"}, - {"id": "beijing", "name": "Beijing", "value": 80, "parent": "china"}, - {"id": "shenzhen", "name": "Shenzhen", "value": 45, "parent": "china"}, -] - -df = pd.DataFrame(data) - -# Root-level data sorted by value descending for consistent color mapping -root_df = df[df["parent"].isna()].copy() -root_order = root_df.sort_values("value", ascending=False)["name"].tolist() -region_colors = IMPRINT[: len(root_order)] - -TITLE = "bar-drilldown · python · altair · anyplot.ai" - -# Bars layer -bars = ( - alt.Chart(root_df) - .mark_bar(cornerRadiusTopLeft=8, cornerRadiusTopRight=8, cursor="pointer") - .encode( - x=alt.X("name:N", title="Region", sort=root_order), - y=alt.Y("value:Q", title="Sales (millions USD)"), - color=alt.Color("name:N", scale=alt.Scale(domain=root_order, range=region_colors), legend=None), - tooltip=[alt.Tooltip("name:N", title="Region"), alt.Tooltip("value:Q", title="Sales ($M)", format=",.0f")], - ) -) - -# Value labels above bars — no in-bar text noise -text_labels = ( - alt.Chart(root_df) - .mark_text(dy=-10, fontSize=12, fontWeight="bold") - .encode( - x=alt.X("name:N", sort=root_order), - y=alt.Y("value:Q"), - text=alt.Text("value:Q", format="$,.0f"), - color=alt.value(INK), - ) -) - -chart = ( - (bars + text_labels) - .properties( - width=800, - height=450, - background=PAGE_BG, - title=alt.Title( - text=TITLE, - subtitle="Sales by Region — open the HTML version to drill down interactively", - fontSize=16, - subtitleFontSize=12, - subtitleColor=INK_SOFT, - anchor="middle", - ), - ) - .configure_view(fill=PAGE_BG, stroke=None) - .configure_axis( - domainColor=INK_SOFT, - tickColor=INK_SOFT, - gridColor=INK, - gridOpacity=0.10, - labelColor=INK_SOFT, - titleColor=INK, - labelFontSize=10, - titleFontSize=12, - labelAngle=0, - ) - .configure_title(color=INK) -) - -# Save static PNG (3200×1800 px) -chart.save(f"plot-{THEME}.png", scale_factor=4.0) - -# Interactive HTML with full drilldown functionality -data_json = df.to_json(orient="records") -id_to_name_json = json.dumps(df.set_index("id")["name"].to_dict()) -okabe_ito_json = json.dumps(IMPRINT) - -html_content = f""" - - - - bar-drilldown — altair — anyplot.ai - - - - - - - -
- - - -""" - -with open(f"plot-{THEME}.html", "w") as f: - f.write(html_content) diff --git a/plots/bar-drilldown/implementations/python/bokeh.py b/plots/bar-drilldown/implementations/python/bokeh.py deleted file mode 100644 index 7cfbf428ba..0000000000 --- a/plots/bar-drilldown/implementations/python/bokeh.py +++ /dev/null @@ -1,344 +0,0 @@ -""" anyplot.ai -bar-drilldown: Column Chart with Hierarchical Drilling -Library: bokeh 3.9.0 | Python 3.13.13 -Quality: 90/100 | Updated: 2026-05-20 -""" - -import os -import sys -import time -from pathlib import Path - - -# This file is named bokeh.py — remove its directory from sys.path so that -# `import bokeh` resolves to the installed package, not this file itself. -sys.path = [p for p in sys.path if Path(p).resolve() != Path(__file__).resolve().parent] - -from bokeh.io import save -from bokeh.layouts import column, row -from bokeh.models import Button, ColumnDataSource, CustomJS, Div, LabelSet, TapTool -from bokeh.plotting import figure -from selenium import webdriver -from selenium.webdriver.chrome.options import Options - - -# 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" - -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477"] - -# Data: 3-level geographic sales hierarchy (Region → Country → City) -hierarchy_data = { - "root": {"children": ["north_america", "europe", "asia_pacific"]}, - "north_america": {"name": "North America", "value": 450, "parent": "root", "children": ["usa", "canada", "mexico"]}, - "europe": {"name": "Europe", "value": 380, "parent": "root", "children": ["uk", "germany", "france"]}, - "asia_pacific": { - "name": "Asia Pacific", - "value": 320, - "parent": "root", - "children": ["japan", "australia", "singapore"], - }, - "usa": {"name": "USA", "value": 280, "parent": "north_america", "children": ["new_york", "los_angeles", "chicago"]}, - "canada": { - "name": "Canada", - "value": 95, - "parent": "north_america", - "children": ["toronto", "vancouver", "montreal"], - }, - "mexico": { - "name": "Mexico", - "value": 75, - "parent": "north_america", - "children": ["mexico_city", "guadalajara", "monterrey"], - }, - "uk": {"name": "UK", "value": 140, "parent": "europe", "children": ["london", "manchester", "birmingham"]}, - "germany": {"name": "Germany", "value": 130, "parent": "europe", "children": ["berlin", "munich", "hamburg"]}, - "france": {"name": "France", "value": 110, "parent": "europe", "children": ["paris", "lyon", "marseille"]}, - "japan": {"name": "Japan", "value": 150, "parent": "asia_pacific", "children": ["tokyo", "osaka", "kyoto"]}, - "australia": { - "name": "Australia", - "value": 100, - "parent": "asia_pacific", - "children": ["sydney", "melbourne", "brisbane"], - }, - "singapore": {"name": "Singapore", "value": 70, "parent": "asia_pacific", "children": []}, - "new_york": {"name": "New York", "value": 120, "parent": "usa", "children": []}, - "los_angeles": {"name": "Los Angeles", "value": 95, "parent": "usa", "children": []}, - "chicago": {"name": "Chicago", "value": 65, "parent": "usa", "children": []}, - "toronto": {"name": "Toronto", "value": 45, "parent": "canada", "children": []}, - "vancouver": {"name": "Vancouver", "value": 30, "parent": "canada", "children": []}, - "montreal": {"name": "Montreal", "value": 20, "parent": "canada", "children": []}, - "mexico_city": {"name": "Mexico City", "value": 40, "parent": "mexico", "children": []}, - "guadalajara": {"name": "Guadalajara", "value": 20, "parent": "mexico", "children": []}, - "monterrey": {"name": "Monterrey", "value": 15, "parent": "mexico", "children": []}, - "london": {"name": "London", "value": 65, "parent": "uk", "children": []}, - "manchester": {"name": "Manchester", "value": 40, "parent": "uk", "children": []}, - "birmingham": {"name": "Birmingham", "value": 35, "parent": "uk", "children": []}, - "berlin": {"name": "Berlin", "value": 55, "parent": "germany", "children": []}, - "munich": {"name": "Munich", "value": 45, "parent": "germany", "children": []}, - "hamburg": {"name": "Hamburg", "value": 30, "parent": "germany", "children": []}, - "paris": {"name": "Paris", "value": 50, "parent": "france", "children": []}, - "lyon": {"name": "Lyon", "value": 35, "parent": "france", "children": []}, - "marseille": {"name": "Marseille", "value": 25, "parent": "france", "children": []}, - "tokyo": {"name": "Tokyo", "value": 70, "parent": "japan", "children": []}, - "osaka": {"name": "Osaka", "value": 50, "parent": "japan", "children": []}, - "kyoto": {"name": "Kyoto", "value": 30, "parent": "japan", "children": []}, - "sydney": {"name": "Sydney", "value": 45, "parent": "australia", "children": []}, - "melbourne": {"name": "Melbourne", "value": 35, "parent": "australia", "children": []}, - "brisbane": {"name": "Brisbane", "value": 20, "parent": "australia", "children": []}, -} - -# Initial root-level data -root_children = hierarchy_data["root"]["children"] -init_names = [hierarchy_data[c]["name"] for c in root_children] -init_values = [hierarchy_data[c]["value"] for c in root_children] -init_max = max(init_values) - -source = ColumnDataSource( - data={ - "names": init_names, - "values": init_values, - "ids": root_children, - "colors": [IMPRINT[i % len(IMPRINT)] for i in range(len(init_names))], - "has_children": [len(hierarchy_data[c].get("children", [])) > 0 for c in root_children], - "label_y": [v + init_max * 0.025 for v in init_values], - } -) - -state_source = ColumnDataSource( - data={"current_parent": ["root"], "breadcrumb_path": ["All"], "breadcrumb_ids": ["root"]} -) - -# Plot — figure height 1640 leaves room for nav row in the combined layout -p = figure( - x_range=init_names, - width=3200, - height=1640, - title="bar-drilldown · python · bokeh · anyplot.ai", - tools="tap", - toolbar_location=None, - x_axis_label="Region", - y_axis_label="Sales ($ millions)", - min_border_bottom=160, - min_border_left=180, - min_border_top=110, - min_border_right=60, -) - -p.vbar( - x="names", - top="values", - width=0.65, - source=source, - fill_color="colors", - line_color=PAGE_BG, - line_width=3, - fill_alpha=0.92, -) - -labels = LabelSet( - x="names", - y="label_y", - text="values", - source=source, - text_align="center", - text_baseline="bottom", - text_font_size="34pt", - text_font_style="bold", - text_color=INK, -) -p.add_layout(labels) - -# Style -p.background_fill_color = PAGE_BG -p.border_fill_color = PAGE_BG -p.outline_line_color = INK_SOFT - -p.title.text_font_size = "50pt" -p.title.text_color = INK - -p.xaxis.axis_label_text_font_size = "42pt" -p.yaxis.axis_label_text_font_size = "42pt" -p.xaxis.axis_label_text_color = INK -p.yaxis.axis_label_text_color = INK - -p.xaxis.major_label_text_font_size = "34pt" -p.yaxis.major_label_text_font_size = "34pt" -p.xaxis.major_label_text_color = INK_SOFT -p.yaxis.major_label_text_color = INK_SOFT -p.xaxis.major_label_orientation = 0 - -p.xaxis.axis_line_color = INK_SOFT -p.yaxis.axis_line_color = INK_SOFT -p.xaxis.major_tick_line_color = INK_SOFT -p.yaxis.major_tick_line_color = INK_SOFT - -p.xgrid.grid_line_color = None -p.ygrid.grid_line_color = INK -p.ygrid.grid_line_alpha = 0.10 - -p.y_range.start = 0 -p.y_range.end = init_max * 1.18 - -# Navigation: breadcrumb trail + back button -breadcrumb_div = Div( - text=( - f'
' - f"All
" - ), - width=2700, - height=90, -) - -back_button = Button(label="← Back", button_type="primary", width=240, height=90, disabled=True) - -# Drill-down: click a bar to show its children -drill_callback = CustomJS( - args={ - "source": source, - "state": state_source, - "p": p, - "hierarchy": hierarchy_data, - "okabe_ito": IMPRINT, - "breadcrumb_div": breadcrumb_div, - "back_button": back_button, - "ink": INK, - "ink_soft": INK_SOFT, - "elevated_bg": ELEVATED_BG, - }, - code=""" - const indices = source.selected.indices; - if (indices.length === 0) return; - const idx = indices[0]; - const clicked_id = source.data['ids'][idx]; - const node = hierarchy[clicked_id]; - if (!node || !node.children || node.children.length === 0) { - source.selected.indices = []; - return; - } - const children_ids = node.children; - const names = [], values = [], has_children = []; - for (const cid of children_ids) { - const c = hierarchy[cid]; - names.push(c.name); - values.push(c.value); - has_children.push(c.children && c.children.length > 0); - } - source.data['names'] = names; - source.data['values'] = values; - source.data['ids'] = children_ids; - source.data['has_children'] = has_children; - source.data['colors'] = names.map((_, i) => okabe_ito[i % okabe_ito.length]); - const max_v = Math.max(...values); - source.data['label_y'] = values.map(v => v + max_v * 0.025); - p.x_range.factors = names; - p.y_range.end = max_v * 1.18; - const path = state.data['breadcrumb_path'].slice(); - const ids_path = state.data['breadcrumb_ids'].slice(); - path.push(node.name); - ids_path.push(clicked_id); - state.data['current_parent'] = [clicked_id]; - state.data['breadcrumb_path'] = path; - state.data['breadcrumb_ids'] = ids_path; - let html = '
'; - for (let i = 0; i < path.length; i++) { - if (i > 0) html += ' '; - html += (i === path.length - 1) ? '' + path[i] + '' : path[i]; - } - html += '
'; - breadcrumb_div.text = html; - back_button.disabled = false; - source.selected.indices = []; - source.change.emit(); - state.change.emit(); -""", -) - -# Drill-up: back button navigates to parent level -drill_up_callback = CustomJS( - args={ - "source": source, - "state": state_source, - "p": p, - "hierarchy": hierarchy_data, - "okabe_ito": IMPRINT, - "breadcrumb_div": breadcrumb_div, - "back_button": back_button, - "ink": INK, - "ink_soft": INK_SOFT, - "elevated_bg": ELEVATED_BG, - }, - code=""" - const path = state.data['breadcrumb_path']; - const ids_path = state.data['breadcrumb_ids']; - if (ids_path.length <= 1) return; - const new_path = path.slice(0, -1); - const new_ids = ids_path.slice(0, -1); - const parent_id = new_ids[new_ids.length - 1]; - const children_ids = (parent_id === 'root') ? hierarchy['root']['children'] : hierarchy[parent_id]['children']; - const names = [], values = [], has_children = []; - for (const cid of children_ids) { - const c = hierarchy[cid]; - names.push(c.name); - values.push(c.value); - has_children.push(c.children && c.children.length > 0); - } - source.data['names'] = names; - source.data['values'] = values; - source.data['ids'] = children_ids; - source.data['has_children'] = has_children; - source.data['colors'] = names.map((_, i) => okabe_ito[i % okabe_ito.length]); - const max_v = Math.max(...values); - source.data['label_y'] = values.map(v => v + max_v * 0.025); - p.x_range.factors = names; - p.y_range.end = max_v * 1.18; - state.data['current_parent'] = [parent_id]; - state.data['breadcrumb_path'] = new_path; - state.data['breadcrumb_ids'] = new_ids; - let html = '
'; - for (let i = 0; i < new_path.length; i++) { - if (i > 0) html += ' '; - html += (i === new_path.length - 1) ? '' + new_path[i] + '' : new_path[i]; - } - html += '
'; - breadcrumb_div.text = html; - if (new_ids.length <= 1) back_button.disabled = true; - source.change.emit(); - state.change.emit(); -""", -) - -back_button.js_on_click(drill_up_callback) -p.select(TapTool).callback = drill_callback - -nav_row = row(breadcrumb_div, back_button, spacing=20) -layout = column(nav_row, p, spacing=10) - -# Save HTML for interactive use -save(layout, filename=f"plot-{THEME}.html", title="bar-drilldown · python · bokeh · anyplot.ai") - -# Save PNG via headless Chrome (Selenium) — export_png is not reliable in this env -W, H = 3200, 1860 -opts = Options() -for arg in ( - "--headless=new", - "--no-sandbox", - "--disable-dev-shm-usage", - "--disable-gpu", - f"--window-size={W},{H}", - "--hide-scrollbars", -): - opts.add_argument(arg) -driver = webdriver.Chrome(options=opts) -driver.set_window_size(W, H) -driver.get(f"file://{Path(f'plot-{THEME}.html').resolve()}") -time.sleep(3) -driver.save_screenshot(f"plot-{THEME}.png") -driver.quit() diff --git a/plots/bar-drilldown/implementations/python/letsplot.py b/plots/bar-drilldown/implementations/python/letsplot.py deleted file mode 100644 index 0463022858..0000000000 --- a/plots/bar-drilldown/implementations/python/letsplot.py +++ /dev/null @@ -1,589 +0,0 @@ -""" anyplot.ai -bar-drilldown: Column Chart with Hierarchical Drilling -Library: letsplot 4.9.0 | Python 3.13.13 -Quality: 90/100 | Updated: 2026-05-20 -""" - -import json -import os - -import pandas as pd -from lets_plot import ( - LetsPlot, - aes, - element_blank, - element_line, - element_rect, - element_text, - geom_bar, - geom_hline, - geom_text, - ggplot, - ggsize, - labs, - scale_fill_manual, - scale_y_continuous, - theme, - theme_minimal, -) -from lets_plot.export import ggsave - - -LetsPlot.setup_html() - -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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" -RULE = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)" - -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233"] - -# Hierarchical data: regional sales breakdown (region → quarterly) -hierarchy_data = { - "root": {"id": "root", "name": "Total Sales", "children": ["north", "south", "east", "west"]}, - "north": { - "id": "north", - "name": "North Region", - "parent": "root", - "value": 485000, - "children": ["north_q1", "north_q2", "north_q3", "north_q4"], - }, - "north_q1": {"id": "north_q1", "name": "Q1", "parent": "north", "value": 98000}, - "north_q2": {"id": "north_q2", "name": "Q2", "parent": "north", "value": 125000}, - "north_q3": {"id": "north_q3", "name": "Q3", "parent": "north", "value": 142000}, - "north_q4": {"id": "north_q4", "name": "Q4", "parent": "north", "value": 120000}, - "south": { - "id": "south", - "name": "South Region", - "parent": "root", - "value": 392000, - "children": ["south_q1", "south_q2", "south_q3", "south_q4"], - }, - "south_q1": {"id": "south_q1", "name": "Q1", "parent": "south", "value": 85000}, - "south_q2": {"id": "south_q2", "name": "Q2", "parent": "south", "value": 98000}, - "south_q3": {"id": "south_q3", "name": "Q3", "parent": "south", "value": 112000}, - "south_q4": {"id": "south_q4", "name": "Q4", "parent": "south", "value": 97000}, - "east": { - "id": "east", - "name": "East Region", - "parent": "root", - "value": 528000, - "children": ["east_q1", "east_q2", "east_q3", "east_q4"], - }, - "east_q1": {"id": "east_q1", "name": "Q1", "parent": "east", "value": 115000}, - "east_q2": {"id": "east_q2", "name": "Q2", "parent": "east", "value": 138000}, - "east_q3": {"id": "east_q3", "name": "Q3", "parent": "east", "value": 155000}, - "east_q4": {"id": "east_q4", "name": "Q4", "parent": "east", "value": 120000}, - "west": { - "id": "west", - "name": "West Region", - "parent": "root", - "value": 445000, - "children": ["west_q1", "west_q2", "west_q3", "west_q4"], - }, - "west_q1": {"id": "west_q1", "name": "Q1", "parent": "west", "value": 92000}, - "west_q2": {"id": "west_q2", "name": "Q2", "parent": "west", "value": 118000}, - "west_q3": {"id": "west_q3", "name": "Q3", "parent": "west", "value": 128000}, - "west_q4": {"id": "west_q4", "name": "Q4", "parent": "west", "value": 107000}, -} - -region_color_map = { - "North Region": IMPRINT[0], - "South Region": IMPRINT[1], - "East Region": IMPRINT[2], - "West Region": IMPRINT[3], -} - -# Root-level dataframe for static PNG -root_children = hierarchy_data["root"]["children"] -categories = [hierarchy_data[cid]["name"] for cid in root_children] -values = [hierarchy_data[cid]["value"] for cid in root_children] -value_labels = [f"${v // 1000}K" for v in values] - -df = pd.DataFrame({"category": categories, "value": values, "value_label": value_labels}) -df["category"] = pd.Categorical(df["category"], categories=categories, ordered=True) - -# Peak and mean references for storytelling -mean_sales = int(df["value"].mean()) -df_peak = df[df["value"] == df["value"].max()].copy() - -# Static PNG using letsplot ggplot grammar -plot = ( - ggplot(df, aes(x="category", y="value", fill="category")) - + geom_bar(stat="identity", width=0.7, show_legend=False, color=PAGE_BG, size=0.5) - + geom_hline(yintercept=mean_sales, color=INK_MUTED, size=0.5, linetype="dashed") - + geom_text(aes(label="value_label"), vjust=-0.4, size=8, color=INK, fontface="bold") - + geom_text( - data=df_peak, - mapping=aes(x="category", y="value"), - label="★ highest", - vjust=-2.0, - size=7, - color=IMPRINT[2], - fontface="bold", - ) - + scale_fill_manual(values=IMPRINT) - + scale_y_continuous(format="${,.0f}", limits=[0, 730000], expand=[0, 0]) - + labs( - title="bar-drilldown · python · letsplot · anyplot.ai", - subtitle="Regional Sales · East leads at $528K · Open HTML for interactive drilldown", - x="Region", - y="Sales ($)", - ) - + ggsize(800, 450) - + theme_minimal() - + theme( - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - panel_background=element_rect(fill=PAGE_BG), - panel_grid_major_x=element_blank(), - panel_grid_minor=element_blank(), - panel_grid_major_y=element_line(color=RULE, size=0.3), - axis_title=element_text(color=INK, size=12), - axis_text=element_text(color=INK_SOFT, size=10), - axis_line_x=element_line(color=INK_SOFT), - axis_line_y=element_line(color=INK_SOFT), - plot_title=element_text(color=INK, size=16, face="bold", hjust=0.5), - plot_subtitle=element_text(color=INK_MUTED, size=10, hjust=0.5), - plot_margin=[30, 30, 30, 30], - ) -) - -ggsave(plot, filename=f"plot-{THEME}.png", path=".", scale=4) - -# Prepare data for all hierarchy levels (used in HTML drilldown) -levels_data = {} -for level_id in ["root", "north", "south", "east", "west"]: - level_data = hierarchy_data[level_id] - if "children" in level_data: - children_ids = level_data["children"] - cats = [hierarchy_data[cid]["name"] for cid in children_ids] - vals = [hierarchy_data[cid]["value"] for cid in children_ids] - if level_id == "root": - level_colors = [region_color_map.get(cat, IMPRINT[0]) for cat in cats] - else: - base_color = region_color_map.get(hierarchy_data[level_id]["name"], IMPRINT[0]) - level_colors = [base_color] * len(cats) - levels_data[level_id] = { - "name": hierarchy_data[level_id]["name"], - "categories": cats, - "values": vals, - "colors": level_colors, - "children": children_ids, - "parent": hierarchy_data[level_id].get("parent"), - } - -# HTML theme tokens for interactive output -html_body_bg = PAGE_BG -html_container_bg = ELEVATED_BG -html_text = INK -html_text_soft = INK_SOFT -html_grid = "rgba(26,26,23,0.12)" if THEME == "light" else "rgba(240,239,232,0.12)" -html_tooltip_bg = "rgba(0,0,0,0.88)" if THEME == "light" else "rgba(36,36,32,0.95)" -html_back_btn_border = INK_SOFT -html_brand = IMPRINT[0] - -html_content = f""" - - - - bar-drilldown · python · letsplot · anyplot.ai - - - -
-

bar-drilldown · python · letsplot · anyplot.ai

-

Regional Sales Breakdown with Interactive Drilling

- - - -
- -
-
- -
- Level Total: $1,850,000 -
- -

Click on a column to drill down into quarterly breakdown

-
- - - -""" - -with open(f"plot-{THEME}.html", "w") as f: - f.write(html_content) diff --git a/plots/bar-drilldown/implementations/python/matplotlib.py b/plots/bar-drilldown/implementations/python/matplotlib.py deleted file mode 100644 index 70eceac776..0000000000 --- a/plots/bar-drilldown/implementations/python/matplotlib.py +++ /dev/null @@ -1,127 +0,0 @@ -""" anyplot.ai -bar-drilldown: Column Chart with Hierarchical Drilling -Library: matplotlib 3.10.9 | Python 3.13.13 -Quality: 86/100 | Created: 2026-05-20 -""" - -import os -import sys - - -sys.path = [p for p in sys.path if "implementations" not in p] # noqa: E402 - -import matplotlib.pyplot as plt # noqa: E402 -import numpy as np # noqa: E402 -from matplotlib.gridspec import GridSpec # noqa: E402 -from matplotlib.patches import ConnectionPatch # noqa: E402 - - -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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" -BRAND = "#009E73" -BAR_MUTED = "#C0BEB8" if THEME == "light" else "#4E4E48" - -# Data — 2024 global retail revenue by product category ($K) -categories = ["Electronics", "Apparel", "Home & Garden", "Sports"] -totals = [4820, 3150, 2760, 2390] -focus_idx = 0 # Electronics is the active drilldown category - -sub_labels = ["Phones", "Laptops", "Tablets", "Accessories"] -sub_values = [1820, 1640, 890, 470] - -# Layout: two stacked panels (1:1 canvas = 2400 × 2400 px) -fig = plt.figure(figsize=(6, 6), dpi=400, facecolor=PAGE_BG) -gs = GridSpec(2, 1, figure=fig, height_ratios=[1, 1.05], hspace=0.48, top=0.91, bottom=0.08, left=0.13, right=0.97) -ax1 = fig.add_subplot(gs[0]) -ax2 = fig.add_subplot(gs[1]) -ax1.set_facecolor(PAGE_BG) -ax2.set_facecolor(PAGE_BG) - -# Overview panel — all top-level categories -x1 = np.arange(len(categories)) -bar_colors = [BRAND if i == focus_idx else BAR_MUTED for i in range(len(categories))] -bars1 = ax1.bar(x1, totals, color=bar_colors, width=0.55, edgecolor=PAGE_BG, linewidth=0.8) -for bar, val in zip(bars1, totals, strict=True): - ax1.text( - bar.get_x() + bar.get_width() / 2, - bar.get_height() + 55, - f"${val:,}K", - ha="center", - va="bottom", - fontsize=8.5, - color=INK_SOFT, - ) -ax1.set_xticks(x1) -ax1.set_xticklabels(categories, fontsize=9, color=INK_SOFT) -ax1.set_ylabel("Revenue ($K)", fontsize=9, color=INK) -ax1.tick_params(axis="both", labelsize=8.5, colors=INK_SOFT) -ax1.set_ylim(0, max(totals) * 1.22) -ax1.set_title("All Categories · 2024", fontsize=11, color=INK, pad=6) - -# Drilldown panel — Electronics subcategories -x2 = np.arange(len(sub_labels)) -bars2 = ax2.bar(x2, sub_values, color=BRAND, width=0.55, edgecolor=PAGE_BG, linewidth=0.8) -for bar, val in zip(bars2, sub_values, strict=True): - ax2.text( - bar.get_x() + bar.get_width() / 2, - bar.get_height() + 28, - f"${val:,}K", - ha="center", - va="bottom", - fontsize=8.5, - color=INK_SOFT, - ) -ax2.set_xticks(x2) -ax2.set_xticklabels(sub_labels, fontsize=9, color=INK_SOFT) -ax2.set_ylabel("Revenue ($K)", fontsize=9, color=INK) -ax2.tick_params(axis="both", labelsize=8.5, colors=INK_SOFT) -ax2.set_ylim(0, max(sub_values) * 1.22) -# Title doubles as breadcrumb trail -ax2.set_title("All Categories › Electronics", fontsize=11, color=INK, pad=6) - -# Back-navigation affordance (static approximation of back-button) -ax2.text( - 0.01, - 0.97, - "◄ Back to All Categories", - transform=ax2.transAxes, - fontsize=8, - color=INK_SOFT, - va="top", - ha="left", - fontstyle="italic", -) - -for ax in (ax1, ax2): - ax.spines["top"].set_visible(False) - ax.spines["right"].set_visible(False) - for s in ("left", "bottom"): - ax.spines[s].set_color(INK_SOFT) - ax.yaxis.grid(True, alpha=0.10, linewidth=0.8, color=INK) - ax.set_axisbelow(True) - -# ConnectionPatch: visual connector from the active bar's top edges to the drilldown panel -bar_hw = 0.55 / 2 # half-width of the Electronics bar -for x_a, x_b in [(-bar_hw, 0.0), (bar_hw, 1.0)]: - con = ConnectionPatch( - xyA=(focus_idx + x_a, totals[focus_idx]), - xyB=(x_b, 1.0), - coordsA="data", - coordsB="axes fraction", - axesA=ax1, - axesB=ax2, - color=BRAND, - lw=0.9, - linestyle="--", - alpha=0.35, - clip_on=False, - ) - fig.add_artist(con) - -fig.suptitle("bar-drilldown · python · matplotlib · anyplot.ai", fontsize=12, fontweight="medium", color=INK) - -plt.savefig(f"plot-{THEME}.png", dpi=400, bbox_inches="tight", facecolor=PAGE_BG) diff --git a/plots/bar-drilldown/implementations/python/plotly.py b/plots/bar-drilldown/implementations/python/plotly.py deleted file mode 100644 index 4cc48ef44a..0000000000 --- a/plots/bar-drilldown/implementations/python/plotly.py +++ /dev/null @@ -1,246 +0,0 @@ -""" anyplot.ai -bar-drilldown: Column Chart with Hierarchical Drilling -Library: plotly 6.7.0 | Python 3.13.13 -Quality: 93/100 | Updated: 2026-05-23 -""" - -import importlib -import os -import sys - - -sys.path = [p for p in sys.path if p not in ("", ".", os.path.dirname(__file__))] -go = importlib.import_module("plotly.graph_objects") - -# 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" -GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)" - -# Imprint palette — positions 1–4 for 4 quarters -PALETTE = ["#009E73", "#C475FD", "#AE3030", "#4467A3"] - -# Data: 2024 Retail Sales — quarter → month two-level hierarchy -QUARTERS = [ - {"name": "Q1 (Jan–Mar)", "value": 1240, "label": "$1.24M", "key": "q1"}, - {"name": "Q2 (Apr–Jun)", "value": 1680, "label": "$1.68M", "key": "q2"}, - {"name": "Q3 (Jul–Sep)", "value": 2140, "label": "$2.14M", "key": "q3"}, - {"name": "Q4 (Oct–Dec)", "value": 2380, "label": "$2.38M", "key": "q4"}, -] -MONTHLY = { - "q1": { - "names": ["January", "February", "March"], - "values": [390, 420, 430], - "labels": ["$390K", "$420K", "$430K"], - "path": "2024 > Q1", - "prefix": "Q1 Jan–Mar Detail", - }, - "q2": { - "names": ["April", "May", "June"], - "values": [520, 570, 590], - "labels": ["$520K", "$570K", "$590K"], - "path": "2024 > Q2", - "prefix": "Q2 Apr–Jun Detail", - }, - "q3": { - "names": ["July", "August", "September"], - "values": [680, 750, 710], - "labels": ["$680K", "$750K", "$710K"], - "path": "2024 > Q3", - "prefix": "Q3 Jul–Sep Detail", - }, - "q4": { - "names": ["October", "November", "December"], - "values": [740, 940, 700], - "labels": ["$740K", "$940K", "$700K"], - "path": "2024 > Q4", - "prefix": "Q4 Oct–Dec Detail", - }, -} - -_edge = {"color": PAGE_BG, "width": 2} -_chrome = { - "textposition": "outside", - "textfont": {"size": 12, "color": INK}, - "hovertemplate": "%{x}
Revenue: %{text}", - "cliponaxis": False, -} - -# Annual trace — Q4 at full opacity (holiday peak focal point), Q1–Q3 muted -annual_trace = go.Bar( - **_chrome, - x=[q["name"] for q in QUARTERS], - y=[q["value"] for q in QUARTERS], - text=[q["label"] for q in QUARTERS], - marker={"color": PALETTE, "line": _edge, "opacity": [0.7, 0.7, 0.7, 1.0]}, -) - -# Monthly traces — bars inherit parent quarter's palette color (cross-level color identity) -monthly_traces = [ - go.Bar( - **_chrome, - x=MONTHLY[q["key"]]["names"], - y=MONTHLY[q["key"]]["values"], - text=MONTHLY[q["key"]]["labels"], - marker={"color": PALETTE[i], "line": _edge}, - ) - for i, q in enumerate(QUARTERS) -] - -# Annotations -_footer = { - "text": "Click a quarter bar to drill into monthly detail · use the dropdown to navigate back", - "xref": "paper", - "yref": "paper", - "x": 0.5, - "y": -0.18, - "showarrow": False, - "font": {"size": 10, "color": INK_SOFT}, - "xanchor": "center", -} -Q_RANGE = [0, max(q["value"] for q in QUARTERS) * 1.38] -_peak = { - "text": "★ Holiday Peak", - "x": "Q4 (Oct–Dec)", - "xref": "x", - "y": max(q["value"] for q in QUARTERS) * 1.25, - "yref": "y", - "showarrow": False, - "font": {"size": 10, "color": PALETTE[3]}, - "xanchor": "center", -} - -# Frames — annual overview + one per quarter -ANNUAL_TITLE = "2024 Retail Sales · bar-drilldown · python · plotly · anyplot.ai" - -annual_frame = go.Frame( - name="annual", - data=[annual_trace], - layout=go.Layout( - title_text=ANNUAL_TITLE, - xaxis={"title": {"text": "Quarter", "font": {"size": 12, "color": INK}}}, - yaxis={"title": {"text": "Revenue ($K)", "font": {"size": 12, "color": INK}}, "range": Q_RANGE}, - annotations=[_footer, _peak], - ), -) -quarterly_frames = [ - go.Frame( - name=q["key"], - data=[monthly_traces[i]], - layout=go.Layout( - title_text=(f"{MONTHLY[q['key']]['prefix']} · bar-drilldown · python · plotly · anyplot.ai"), - xaxis={ - "title": {"text": f"Month · Path: {MONTHLY[q['key']]['path']}", "font": {"size": 12, "color": INK}} - }, - yaxis={ - "title": {"text": "Revenue ($K)", "font": {"size": 12, "color": INK}}, - "range": [0, max(MONTHLY[q["key"]]["values"]) * 1.3], - }, - annotations=[_footer], - ), - ) - for i, q in enumerate(QUARTERS) -] - -# Dropdown navigation buttons -dropdown_buttons = [ - { - "label": "▶ 2024 Annual — All Quarters", - "method": "animate", - "args": [ - ["annual"], - {"mode": "immediate", "frame": {"duration": 400, "redraw": True}, "transition": {"duration": 250}}, - ], - } -] + [ - { - "label": f"▸ {q['name']} — Monthly Detail", - "method": "animate", - "args": [ - [q["key"]], - {"mode": "immediate", "frame": {"duration": 400, "redraw": True}, "transition": {"duration": 250}}, - ], - } - for q in QUARTERS -] - -# Figure -fig = go.Figure(annual_trace) -fig.frames = [annual_frame] + quarterly_frames - -fig.update_layout( - autosize=False, - title={"text": ANNUAL_TITLE, "font": {"size": 16, "color": INK}, "x": 0.5, "xanchor": "center"}, - xaxis={ - "title": {"text": "Quarter", "font": {"size": 12, "color": INK}}, - "tickfont": {"size": 10, "color": INK_SOFT}, - "gridcolor": GRID, - "linecolor": INK_SOFT, - "showgrid": False, - }, - yaxis={ - "title": {"text": "Revenue ($K)", "font": {"size": 12, "color": INK}}, - "tickfont": {"size": 10, "color": INK_SOFT}, - "gridcolor": GRID, - "linecolor": INK_SOFT, - "zerolinecolor": INK_SOFT, - "tickformat": ",d", - "range": Q_RANGE, - "showgrid": True, - }, - paper_bgcolor=PAGE_BG, - plot_bgcolor=PAGE_BG, - font={"color": INK}, - showlegend=False, - margin={"t": 80, "b": 80, "l": 80, "r": 40}, - annotations=[_footer, _peak], - updatemenus=[ - { - "type": "dropdown", - "direction": "down", - "x": 0.0, - "y": 1.14, - "xanchor": "left", - "yanchor": "top", - "showactive": True, - "active": 0, - "bgcolor": ELEVATED_BG, - "bordercolor": INK_SOFT, - "font": {"size": 11, "color": INK}, - "pad": {"r": 10, "t": 5}, - "buttons": dropdown_buttons, - } - ], -) - -# Save PNG — 3200×1800 landscape -fig.write_image(f"plot-{THEME}.png", width=800, height=450, scale=4) - -# Click-on-bar JavaScript: clicking a quarter bar drills into it; clicking a month bar returns to annual -_click_js = ( - "var el = document.querySelectorAll('.js-plotly-plot')[0];" - "var drillMap = {" - "'Q1 (Jan–Mar)': 'q1'," - "'Q2 (Apr–Jun)': 'q2'," - "'Q3 (Jul–Sep)': 'q3'," - "'Q4 (Oct–Dec)': 'q4'" - "};" - "el.on('plotly_click', function(data) {" - " var label = data.points[0].x;" - " var target = drillMap[label] !== undefined ? drillMap[label] : 'annual';" - " Plotly.animate(el, [target], {" - " mode: 'immediate'," - " frame: {duration: 400, redraw: true}," - " transition: {duration: 250}" - " });" - "});" -) -fig.write_html( - f"plot-{THEME}.html", - include_plotlyjs="cdn", - config={"displayModeBar": True, "displaylogo": False}, - post_script=_click_js, -) diff --git a/plots/bar-drilldown/implementations/python/plotnine.py b/plots/bar-drilldown/implementations/python/plotnine.py deleted file mode 100644 index b19bb0706b..0000000000 --- a/plots/bar-drilldown/implementations/python/plotnine.py +++ /dev/null @@ -1,100 +0,0 @@ -""" anyplot.ai -bar-drilldown: Column Chart with Hierarchical Drilling -Library: plotnine 0.15.4 | Python 3.13.13 -Quality: 82/100 | Created: 2026-05-20 -""" - -import os - -import pandas as pd -from plotnine import ( - aes, - element_blank, - element_line, - element_rect, - element_text, - facet_wrap, - geom_col, - geom_text, - ggplot, - labs, - scale_fill_manual, - scale_y_continuous, - theme, - theme_minimal, -) - - -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" - -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233"] - -# Data — Annual retail revenue by category and subcategory ($ millions) -data = { - "category": (["Electronics"] * 4 + ["Apparel"] * 4 + ["Groceries"] * 4 + ["Sports"] * 4), - "subcategory": [ - "Phones", - "Laptops", - "Tablets", - "Cameras", - "Tops", - "Bottoms", - "Jackets", - "Footwear", - "Beverages", - "Produce", - "Dairy", - "Snacks", - "Fitness", - "Outdoor", - "Team", - "Aquatics", - ], - "revenue": [45, 38, 23, 14, 29, 21, 18, 12, 22, 19, 16, 13, 20, 17, 11, 9], -} - -df = pd.DataFrame(data) - -cat_order = ["Electronics", "Apparel", "Groceries", "Sports"] -df["category"] = pd.Categorical(df["category"], categories=cat_order, ordered=True) - -global_order = df.sort_values("revenue", ascending=False)["subcategory"].tolist() -df["subcategory"] = pd.Categorical(df["subcategory"], categories=global_order, ordered=True) - -# Strip labels include category total — L1 summary visible alongside L2 breakdown -cat_totals = df.groupby("category", observed=True)["revenue"].sum().to_dict() -label_order = [f"{c} · ${cat_totals[c]}M total" for c in cat_order] -df["panel_label"] = pd.Categorical( - [f"{c} · ${cat_totals[str(c)]}M total" for c in df["category"]], categories=label_order, ordered=True -) - -plot = ( - ggplot(df, aes(x="subcategory", y="revenue", fill="category")) - + geom_col(width=0.72) - + geom_text(aes(label="revenue"), va="bottom", nudge_y=0.5, size=9, color=INK_SOFT) - + facet_wrap("~panel_label", ncol=2, scales="free") - + scale_fill_manual(values=IMPRINT) - + scale_y_continuous(expand=(0.08, 0)) - + labs(x="", y="Revenue ($ millions)", title="bar-drilldown · python · plotnine · anyplot.ai") - + theme_minimal() - + theme( - figure_size=(6, 6), - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - panel_background=element_rect(fill=PAGE_BG), - panel_grid_major_y=element_line(color=INK, size=0.3, alpha=0.10), - panel_grid_major_x=element_blank(), - panel_grid_minor=element_blank(), - legend_position="none", - axis_title=element_text(color=INK, size=10), - axis_text=element_text(color=INK_SOFT, size=8), - plot_title=element_text(color=INK, size=12), - strip_background=element_rect(fill=ELEVATED_BG, color="none"), - strip_text=element_text(color=INK, size=9, face="bold"), - ) -) - -plot.save(f"plot-{THEME}.png", dpi=400, width=6, height=6, units="in") diff --git a/plots/bar-drilldown/implementations/python/pygal.py b/plots/bar-drilldown/implementations/python/pygal.py deleted file mode 100644 index 14f3206e6b..0000000000 --- a/plots/bar-drilldown/implementations/python/pygal.py +++ /dev/null @@ -1,264 +0,0 @@ -""" anyplot.ai -bar-drilldown: Column Chart with Hierarchical Drilling -Library: pygal 3.1.0 | Python 3.13.13 -Quality: 88/100 | Updated: 2026-05-20 -""" - -import json -import os -import sys - - -# This file is named pygal.py — without this fix it shadows the installed pygal package. -_d = os.path.dirname(os.path.abspath(__file__)) -sys.path = [p for p in sys.path if os.path.abspath(p) != _d] -os.chdir(_d) # ensure relative output paths (plot-*.png/html) land next to this script - -import pygal -from pygal.style import Style - - -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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -IMPRINT = ("#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477") - -# Hierarchical data: Company -> Divisions -> Teams -> Sub-teams -hierarchy = { - "root": {"name": "Company", "value": None, "parent": None, "children": ["tech", "sales", "ops"]}, - "tech": {"name": "Technology", "value": 4200, "parent": "root", "children": ["dev", "infra", "data"]}, - "sales": {"name": "Sales", "value": 3100, "parent": "root", "children": ["retail", "enterprise", "partner"]}, - "ops": {"name": "Operations", "value": 2400, "parent": "root", "children": ["hr", "finance", "legal"]}, - "dev": {"name": "Development", "value": 1800, "parent": "tech", "children": ["frontend", "backend", "mobile"]}, - "infra": {"name": "Infrastructure", "value": 1400, "parent": "tech", "children": ["cloud", "security", "network"]}, - "data": {"name": "Data Science", "value": 1000, "parent": "tech", "children": ["ml", "analytics", "etl"]}, - "retail": {"name": "Retail", "value": 1200, "parent": "sales", "children": []}, - "enterprise": {"name": "Enterprise", "value": 1400, "parent": "sales", "children": []}, - "partner": {"name": "Partners", "value": 500, "parent": "sales", "children": []}, - "hr": {"name": "Human Resources", "value": 800, "parent": "ops", "children": []}, - "finance": {"name": "Finance", "value": 1000, "parent": "ops", "children": []}, - "legal": {"name": "Legal", "value": 600, "parent": "ops", "children": []}, - "frontend": {"name": "Frontend", "value": 600, "parent": "dev", "children": []}, - "backend": {"name": "Backend", "value": 800, "parent": "dev", "children": []}, - "mobile": {"name": "Mobile", "value": 400, "parent": "dev", "children": []}, - "cloud": {"name": "Cloud", "value": 600, "parent": "infra", "children": []}, - "security": {"name": "Security", "value": 500, "parent": "infra", "children": []}, - "network": {"name": "Network", "value": 300, "parent": "infra", "children": []}, - "ml": {"name": "Machine Learning", "value": 450, "parent": "data", "children": []}, - "analytics": {"name": "Analytics", "value": 350, "parent": "data", "children": []}, - "etl": {"name": "ETL Pipeline", "value": 200, "parent": "data", "children": []}, -} - -# Use primary INK for foreground_subtle in dark theme so value labels stay fully readable -custom_style = Style( - background=PAGE_BG, - plot_background=PAGE_BG, - foreground=INK, - foreground_strong=INK, - foreground_subtle=INK if THEME == "dark" else INK_MUTED, - colors=IMPRINT, - title_font_size=66, - label_font_size=56, - major_label_font_size=44, - legend_font_size=44, - value_font_size=36, - tooltip_font_size=36, - stroke_width=2.5, - opacity=0.9, - opacity_hover=1.0, - transition="400ms ease-in-out", -) - -# Pre-render SVGs for all drillable levels; save PNG from root -level_svgs = {} -for level_id, level_node in hierarchy.items(): - if not level_node["children"]: - continue - - # Build breadcrumb for this level - level_trail = [] - level_cursor = level_id - while level_cursor and level_cursor in hierarchy: - level_trail.insert(0, hierarchy[level_cursor]["name"]) - level_cursor = hierarchy[level_cursor]["parent"] - level_breadcrumb = " > ".join(level_trail) - - level_children = level_node["children"] - level_names = [hierarchy[cid]["name"] for cid in level_children] - level_values = [hierarchy[cid]["value"] for cid in level_children] - level_has_children_flags = [bool(hierarchy[cid]["children"]) for cid in level_children] - - level_chart = pygal.Bar( - width=3200, - height=1800, - style=custom_style, - title="bar-drilldown · python · pygal · anyplot.ai", - x_title=f"Breadcrumb: {level_breadcrumb}", - y_title="Budget (thousands $)", - show_legend=False, - show_y_guides=False, - show_x_guides=False, - x_label_rotation=0, - print_values=True, - print_values_position="top", - value_formatter=lambda x: f"${x:,.0f}K", - human_readable=True, - spacing=30, - margin=60, - margin_top=100, - margin_bottom=120, - truncate_legend=-1, - truncate_label=-1, - ) - - level_data = [] - for i, (lname, lval, lhas_child, lcid) in enumerate( - zip(level_names, level_values, level_has_children_flags, level_children, strict=True) - ): - # Per-bar Okabe-Ito colors: visually distinguish categories and emphasize the dominant bar - lcolor = IMPRINT[i % len(IMPRINT)] - llabel = f"{lname}: ${lval:,}K — click to drill down" if lhas_child else f"{lname}: ${lval:,}K (leaf node)" - level_data.append({"value": lval, "label": llabel, "xlink": f"javascript:drillDown('{lcid}')", "color": lcolor}) - - level_chart.add("Divisions", level_data) - level_chart.x_labels = level_names - - if level_id == "root": - level_chart.render_to_png(f"plot-{THEME}.png") - - level_svg_bytes = level_chart.render() - level_svgs[level_id] = level_svg_bytes.decode("utf-8") if isinstance(level_svg_bytes, bytes) else level_svg_bytes - -# JSON-safe embedding: escape sequences that would break the HTML parser -hierarchy_json = json.dumps({k: {**v} for k, v in hierarchy.items()}).replace(" - - - - bar-drilldown · python · pygal · anyplot.ai - - - -
- -
- Interactive Drilldown: Click any bar to explore its sub-categories. - Use the Back button to navigate up the hierarchy. -
-
{level_svgs.get("root", "")}
-
- - - -""" - -with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f: - f.write(html_content) diff --git a/plots/bar-drilldown/implementations/python/seaborn.py b/plots/bar-drilldown/implementations/python/seaborn.py deleted file mode 100644 index 78bdc97dbc..0000000000 --- a/plots/bar-drilldown/implementations/python/seaborn.py +++ /dev/null @@ -1,138 +0,0 @@ -""" anyplot.ai -bar-drilldown: Column Chart with Hierarchical Drilling -Library: seaborn 0.13.2 | Python 3.13.13 -Quality: 89/100 | Created: 2026-05-20 -""" - -import os - -import matplotlib.pyplot as plt -import pandas as pd -import seaborn as sns - - -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" -AMBER = "#DDCC77" # imprint amber — accent edge (was meant as a gold highlight) - -BRAND = "#009E73" -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233"] - -sns.set_theme( - style="ticks", - rc={ - "figure.facecolor": PAGE_BG, - "axes.facecolor": PAGE_BG, - "axes.edgecolor": INK_SOFT, - "axes.labelcolor": INK, - "text.color": INK, - "xtick.color": INK_SOFT, - "ytick.color": INK_SOFT, - "grid.color": INK, - "grid.alpha": 0.10, - "legend.facecolor": ELEVATED_BG, - "legend.edgecolor": INK_SOFT, - }, -) - -# Data — regional annual sales with quarterly breakdown -regions = ["North", "South", "East", "West"] -region_totals = [12.4, 8.7, 15.2, 10.1] -quarters = ["Q1", "Q2", "Q3", "Q4"] -quarterly = { - "North": [2.8, 3.1, 3.4, 3.1], - "South": [1.9, 2.3, 2.2, 2.3], - "East": [3.5, 3.9, 4.1, 3.7], - "West": [2.4, 2.5, 2.7, 2.5], -} - -df_top = pd.DataFrame({"Region": regions, "Revenue": region_totals}) -df_sub = pd.DataFrame( - [{"Region": r, "Quarter": q, "Revenue": quarterly[r][i]} for r in regions for i, q in enumerate(quarters)] -) - -# Plot -fig, (ax1, ax2) = plt.subplots( - 2, 1, figsize=(8, 5.5), dpi=400, gridspec_kw={"height_ratios": [2, 3]}, facecolor=PAGE_BG -) - -fig.suptitle( - "Sales by Region · bar-drilldown · python · seaborn · anyplot.ai", fontsize=12, fontweight="medium", color=INK -) - -# Top panel — overview level (region totals) -sns.barplot(data=df_top, x="Region", y="Revenue", color=BRAND, edgecolor=PAGE_BG, linewidth=0.5, errorbar=None, ax=ax1) -for i, p in enumerate(ax1.patches): - if i == 2: # East is the top-performing region — gold accent edge as focal point - p.set_edgecolor(AMBER) - p.set_linewidth(1.5) - ax1.text( - p.get_x() + p.get_width() / 2.0, - p.get_height() + 0.25, - f"${p.get_height():.1f}M", - ha="center", - va="bottom", - fontsize=8, - color=INK_SOFT, - ) -ax1.set_xlabel("") -ax1.set_ylabel("Annual Revenue ($M)", fontsize=10, color=INK) -ax1.tick_params(axis="both", labelsize=8) -sns.despine(ax=ax1) -ax1.yaxis.grid(True, alpha=0.10, linewidth=0.8) -ax1.set_facecolor(PAGE_BG) - -# Breadcrumb trail between panels -ax2.text( - 0.5, - 1.06, - "All Regions ▶ Quarterly Breakdown by Region", - transform=ax2.transAxes, - ha="center", - va="bottom", - fontsize=8.5, - color=INK_SOFT, - clip_on=False, -) - -# Bottom panel — quarterly drill level (breakdown per region) -sns.barplot( - data=df_sub, - x="Region", - y="Revenue", - hue="Quarter", - palette=IMPRINT, - edgecolor=PAGE_BG, - linewidth=0.5, - errorbar=None, - ax=ax2, -) -for p in ax2.patches: - h = p.get_height() - if h > 0.1: - ax2.text( - p.get_x() + p.get_width() / 2.0, - h + 0.05, - f"${h:.1f}M", - ha="center", - va="bottom", - fontsize=6, - color=INK_SOFT, - ) -ax2.set_ylim(0, 5.0) -ax2.set_xlabel("Region", fontsize=10, color=INK) -ax2.set_ylabel("Quarterly Revenue ($M)", fontsize=10, color=INK) -ax2.tick_params(axis="both", labelsize=8) -sns.despine(ax=ax2) -ax2.yaxis.grid(True, alpha=0.10, linewidth=0.8) -ax2.set_facecolor(PAGE_BG) -sns.move_legend(ax2, loc="upper right", title="Quarter", fontsize=8, title_fontsize=8, frameon=True, framealpha=0.9) -leg = ax2.get_legend() -leg.get_frame().set_facecolor(ELEVATED_BG) -leg.get_frame().set_edgecolor(INK_SOFT) - -plt.tight_layout(rect=[0, 0, 1, 0.93], h_pad=2.0) -plt.savefig(f"plot-{THEME}.png", dpi=400, bbox_inches="tight", facecolor=PAGE_BG) diff --git a/plots/bar-drilldown/implementations/r/ggplot2.R b/plots/bar-drilldown/implementations/r/ggplot2.R deleted file mode 100644 index 1b73d5f835..0000000000 --- a/plots/bar-drilldown/implementations/r/ggplot2.R +++ /dev/null @@ -1,132 +0,0 @@ -#' anyplot.ai -#' bar-drilldown: Column Chart with Hierarchical Drilling -#' Library: ggplot2 3.5.1 | R 4.4.1 -#' Quality: 87/100 | Created: 2026-05-20 - -library(ggplot2) -library(dplyr) -library(scales) -library(ragg) - -# --- Theme tokens --- -THEME <- Sys.getenv("ANYPLOT_THEME", "light") -PAGE_BG <- if (THEME == "light") "#FAF8F1" else "#1A1A17" -ELEVATED_BG <- if (THEME == "light") "#FFFDF6" else "#242420" -INK <- if (THEME == "light") "#1A1A17" else "#F0EFE8" -INK_SOFT <- if (THEME == "light") "#4A4A44" else "#B8B7B0" -GRID_COLOR <- if (THEME == "light") "#D0CFC8" else "#2C2C29" -IMPRINT <- c("#009E73", "#C475FD", "#4467A3", "#BD8233", - "#AE3030", "#2ABCCD", "#954477") - -# --- Data: company annual budget hierarchy (department -> expense category) --- -departments <- c("Engineering", "Sales", "Marketing", "Operations") - -df <- tibble::tibble( - department = rep(departments, each = 3), - item = c( - "Infrastructure", "R&D", "Tooling", - "Headcount", "Travel", "CRM Software", - "Advertising", "Events", "Content", - "Facilities", "Equipment", "Logistics" - ), - budget_m = c( - 1.85, 1.62, 0.73, - 1.55, 0.92, 0.63, - 0.88, 0.61, 0.51, - 0.78, 0.62, 0.40 - ) -) - -# Compute department totals for strip labels and insight subtitle -dept_totals <- df %>% - group_by(department) %>% - summarize(total_m = sum(budget_m), .groups = "drop") - -top_dept <- dept_totals[which.max(dept_totals$total_m), ] -bottom_dept <- dept_totals[which.min(dept_totals$total_m), ] -ratio <- top_dept$total_m / bottom_dept$total_m - -# Strip labels embed each department's total — immediately reveals Engineering's lead -dept_strip_labels <- setNames( - sprintf("%s · $%.2fM total", dept_totals$department, dept_totals$total_m), - dept_totals$department -) - -# Insight subtitle makes the data story explicit -subtitle_text <- sprintf( - "%s leads at $%.2fM · %.1f× %s ($%.2fM) · Annual budget by department", - top_dept$department, top_dept$total_m, ratio, - bottom_dept$department, bottom_dept$total_m -) - -# Order items within each department by budget ascending (bottom-to-top after coord_flip) -df <- df %>% - group_by(department) %>% - arrange(budget_m, .by_group = TRUE) %>% - mutate(bar_emphasis = if_else(budget_m == max(budget_m), "top", "other")) %>% - ungroup() %>% - mutate( - department = factor(department, levels = departments), - unique_item = factor( - paste(department, item, sep = "|"), - levels = paste(department, item, sep = "|") - ) - ) - -dept_colors <- setNames(IMPRINT[1:4], departments) - -# --- Plot --- -p <- ggplot(df, aes(x = unique_item, y = budget_m, fill = department, alpha = bar_emphasis)) + - geom_col(width = 0.65, show.legend = FALSE) + - geom_text( - aes(label = sprintf("$%.2fM", budget_m)), - hjust = -0.1, - size = 3.3, - color = INK_SOFT - ) + - facet_wrap(~department, scales = "free", ncol = 2, - labeller = as_labeller(dept_strip_labels)) + - scale_x_discrete(labels = function(x) sub(".*\\|", "", x)) + - scale_y_continuous( - labels = function(x) sprintf("$%.1fM", x), - expand = expansion(mult = c(0, 0.48)) - ) + - scale_fill_manual(values = dept_colors) + - scale_alpha_manual(values = c("top" = 1.0, "other" = 0.72), guide = "none") + - coord_flip() + - labs( - title = "Department Budget Breakdown · bar-drilldown · r · ggplot2 · anyplot.ai", - subtitle = subtitle_text, - x = NULL, - y = "Budget (USD millions)" - ) + - theme_minimal(base_size = 8) + - theme( - plot.background = element_rect(fill = PAGE_BG, color = PAGE_BG), - panel.background = element_rect(fill = PAGE_BG, color = NA), - panel.grid.major.x = element_blank(), - panel.grid.major.y = element_line(color = GRID_COLOR, linewidth = 0.15), - panel.grid.minor = element_blank(), - axis.title.x = element_text(color = INK, size = 10), - axis.title.y = element_blank(), - axis.text = element_text(color = INK_SOFT, size = 8), - axis.ticks = element_blank(), - strip.text = element_text(color = INK, size = 10, face = "bold"), - strip.background = element_rect(fill = ELEVATED_BG, color = NA), - plot.title = element_text(color = INK, size = 11), - plot.subtitle = element_text(color = INK_SOFT, size = 8.5), - panel.spacing.x = unit(2, "lines"), - panel.spacing.y = unit(1.5, "lines"), - plot.margin = margin(15, 15, 10, 10) - ) - -# --- Save --- -ggsave( - filename = sprintf("plot-%s.png", THEME), - plot = p, - device = ragg::agg_png, - width = 8, - height = 4.5, - units = "in", - dpi = 400 -) diff --git a/plots/bar-drilldown/metadata/python/altair.yaml b/plots/bar-drilldown/metadata/python/altair.yaml deleted file mode 100644 index 28905ea40e..0000000000 --- a/plots/bar-drilldown/metadata/python/altair.yaml +++ /dev/null @@ -1,248 +0,0 @@ -library: altair -language: python -specification_id: bar-drilldown -created: '2026-01-16T17:16:59Z' -updated: '2026-05-20T11:46:11Z' -generated_by: claude-sonnet -workflow_run: 26159133310 -issue: 3783 -language_version: 3.13.13 -library_version: 6.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/altair/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/altair/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/altair/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/altair/plot-dark.html -quality_score: 90 -review: - strengths: - - 'Correct Okabe-Ito palette (first series #009E73) with proper order through all - 4 regions; both light and dark themes fully legible' - - 'Complete drilldown functionality in HTML: breadcrumbs, back navigation, cursor - pointer, value labels, and smooth transitions with Vega-Lite; parent-child sums - always match' - - Comprehensive 3-level hierarchy (region → country → city) with 4 root categories, - each with 3 children and some with grandchildren — fully exercises the spec - - 'Clean idiomatic Altair: layer composition (bars + text), configure_axis/configure_view - theme tokens, alt.Title with subtitle, all explicitly set font sizes' - - Sorted-by-value layout makes the bar chart immediately communicative; subtitle - honestly directs users to the HTML version - weaknesses: - - Design sophistication is solid but not exceptional — the static PNG is a plain - 4-bar chart; no emphasis (no highlighted bar, no annotation on the largest region, - no visual storytelling beyond sort order) - - 'DE-02 could go further: spines are present on left/bottom but a top-right guideline - removal or lighter axis domain line could polish the look' - - 'LM-02: the HTML chart re-implements Vega-Lite from scratch in raw JSON rather - than using Altair Python API to generate the spec — loses some library-distinctive - Altair identity in the interactive path' - - Transition config (config.transition.duration=300) in Vega-Lite HTML may not actually - animate between re-renders since vegaEmbed replaces the chart rather than updating - it — consider using view.change() + run() for smooth transitions - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct, not pure white - Chrome: Title "bar-drilldown · python · altair · anyplot.ai" in bold dark ink, clearly readable. Subtitle in softer INK_SOFT tone. X-axis label "Region" and Y-axis label "Sales (millions USD)" in dark ink, well-proportioned. Tick labels in INK_SOFT at 10px — readable. Grid lines are subtle horizontal lines at low opacity. - Data: 4 bars — Asia Pacific (#009E73 brand green), Americas (#D55E00 vermillion), Europe (#0072B2 blue), Africa (#CC79A7 reddish purple). Bars sorted descending by value. Bold value labels ($520, $450, $380, $180) appear above each bar in dark ink. Rounded top corners on each bar. - Legibility verdict: PASS — all text clearly readable against warm off-white background, no light-on-light issues. - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct, not pure black - Chrome: Title in light #F0EFE8-range text, clearly visible. Subtitle in lighter INK_SOFT-equivalent tone. Axis labels in light text, readable. Tick labels in INK_SOFT equivalent, readable. Grid lines are subtle at low opacity against dark background. - Data: Identical bar colors to light render — Asia Pacific (#009E73), Americas (#D55E00), Europe (#0072B2), Africa (#CC79A7). Value labels appear in light-colored text clearly distinguishable from dark background. No dark-on-dark failures observed. - Legibility verdict: PASS — all text elements are light-colored and readable against near-black background; brand green #009E73 remains clearly visible; no dark-on-dark issues detected. - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All font sizes explicitly set (title 16, subtitle 12, axis labels - 12, tick labels 10, value labels 12); proportions well-balanced in both - themes - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text or data elements in either render - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Bars well-sized, value labels clearly visible, rounded corners add - definition - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette, good luminance contrast between all bars and background - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Bars fill canvas well, balanced margins, good whitespace around plot - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Y-axis 'Sales (millions USD)' with units, X-axis 'Region' descriptive - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73, Okabe-Ito order, #FAF8F1 light bg, #1A1A17 - dark bg, all chrome theme-adaptive' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Above well-configured defaults: rounded bar corners, sorted layout, - layered chart with value labels; not exceptional' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Subtle 10% opacity grid, no view border (stroke=None), proper INK_SOFT - axis chrome — good refinement - - id: DE-03 - name: Data Storytelling - score: 3 - max: 6 - passed: true - comment: Sorted descending makes comparison immediate; informative subtitle - directs to HTML; no focal point emphasis - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct bar chart with full hierarchical drilldown in HTML - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Drilldown, breadcrumbs, back navigation, cursor pointer, value labels, - color mapping, transition config all implemented - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X=region names, Y=sales values; 3-level hierarchy fully modeled - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title is 'bar-drilldown · python · altair · anyplot.ai'; no legend - needed (per-bar colors keyed by axis labels) - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 3 hierarchy levels, 4 root regions, 3 children each, some with grandchildren; - children sums match parent values - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Geographic sales data with named real regions, countries, and cities - — neutral business scenario - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Sales in millions USD with plausible regional proportions; Asia Pacific - > Americas > Europe > Africa is reasonable - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Flat structure: imports → tokens → data → chart → save; HTML generation - is inline but necessary' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Fully deterministic hardcoded data; no random state needed - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: json, os, altair, pandas — all used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean Altair layering, well-organized HTML generation; appropriate - complexity for the interactive spec - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html correctly - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: Layer composition with +, proper encoding types (:N, :Q), alt.Title - with subtitle, configure_axis/configure_view — expert Altair usage - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Altair layer composition and HTML export used; but interactive HTML - rebuilds Vega-Lite spec in raw JSON rather than using Altair Python API, - losing some library identity - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - layer-composition - - hover-tooltips - - html-export - patterns: [] - dataprep: [] - styling: [] diff --git a/plots/bar-drilldown/metadata/python/bokeh.yaml b/plots/bar-drilldown/metadata/python/bokeh.yaml deleted file mode 100644 index eab442239a..0000000000 --- a/plots/bar-drilldown/metadata/python/bokeh.yaml +++ /dev/null @@ -1,253 +0,0 @@ -library: bokeh -language: python -specification_id: bar-drilldown -created: '2026-01-16T17:16:44Z' -updated: '2026-05-20T11:33:04Z' -generated_by: claude-sonnet -workflow_run: 26159036364 -issue: 3783 -language_version: 3.13.13 -library_version: 3.9.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/bokeh/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/bokeh/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/bokeh/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/bokeh/plot-dark.html -quality_score: 90 -review: - strengths: - - Internally consistent hierarchical data — every parent equals sum of children - exactly - - Correct Okabe-Ito palette in canonical order with clean theme-adaptive chrome - in both renders - - Real client-side drilldown via CustomJS + TapTool — no server required; fully - functional in the saved HTML - - All font sizes explicitly set to Bokeh style-guide defaults (50pt/42pt/34pt) - - ColumnDataSource, Bokeh widget integration, and layout composition are all idiomatic - weaknesses: - - No transition animation when drilling between hierarchy levels (spec calls for - animated transitions) - - No cursor-change or click-affordance indicator on bars (spec calls for visual - indicator that columns are clickable) - - Full box outline around plot area (outline_line_color=INK_SOFT creates a rectangle - frame); style guide prefers L-shaped or frameless - - Back button uses Bokeh primary button_type (blue) which is not theme-adaptive; - could use button_type=light or custom HTML styling - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct light theme surface - Chrome: Title "bar-drilldown · python · bokeh · anyplot.ai" in bold dark ink, clearly readable. X-axis label "Region" and y-axis label "Sales ($ millions)" both readable in dark ink. Tick labels in softer dark ink (INK_SOFT). Navigation row at top: "All" breadcrumb badge (dark text on elevated off-white) and disabled "Back" button. - Data: Three bars — North America (450, #009E73 brand green), Europe (380, #D55E00 vermillion), Asia Pacific (320, #0072B2 blue). Okabe-Ito positions 1-3 in canonical order. Bold value labels (450, 380, 320) above bars in dark ink. - Legibility verdict: PASS - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct dark theme surface - Chrome: Title, axis labels, tick labels, and value labels all render in light ink (#F0EFE8 / #B8B7B0) — fully readable against the dark background. No dark-on-dark failures detected. Navigation row breadcrumb shows "All" in light text on elevated dark surface (#242420). - Data: Identical bar colors to light render — #009E73, #D55E00, #0072B2 — confirming data colors are theme-invariant. Only chrome (background, text, grid) flips between themes. - Legibility verdict: PASS - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All font sizes explicitly set (50pt title, 42pt axis labels, 34pt - ticks/value labels). Well-proportioned in both themes. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No collisions between value labels, tick labels, axis labels, or - bars in either render. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Bars are large and prominently visible; Okabe-Ito colors provide - strong contrast. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: CVD-safe Okabe-Ito palette; adequate luminance contrast between all - series. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Chart fills canvas well; navigation row cleanly integrated; no content - cut off. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Region (x) and Sales ($ millions) (y) with units present. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73; Okabe-Ito positions 1-3; backgrounds #FAF8F1/#1A1A17; - chrome fully theme-adaptive.' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Above well-configured default. Correct palette, clean bar edges (line_color=PAGE_BG), - subtle fill_alpha=0.92. Box outline frame and default-blue button type are - minor rough edges. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Y-grid only at alpha=0.10; x-grid disabled; clean value labels. Full - box outline_line_color reduces refinement vs L-shaped frame. - - id: DE-03 - name: Data Storytelling - score: 3 - max: 6 - passed: true - comment: Drilldown hierarchy is the storytelling mechanism; breadcrumb+back-button - makes structure legible. Static root view shows three differentiated bars. - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Column/bar chart with working hierarchical drilldown via TapTool - + CustomJS; drill-up via back button. - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: 'Click-to-drill, breadcrumb trail, back button, value labels all - present. Missing: animated transitions and cursor-change clickability indicator.' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Categories on X, sales values on Y; y-range correctly scales per - drill level. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title is exactly 'bar-drilldown · python · bokeh · anyplot.ai'; no - legend needed. - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Complete 3-level hierarchy (Region -> Country -> City); all paths - fully populated; leaf nodes at level 2 (Singapore) handled correctly. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Geographic sales data is canonical drilldown use case; real country/city - names; neutral business scenario. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Every parent value equals exact sum of its children. Proportions - reflect plausible regional sales distributions. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Linear Imports -> Data -> Plot -> Callbacks -> Save; no functions - or classes. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Fully deterministic hardcoded data; no random elements. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Every import is used; no stray imports. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: CustomJS implements real browser-side interactivity; sys.path workaround - is documented necessity. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.html (interactive) and plot-{THEME}.png (screenshot); - current Bokeh API. - library_mastery: - score: 9 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: ColumnDataSource, TapTool, CustomJS, LabelSet, Bokeh widget layouts, - js_on_click, p.select(TapTool).callback — all canonical Bokeh patterns used - correctly. - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: 'Client-side-only drilldown via CustomJS + dynamic ColumnDataSource - mutation is quintessentially Bokeh. Minor: JS logic is fairly generic dictionary - traversal rather than streaming/patching patterns.' - verdict: APPROVED -impl_tags: - dependencies: - - selenium - techniques: - - html-export - - annotations - patterns: - - columndatasource - - data-generation - dataprep: [] - styling: - - edge-highlighting - - alpha-blending diff --git a/plots/bar-drilldown/metadata/python/letsplot.yaml b/plots/bar-drilldown/metadata/python/letsplot.yaml deleted file mode 100644 index 1b0f82dda3..0000000000 --- a/plots/bar-drilldown/metadata/python/letsplot.yaml +++ /dev/null @@ -1,250 +0,0 @@ -library: letsplot -language: python -specification_id: bar-drilldown -created: '2026-01-16T17:16:28Z' -updated: '2026-05-20T11:58:58Z' -generated_by: claude-sonnet -workflow_run: 26159524768 -issue: 3783 -language_version: 3.13.13 -library_version: 4.9.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/letsplot/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/letsplot/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/letsplot/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/letsplot/plot-dark.html -quality_score: 90 -review: - strengths: - - 'Perfect spec compliance: all drilldown features (click, breadcrumb, back button, - animation, tooltips, cursor change, value labels) implemented and working' - - Internally consistent hierarchical data where quarterly values sum exactly to - region totals - - 'Both themes fully correct: backgrounds, text, grid, and data colors all theme-adaptive - in both renders' - - 'Okabe-Ito palette applied in canonical order with first series correctly #009E73' - - Animated transitions with cubic easing (480ms) provide smooth UX as required by - spec - - Clean KISS code structure with no functions/classes; deterministic hardcoded data - - Mean reference line and star-highest annotation create clear visual hierarchy - and storytelling - weaknesses: - - 'VQ-01 MINOR: annotation size=7 is slightly smaller than value labels at size=8; - increase to size=8 for consistency and better mobile readability' - - 'LM-02 PARTIAL: interactive HTML uses raw Canvas/JavaScript rather than letsplot - native HTML export; a letsplot-generated HTML with injected interactivity would - be more library-native' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white, matches #FAF8F1 — not pure white - Chrome: Title "bar-drilldown · python · letsplot · anyplot.ai" bold and centered in dark ink; subtitle in muted dark; axis labels "Region" and "Sales ($)" in dark ink; tick labels in INK_SOFT dark tone — all clearly readable - Data: Four bars in Okabe-Ito canonical order: North (#009E73), South (#D55E00), East (#0072B2), West (#CC79A7); value labels in bold dark text above bars; dashed mean line in muted tone; "★ highest" annotation in blue above East Region - Legibility verdict: PASS - - Dark render (plot-dark.png): - Background: Warm near-black, matches #1A1A17 — not pure black - Chrome: Title and subtitle rendered in light cream (#F0EFE8); axis labels and tick labels in INK_SOFT light tone (#B8B7B0); all text clearly readable against dark surface; no dark-on-dark failures observed - Data: Identical Okabe-Ito colors as light render — North (#009E73), South (#D55E00), East (#0072B2), West (#CC79A7); value labels in light text above bars; "★ highest" annotation in Okabe-Ito blue still clearly visible - Legibility verdict: PASS - criteria_checklist: - visual_quality: - score: 29 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: 'All font sizes explicitly set; all text readable in both themes. - Minor: annotation size=7 is slightly smaller than value labels at size=8.' - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text or elements in either render. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Bars well-proportioned and clearly visible; dashed mean line appropriately - subtle. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette is CVD-safe; good luminance contrast between all - bars and backgrounds. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Plot fills canvas well; generous margins; balanced whitespace. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Sales ($) includes currency unit; Region is descriptive; title follows - mandated format. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73; Okabe-Ito canonical order; backgrounds #FAF8F1/#1A1A17; - all chrome theme-adaptive.' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Above configured defaults: Okabe-Ito explicitly applied, mean reference - line, peak annotation, bar edges blending into background. Not publication-ready.' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: 'Good refinement: X-axis major grid removed, minor grid removed, - Y-only grid subtle, generous margins, bar edges styled to match background.' - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: 'Clear story: mean line provides comparison context, star annotation - highlights East Region peak, subtitle states East leads at $528K.' - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct bar chart in static PNG; full interactive Canvas-based bar - chart with click-to-drill in HTML. - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: 'All spec features present: click-to-drill, breadcrumb, back button, - animated transitions, consistent color mapping, cursor change, value labels.' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: 'X: category names; Y: sales values; hierarchical structure correctly - mapped.' - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title exactly 'bar-drilldown · python · letsplot · anyplot.ai'; no - legend needed; show_legend=False appropriate. - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Shows root level (4 regions) and drill-down level (quarterly breakdown - per region); variation in values across both levels. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Regional sales quarterly breakdown is a standard, neutral business - scenario; no controversial content. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Quarterly values sum precisely to region totals (North 98+125+142+120=$485K - etc.); internally consistent and plausible. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Flat: imports → tokens → data → letsplot plot → ggsave → HTML generation. - No functions or classes.' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: All data hardcoded; fully deterministic. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports used; json and os used in HTML template; no dead imports. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: HTML is verbose but necessarily so for full Canvas-based interactive - chart; no over-engineering; no fake UI in letsplot plot. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png via ggsave and plot-{THEME}.html via file - write; current API throughout. - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: 'Expert use of letsplot grammar: ggplot + geom_bar + geom_hline + - geom_text, scale_fill_manual, theme with element_* functions, labs, ggsize, - ggsave scale=4.' - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Grammar-of-graphics composition used effectively for PNG, but interactive - HTML is custom Canvas rather than letsplot native HTML export. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - annotations - - layer-composition - - hover-tooltips - - html-export - patterns: [] - dataprep: [] - styling: - - edge-highlighting - - grid-styling diff --git a/plots/bar-drilldown/metadata/python/matplotlib.yaml b/plots/bar-drilldown/metadata/python/matplotlib.yaml deleted file mode 100644 index 4c8364d9d6..0000000000 --- a/plots/bar-drilldown/metadata/python/matplotlib.yaml +++ /dev/null @@ -1,253 +0,0 @@ -library: matplotlib -language: python -specification_id: bar-drilldown -created: '2026-05-20T11:16:18Z' -updated: '2026-05-20T11:33:14Z' -generated_by: claude-sonnet -workflow_run: 26158739442 -issue: 3783 -language_version: 3.13.13 -library_version: 3.10.9 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/matplotlib/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/matplotlib/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 86 -review: - strengths: - - 'Compelling single-color spotlight technique: one green bar against muted peers - instantly communicates the active drill level without any legend.' - - ConnectionPatch used idiomatically to bridge panels — a genuinely distinctive - matplotlib feature that makes the drill relationship tangible. - - 'Perfect data integrity: subcategory values sum exactly to parent total ($1,820K - + $1,640K + $890K + $470K = $4,820K).' - - 'Full theme adaptation: both light and dark renders are clean, readable, and correct - with no dark-on-dark or wrong-background issues.' - - KISS code structure with zero unnecessary abstraction; linear flow and deterministic - hardcoded data. - weaknesses: - - Only one hierarchy level shown statically; spec calls for 2-3 levels — a third - panel or small-multiple row showing a second category's breakdown would better - demonstrate the drilldown concept. - - Typography hierarchy between the three title levels (suptitle / subplot-title-1 - / subplot-title-2) is flat; suptitle could be bolder/larger to create a clearer - visual tier. - - ConnectionPatch lines are very faint (alpha=0.35, lw=0.9) — increasing their presence - would reinforce the drill relationship and better showcase this distinctive matplotlib - feature. - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct theme surface, not pure white. - Chrome: suptitle "bar-drilldown · python · matplotlib · anyplot.ai" in dark INK; subplot titles "All Categories · 2024" and "All Categories › Electronics" in dark INK; Y-axis labels "Revenue ($K)" in dark INK; tick labels and value labels in INK_SOFT (#4A4A44). All text is clearly readable against the light background. - Data: Electronics bar in #009E73 (brand green); Apparel/Home & Garden/Sports bars in muted gray #C0BEB8. All four drilldown bars (Phones, Laptops, Tablets, Accessories) in #009E73. Dollar value labels above each bar. Dashed green ConnectionPatch lines (alpha=0.35) connect the Electronics bar top to the drilldown panel. Small italic "◄ Back to All Categories" text in INK_SOFT. - Legibility verdict: PASS — all text elements readable; no light-on-light issues. - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct dark theme surface, not pure black. - Chrome: suptitle, subplot titles, axis labels, tick labels, and value labels all rendered in light-colored text (#F0EFE8 / #B8B7B0). Grid lines are subtle against the dark surface. - Data: Data colors are identical to light render — Electronics and drilldown bars remain #009E73; inactive bars use dark muted neutral #4E4E48. ConnectionPatch lines retain the same dashed green style. - Legibility verdict: PASS — all text clearly readable against dark background; no dark-on-dark failures observed; brand green #009E73 clearly visible. - criteria_checklist: - visual_quality: - score: 29 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: All font sizes explicitly set; both themes readable; three-level - header stack adds minor complexity but does not impede legibility - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text or data elements in either render - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Bars well-proportioned; value labels clearly positioned; connection - lines appropriately subtle - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Brand green vs muted neutral provides strong contrast; CVD-safe Okabe-Ito - palette - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Two panels fill 2400x2400 canvas well; generous hspace=0.48 spacing; - nothing cut off - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Revenue ($K) on both Y-axes includes units - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Primary series #009E73; backgrounds #FAF8F1/#1A1A17; all chrome - theme-correct in both renders' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Intentional color spotlight and two-panel narrative above generic - defaults; not yet FiveThirtyEight-level - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Top/right spines removed; y-grid at alpha=0.10; bar edges use PAGE_BG; - connection lines styled - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Green spotlight bar draws eye; connection lines make drill path tangible; - breadcrumb contextualises view - spec_compliance: - score: 13 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 4 - max: 5 - passed: true - comment: Correct bar chart type; static two-panel is best achievable matplotlib - approximation of interactive drilldown - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Breadcrumb, back affordance, value labels, color mapping all present; - animation N/A; only one drill path shown - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Categories/subcategories on X; revenue on Y; all data visible - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: suptitle is 'bar-drilldown · python · matplotlib · anyplot.ai'; no - legend needed - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: Shows overview highlighting, drilldown breakdown, breadcrumb, back - affordance; one drill path only - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: 2024 global retail revenue; Electronics/Apparel/Home/Sports and subcategories - are realistic neutral domain - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Subcategory values sum exactly to parent ($1820K+$1640K+$890K+$470K=$4820K); - proportions realistic - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear: imports -> data -> layout -> plots -> styling -> save; no - functions or classes' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: All data deterministic (hardcoded); no random seed needed - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: os, sys, matplotlib.pyplot, numpy, GridSpec, ConnectionPatch — all - actively used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean Pythonic code; no fake UI elements; static approximation comment - is a design note not AR-08 trigger - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png; uses current matplotlib 3.10 API - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Axes methods throughout; GridSpec for multi-panel; fig.add_artist - for cross-figure elements - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: ConnectionPatch for inter-axes connectors is genuinely matplotlib-specific; - combined with GridSpec and fig.add_artist - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - subplots - - annotations - - patches - - manual-ticks - patterns: - - explicit-figure - - iteration-over-groups - dataprep: [] - styling: - - alpha-blending - - edge-highlighting - - grid-styling diff --git a/plots/bar-drilldown/metadata/python/plotly.yaml b/plots/bar-drilldown/metadata/python/plotly.yaml deleted file mode 100644 index 2aa0da9791..0000000000 --- a/plots/bar-drilldown/metadata/python/plotly.yaml +++ /dev/null @@ -1,260 +0,0 @@ -library: plotly -language: python -specification_id: bar-drilldown -created: '2026-01-16T17:18:44Z' -updated: '2026-05-23T21:58:20Z' -generated_by: claude-sonnet -workflow_run: 26344336407 -issue: 3783 -language_version: 3.13.13 -library_version: 6.7.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/plotly/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/plotly/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/plotly/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/plotly/plot-dark.html -quality_score: 93 -review: - strengths: - - 'Full spec compliance: click-to-drilldown via Plotly frames, dropdown navigation, - animated transitions, and JavaScript click handler all implemented correctly' - - 'Excellent palette use: Q1=#009E73 (brand green) correctly first, all four anyplot - palette positions used in canonical order' - - 'Focal-point storytelling: Q4 rendered at full opacity (1.0) with Q1-Q3 muted - (0.7) + Holiday Peak annotation creates clear narrative hierarchy' - - 'Theme tokens applied throughout: PAGE_BG, ELEVATED_BG, INK, INK_SOFT, GRID all - thread correctly into both light and dark renders with zero dark-on-dark failures' - - 'Strong library mastery: uses go.Frame animation system, updatemenus dropdown, - and post_script JavaScript plotly_click handler — all distinctively Plotly features' - - 'Code quality: clean KISS structure, dict unpacking for shared properties (_chrome, - _edge, _footer), list comprehensions for traces and buttons' - - 'Cross-level color identity: monthly bars inherit parent quarter color, preserving - categorical identity across drill levels as specified' - weaknesses: - - Footer instruction text (size=10, renders to ~40 source px) is readable at desktop - but will be very small at mobile width (~5 display px at 400px viewport) — consider - increasing to size=11 or size=12 - - 'Design excellence defaults are low: the focal-point opacity technique and holiday - peak annotation are good, but more could be done (e.g., subtle bar labels styled - differently from the data labels, refined axis spine handling)' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct, not pure white. - Chrome: Title "2024 Retail Sales · bar-drilldown · python · plotly · anyplot.ai" at top center in dark ink, clearly readable. Axis labels "Quarter" (x) and "Revenue ($K)" (y) in dark ink at appropriate size. Tick labels in muted dark ink (INK_SOFT). Dropdown widget top-left with "▶ 2024 Annual — All Quarters" in matching theme colors. - Data: Four bars using Imprint palette — Q1 green (#009E73), Q2 purple (#9418DB), Q3 red (#B71D27), Q4 sky blue (#16B8F3). Q1–Q3 rendered at 0.7 opacity, Q4 at full opacity emphasizing holiday peak. Value labels ($1.24M, $1.68M, $2.14M, $2.38M) above each bar. "★ Holiday Peak" annotation in Q4 blue color at upper right. - Footer: "Click a quarter bar to drill into monthly detail · use the dropdown to navigate back" in muted ink — readable but small (size=10). - Legibility verdict: PASS — all text readable, no light-on-light issues. - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct, not pure black. - Chrome: Title in light cream (#F0EFE8), axis labels and tick labels in appropriate light tones (INK/INK_SOFT tokens correctly applied). Dropdown widget uses ELEVATED_BG (#242420) background with INK_SOFT border — renders correctly in dark theme. Grid lines subtle and visible. - Data: Colors are identical to light render — Q1 #009E73, Q2 #9418DB, Q3 #B71D27, Q4 #16B8F3. Q4 opacity emphasis maintained. All value labels and "★ Holiday Peak" annotation visible in light ink. - Footer: Same instructional text, readable in light muted tone against dark background. - Legibility verdict: PASS — no dark-on-dark failures; all chrome elements correctly flip to light tones on dark surface. - criteria_checklist: - visual_quality: - score: 29 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: Title (16px), axis labels (12px), tick labels (10px) all readable - in both themes. Footer text (size=10 → ~40 source px) is small for mobile - scale. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping elements. Value labels clear of bars, annotation clear - of data area. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: All 4 bars prominently visible in both renders. Opacity differentiation - (0.7 vs 1.0) enhances rather than obscures. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Imprint palette is colorblind-safe. 4 colors with sufficient perceptual - contrast. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Canvas gate passed (3200×1800). Good proportions, no clipping. Dropdown - fits cleanly in upper left. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: 'X: ''Quarter'', Y: ''Revenue ($K)'' — descriptive with units. Monthly - frames show path in x-axis title.' - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Q1=#009E73 (brand green, first), Q2-Q4 follow canonical anyplot - order. Backgrounds #FAF8F1/#1A1A17 correct. Data colors identical across - themes.' - design_excellence: - score: 14 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 6 - max: 8 - passed: true - comment: Intentional opacity hierarchy (Q4 focal point), holiday peak annotation, - theme-adaptive dropdown styling. Professional finish above median. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Subtle y-axis grid only, white bar edges for definition, clean minimal - chrome. Plotly defaults already remove top/right spines. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Q4 emphasis + Holiday Peak annotation creates narrative. Monotonic - sales growth clearly visible. Footer guides user interaction. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Column/bar chart with hierarchical drilldown via Plotly frames. Correct - implementation. - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Click-to-drill (JS handler), dropdown back-navigation, animated transitions - (400ms frames), consistent cross-level color, value labels, footer indicator. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X=quarters/months, Y=revenue. Two-level hierarchy Q→M correctly structured. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title: ''2024 Retail Sales · bar-drilldown · python · plotly · anyplot.ai'' - — correct format with optional descriptive prefix. No legend needed (x-axis - labels serve this role).' - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Full two-level hierarchy (4 quarters × 3 months), multiple navigation - methods, drilldown mechanics fully demonstrated. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: 2024 Retail Sales with realistic seasonal pattern (Q4 holiday peak, - gradual growth). Neutral business domain. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Monthly revenues $390K–$940K, quarterly $1.24M–$2.38M — realistic - mid-size retail company scale. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: No classes or functions. Sequential top-level code with clear data-then-layout-then-output - flow. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: All data hardcoded. Deterministic output. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: importlib, os, sys all used. Plotly loaded via importlib.import_module - to avoid path pollution. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Dict unpacking for shared properties (_chrome, _edge, _footer), list - comprehensions for traces and buttons. Clean and maintainable. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png (width=800, height=450, scale=4) and plot-{THEME}.html. - Current Plotly API. - library_mastery: - score: 10 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: go.Figure, go.Bar, go.Frame, update_layout, updatemenus — all idiomatic - Plotly graph_objects patterns. - - id: LM-02 - name: Distinctive Features - score: 5 - max: 5 - passed: true - comment: Plotly frame animation system, updatemenus dropdown, post_script - JS injection, plotly_click event handler, Plotly.animate() — all distinctively - Plotly capabilities unavailable in static libraries. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - annotations - - hover-tooltips - - html-export - patterns: - - iteration-over-groups - dataprep: [] - styling: - - alpha-blending - - edge-highlighting diff --git a/plots/bar-drilldown/metadata/python/plotnine.yaml b/plots/bar-drilldown/metadata/python/plotnine.yaml deleted file mode 100644 index 336ca3006d..0000000000 --- a/plots/bar-drilldown/metadata/python/plotnine.yaml +++ /dev/null @@ -1,239 +0,0 @@ -library: plotnine -language: python -specification_id: bar-drilldown -created: '2026-05-20T11:29:52Z' -updated: '2026-05-20T11:44:58Z' -generated_by: claude-sonnet -workflow_run: 26159231617 -issue: 3783 -language_version: 3.13.13 -library_version: 0.15.4 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/plotnine/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/plotnine/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 82 -review: - strengths: - - Both light and dark themes render correctly with proper chrome token usage — no - dark-on-dark or light-on-light legibility failures - - Enriched strip labels with computed category totals add meaningful context and - enable cross-panel comparison without annotation clutter - - Sorted bars within each panel and consistent Okabe-Ito colors make within-category - and cross-category comparisons natural - - Fully deterministic data with realistic retail domain context - weaknesses: - - Value label font size (9pt) is slightly small; at mobile viewport scale (~400px) - these may become hard to read — increase geom_text size from 9 to 10 - - DE-03 lacks a focal point or narrative emphasis; consider using reduced alpha - on non-top bars (e.g., alpha=0.65 for bars 2–4) to highlight category leaders - - DE-01 stays at well-configured default level with no typographic weight variation - or subtle size differentiation to create visual hierarchy within panels - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct, not pure white - Chrome: Title "bar-drilldown · python · plotnine · anyplot.ai" in dark INK (#1A1A17) — readable; Y-axis label "Revenue ($ millions)" in INK; tick labels and value labels in INK_SOFT (#4A4A44) — all readable against light background; strip text in bold INK — readable - Data: Electronics = #009E73 (brand green), Apparel = #D55E00 (orange), Groceries = #0072B2 (blue), Sports = #CC79A7 (pink-purple); Okabe-Ito positions 1–4 in order; first series correctly #009E73 - Legibility verdict: PASS - - Dark render (plot-dark.png): - Background: Near-black #1A1A17 — correct, not pure black - Chrome: Title in light #F0EFE8 — readable; axis labels in #F0EFE8 — readable; tick labels and value labels in #B8B7B0 — readable; strip backgrounds use #242420 (ELEVATED_BG) with INK text — readable; no dark-on-dark failures observed - Data: All four Okabe-Ito data colors identical to light render — Electronics = #009E73, Apparel = #D55E00, Groceries = #0072B2, Sports = #CC79A7; brand green clearly visible on dark surface - Legibility verdict: PASS - criteria_checklist: - visual_quality: - score: 29 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: All font sizes explicitly set; readable in both themes; minor deduction - for value labels at 9pt potentially small at mobile width - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text or data elements across all four panels - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Bars well-sized; value labels readable above bars - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette, CVD-safe, good luminance contrast in both themes - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Square 2400x2400px canvas well-utilized by 2x2 facet grid; no clipping - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Y-axis Revenue ($ millions) with units; title in correct format - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73; Okabe-Ito positions 1-4 in order; correct - light/dark backgrounds; both themes pass' - design_excellence: - score: 11 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: true - comment: Well-configured above defaults; strip labels enriched with totals; - not publication-quality - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Y-only grid at alpha=0.10; minimal theme; custom strip background; - clean whitespace - - id: DE-03 - name: Data Storytelling - score: 3 - max: 6 - passed: true - comment: Sorted bars; strip totals for cross-panel comparison; color identity; - no focal point or narrative emphasis - spec_compliance: - score: 11 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 3 - max: 5 - passed: true - comment: Correct bar/column chart type with hierarchical data; faceted grid - is correct static alternative; drill mechanism absent - - id: SC-02 - name: Required Features - score: 2 - max: 4 - passed: true - comment: Value labels and color mapping present; interactive features (breadcrumbs, - back button, animations, click indicators) not implementable statically - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Subcategories on X-axis, revenue on Y-axis, categories as facets - — all correctly mapped - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title matches required format; no legend correct as color identity - carried by panel labels - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: 4 categories x 4 subcategories with meaningful variation; minor deduction - for not illustrating parent-level aggregates alongside drill level - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Retail revenue by product category with realistic subcategories; - neutral business context - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Revenue $9M-$45M per subcategory, totals $57M-$120M; plausible for - mid-size retailer - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Clean imports → data → plot → save; no functions or classes - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Fully deterministic data - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imported names are used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Efficient groupby for strip labels; categorical ordering via pd.Categorical; - no over-engineering - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: plot.save(f'plot-{THEME}.png', dpi=400, width=6, height=6, units='in') - correct - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Proper ggplot grammar with geom_col, geom_text, facet_wrap, scale_fill_manual; - categorical ordering with pd.Categorical - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: facet_wrap with computed strip labels (totals embedded) is plotnine-distinctive; - scales='free' for per-panel Y-axis - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - faceting - - annotations - patterns: - - groupby-aggregation - dataprep: [] - styling: - - grid-styling diff --git a/plots/bar-drilldown/metadata/python/pygal.yaml b/plots/bar-drilldown/metadata/python/pygal.yaml deleted file mode 100644 index 71026ec6ef..0000000000 --- a/plots/bar-drilldown/metadata/python/pygal.yaml +++ /dev/null @@ -1,263 +0,0 @@ -library: pygal -language: python -specification_id: bar-drilldown -created: '2026-01-16T17:19:16Z' -updated: '2026-05-20T11:59:57Z' -generated_by: claude-sonnet -workflow_run: 26159329414 -issue: 3783 -language_version: 3.13.13 -library_version: 3.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/pygal/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/pygal/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/pygal/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/pygal/plot-dark.html -quality_score: 88 -review: - strengths: - - 'Perfect spec compliance: all drilldown features implemented (breadcrumb, back - button, clickable bars, value labels, consistent color mapping, animated transitions)' - - 'Excellent data quality: corporate budget hierarchy with three levels (Company - → Divisions → Teams) and values that sum correctly across levels' - - 'Strong palette compliance: Okabe-Ito per-bar coloring with #009E73 first, correct - dark/light theme adaptation with INK fix for dark value labels' - - 'Idiomatic pygal usage: leverages xlink per-bar clickable links, label tooltips, - render() for SVG embedding, print_values with value_formatter' - - 'Clean HTML+JS drilldown: event delegation on chart-container correctly intercepts - xlink href clicks, breadcrumb nav bar and back button work properly' - - 'Full code quality: deterministic data, all imports used, KISS flat structure, - saves plot-{THEME}.png and plot-{THEME}.html correctly' - weaknesses: - - 'Design Excellence below ceiling: DE-01=4 (well-configured default, not FiveThirtyEight-level - polish) — consider bar border highlights, subtle shadow, or typography weight - variation to push towards 6' - - 'Visual Refinement at 4/6: no grid lines is a good call, but spacing/margins could - be tuned further — e.g. centering the three bars across the full canvas width - rather than pygal''s left-leaning default layout' - - 'Data Storytelling at 3/6: value differences are visible via bar height but no - focal-point emphasis — e.g. the dominant bar (Technology) could use a stronger - accent or annotation to anchor the story' - - 'Layout efficiency: with only 3 bars at 3200px width, the chart area can feel - slightly left-heavy due to pygal''s default bar-width algorithm; tighter spacing - or wider bars would improve canvas utilization' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct theme surface, not pure white. - Chrome: Title "bar-drilldown · python · pygal · anyplot.ai" in dark text at top (~50% canvas width). Y-axis label "Budget (thousands $)" in dark text, rotated. X-axis label "Breadcrumb: Company" in dark text below bars. Y-axis tick labels ($0K–$4,000K) and x-axis category labels (Technology, Sales, Operations) all in dark text. All chrome clearly readable against the light background. No text legibility issues. - Data: Three vertical bars — Technology (#009E73 brand green, $4,200K), Sales (#D55E00 vermillion, $3,100K), Operations (#0072B2 blue, $2,400K). Okabe-Ito positions 1–3 in order. Value labels printed above each bar in small dark text, readable. No grid lines (clean minimal). Bars well-spaced across most of canvas width. - Legibility verdict: PASS - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct dark surface, not pure black. - Chrome: Title, axis labels, tick labels, and category labels all rendered in light/cream text (#F0EFE8 / INK tokens), clearly readable against the dark background. No dark-on-dark failures detected. The code fix (foreground_subtle=INK for dark theme) ensures value labels above bars are also light-colored. Y-axis label readable. - Data: Bar colors identical to light render — Technology (#009E73), Sales (#D55E00), Operations (#0072B2). Data colors are theme-invariant as required. Value labels ($4,200K, $3,100K, $2,400K) visible above bars in light text. - Legibility verdict: PASS - criteria_checklist: - visual_quality: - score: 28 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: 'All font sizes explicitly set (title=66, label=56, major_label=44, - value=36). Title, axis labels, tick labels all clear in both themes. Minor: - value labels above bars are on the smaller side but readable.' - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping elements. Three bars well-spaced with clear category - labels and value labels that don't collide. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Bars clearly visible at full color. Only 3 bars — good prominence. - Okabe-Ito colors provide strong contrast. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette — CVD-safe. Categories distinguishable by hue and - luminance contrast. - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: 'Plot fills most of canvas. Minor: with only 3 bars at 3200px, pygal''s - bar layout leaves some right-side space. Not severe but canvas utilization - could be improved.' - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: 'Y-axis: ''Budget (thousands $)'' with units. X-axis: ''Breadcrumb: - Company'' serves drilldown navigation. Title in correct format.' - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First bar #009E73, second #D55E00, third #0072B2 — Okabe-Ito in - order. Light bg #FAF8F1, dark bg #1A1A17. Data colors identical between - themes. Chrome adapts correctly.' - design_excellence: - score: 11 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: true - comment: Well-configured library look. Intentional per-bar Okabe-Ito coloring - is thoughtful, minimal clean style. Not publication-ready (no typography - weight hierarchy, no focal-point emphasis beyond bar height). - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: No grid lines (good clean choice), careful margin/spacing settings, - opacity=0.9, smooth CSS transition. Some explicit refinement visible beyond - defaults. - - id: DE-03 - name: Data Storytelling - score: 3 - max: 6 - passed: true - comment: Per-bar distinct colors create visual distinction across divisions. - Technology clearly dominates via bar height. Value labels support comparison. - No dedicated focal-point emphasis or annotation-driven storytelling. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct bar chart with hierarchical drilldown. Root level shows top-level - categories; drilling shows sub-categories. - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: 'All features: click-to-drill via xlink, breadcrumb trail, back button, - animated transitions, consistent color mapping, cursor pointer, value labels.' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Categories on X, budget values on Y. All data correctly mapped. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title 'bar-drilldown · python · pygal · anyplot.ai' correct. Legend - omitted (show_legend=False) — appropriate for single-series with per-bar - colors. - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 3-level hierarchy with parent/child relationships, both intermediate - nodes (drillable) and leaf nodes, varying value ranges across levels. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Corporate budget breakdown — real comprehensible business scenario, - neutral domain. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Values sum correctly across hierarchy levels (dev+infra+data=4200, - retail+enterprise+partner=3100, hr+finance+legal=2400). Realistic budget - proportions. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Flat loop-based structure, no classes/functions. Loop over hierarchy - levels is the right pattern. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Data is hardcoded — fully deterministic, no random seed needed. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: json, os, sys, pygal, pygal.style.Style — all used. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean, appropriate complexity for drilldown feature. HTML template - well-structured. JS event delegation is idiomatic. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html. Current pygal API used. - library_mastery: - score: 9 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: 'Expert use of pygal API: custom Style with all tokens, print_values/value_formatter, - xlink per-bar, label per-bar, render_to_png + render() for SVG.' - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: Creative use of pygal's xlink per-bar data dicts combined with SVG - embedding in HTML + JS event delegation for true drilldown. Distinctive - to pygal's SVG-centric architecture. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - html-export - - hover-tooltips - patterns: - - iteration-over-groups - dataprep: [] - styling: - - alpha-blending diff --git a/plots/bar-drilldown/metadata/python/seaborn.yaml b/plots/bar-drilldown/metadata/python/seaborn.yaml deleted file mode 100644 index 5ddd85af1e..0000000000 --- a/plots/bar-drilldown/metadata/python/seaborn.yaml +++ /dev/null @@ -1,241 +0,0 @@ -library: seaborn -language: python -specification_id: bar-drilldown -created: '2026-05-20T11:18:01Z' -updated: '2026-05-20T11:37:36Z' -generated_by: claude-sonnet -workflow_run: 26158838659 -issue: 3783 -language_version: 3.13.13 -library_version: 0.13.2 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/seaborn/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/python/seaborn/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 89 -review: - strengths: - - Gold edge accent on East bar is an elegant focal-point technique that guides the - viewer to the top performer without simulating interactivity - - Quarterly values sum precisely to annual totals — internally consistent data with - real-world plausibility - - 'Both theme renders fully correct: warm backgrounds (#FAF8F1/#1A1A17), theme-adaptive - chrome, identical Okabe-Ito data colors across light and dark' - - Two-panel layout with breadcrumb text effectively narrates the hierarchical drilldown - concept in static form - - Clean code with all font sizes explicitly set and correct current seaborn API - usage (errorbar=None, sns.move_legend) - weaknesses: - - Bottom panel value labels at fontsize=6 are borderline small for mobile readability - — raise to 7pt - - Most detailed annotation work (patch iteration, ax.text labels) is done at matplotlib - level rather than seaborn level, limiting library distinctiveness score - - Breadcrumb text styling is functional but plain — a slightly bolder weight or - subtle background box would integrate it more visually with the panel design - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct, not pure white - Chrome: Title "Sales by Region · bar-drilldown · python · seaborn · anyplot.ai" in dark ink, readable. Axis labels "Annual Revenue ($M)", "Quarterly Revenue ($M)", "Region" all in dark ink at 10pt. Tick labels at 8pt in INK_SOFT (#4A4A44). Breadcrumb "All Regions ▶ Quarterly Breakdown by Region" in muted dark tone between panels. All chrome elements readable. - Data: Top panel — 4 bars in brand green (#009E73), East bar has gold (#E69F00) border accent. Dollar value labels ($12.4M, $8.7M, $15.2M, $10.1M) above bars at 8pt. Bottom panel — 16 grouped bars using Okabe-Ito: Q1=#009E73, Q2=#D55E00, Q3=#0072B2, Q4=#CC79A7. Value labels above each bar at 6pt in INK_SOFT. - Legibility verdict: PASS - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct, not pure black - Chrome: Title in light ink (#F0EFE8), readable. Axis labels and tick labels in light chrome (#B8B7B0). Breadcrumb in muted light tone. Legend frame in elevated dark (#242420) with muted border. No dark-on-dark failures — all text elements clearly visible against the dark surface. - Data: Identical colors to light render — brand green (#009E73) bars in top panel, East gold border, same Okabe-Ito Q1-Q4 colors in bottom panel. Data series colors unchanged between themes as required. - Legibility verdict: PASS - criteria_checklist: - visual_quality: - score: 29 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: All font sizes explicitly set; 6pt bottom-panel labels borderline - small but visible in both themes - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text in either render - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Bars clearly visible, colors well-contrasted on both backgrounds - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito is CVD-safe; gold accent supplementary only - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Two-panel layout with height_ratios=[2,3] uses canvas well, no clipping - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Descriptive labels with units ($M) on both axes - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73, Okabe-Ito order for multi-series, correct - warm backgrounds, full theme-adaptive chrome' - design_excellence: - score: 14 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 6 - max: 8 - passed: true - comment: 'Strong design: gold focal-point accent, warm theme backgrounds, - clear panel hierarchy — above generic defaults' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: 'Good: despined axes, very subtle grid (alpha=0.10), elevated legend - background, generous panel spacing' - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: 'Good: two-panel hierarchy narrates overview→drill, gold East border - is a data-driven focal point, breadcrumb contextualizes relationship' - spec_compliance: - score: 13 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 4 - max: 5 - passed: true - comment: Correct bar chart type with hierarchical data; static two-panel is - correct seaborn alternative to click-drill interactivity - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Breadcrumb, value labels, two-level display, color mapping present. - Back button and animations are not feasible for static seaborn. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X=Region, Y=Revenue correct. All 4 regions × 4 quarters visible. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title follows required format with descriptive prefix. Quarter legend - labels match data. - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Shows both hierarchy levels, regional variation, and quarterly seasonal - patterns - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Real-world regional sales scenario, plausible revenue values, neutral - topic - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Quarterly values sum precisely to annual totals — internally consistent - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Linear imports→data→plot→save, no functions or classes - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Fully deterministic hardcoded data - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All 4 imports used, no extras - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean Pythonic code, no over-engineering or fake UI - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Correct plot-{THEME}.png output, current seaborn API - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: Correct axes-level barplot, errorbar=None, sns.set_theme with rc, - sns.despine, sns.move_legend — all idiomatic - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Uses sns.move_legend() and sns.despine() distinctively; detailed - annotation work is at matplotlib level - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - subplots - - annotations - - custom-legend - patterns: - - explicit-figure - - iteration-over-groups - dataprep: [] - styling: - - edge-highlighting - - grid-styling diff --git a/plots/bar-drilldown/metadata/r/ggplot2.yaml b/plots/bar-drilldown/metadata/r/ggplot2.yaml deleted file mode 100644 index 420c235c2d..0000000000 --- a/plots/bar-drilldown/metadata/r/ggplot2.yaml +++ /dev/null @@ -1,248 +0,0 @@ -library: ggplot2 -language: r -specification_id: bar-drilldown -created: '2026-05-20T11:36:44Z' -updated: '2026-05-20T12:00:38Z' -generated_by: claude-sonnet -workflow_run: 26159622592 -issue: 3783 -language_version: 4.4.1 -library_version: 3.5.1 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/r/ggplot2/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/bar-drilldown/r/ggplot2/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 87 -review: - strengths: - - 'Perfect theme adaptability: both renders fully readable with correct backgrounds - (#FAF8F1/#1A1A17) and all chrome tokens correctly applied' - - Informative facet strip labels ('Engineering · $4.20M total') give immediate department-level - context without a separate plot element - - Alpha emphasis (1.0 vs 0.72) on top-budget item per department creates visual - hierarchy without extra annotations - - Data-insight subtitle explicitly names the story ('Engineering leads … 2.3×') - - unique_item factor pattern correctly solves independent bar ordering in free-scale - facets — a non-obvious ggplot2 technique - - Clean, linear code structure with no over-engineering - weaknesses: - - library(scales) imported but unused — remove it - - Only 2 of the spec's 2–3 hierarchy levels are represented; a third level would - improve DQ-01 feature coverage - - Design polish stops short of 'strong' tier — an explicit facet border line or - a horizontal rule below strip labels would elevate DE-02 to 5/6 - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct, not pure white - Chrome: Title "Department Budget Breakdown · bar-drilldown · r · ggplot2 · anyplot.ai" in dark ink; subtitle in soft dark ink; facet strip labels bold dark; axis tick labels in secondary dark ink; x-axis label "Budget (USD millions)" in dark ink — all clearly readable - Data: Engineering bars in #009E73 (Okabe-Ito pos 1), Sales in #D55E00 (pos 2), Marketing in #0072B2 (pos 3), Operations in #CC79A7 (pos 4); top item per department at alpha 1.0, others at 0.72; dollar labels to right of bars - Legibility verdict: PASS - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct, not pure black - Chrome: Title and strip labels in near-white (#F0EFE8); axis tick labels and data labels in warm gray (#B8B7B0); facet strip backgrounds in elevated near-black (#242420) — all clearly readable, no dark-on-dark failures detected - Data: Bar colors identical to light render — #009E73, #D55E00, #0072B2, #CC79A7 unchanged; alpha variation preserved - Legibility verdict: PASS - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All font sizes explicitly set (base 8pt, axis title 10pt, strip 10pt, - title 11pt, subtitle 8.5pt); proportions well-balanced; readable in both - themes - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: Bar labels positioned with hjust=-0.1; generous panel spacing; no - text collision - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Bars clearly visible; alpha variation (1.0 vs 0.72) emphasises top - items without hiding others - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette, CVD-safe; alpha adds luminance separation - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 2x2 facet fills canvas well; generous panel spacing; nothing cut - off - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: '''Budget (USD millions)'' is descriptive with units; y-axis NULL - appropriate' - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Okabe-Ito positions 1-4 in order; backgrounds #FAF8F1/#1A1A17; chrome - fully adaptive' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Alpha emphasis on top items, informative strip labels with totals, - data-insight subtitle — clearly above default, not yet FiveThirtyEight-level - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Spines removed via theme_minimal; x-grid blanked; y-grid at linewidth - 0.15; ELEVATED_BG on strips; axis ticks removed - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Subtitle names the leader and gap; alpha accent highlights top items; - bars sorted ascending for natural comparison - spec_compliance: - score: 12 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 3 - max: 5 - passed: true - comment: Correct base type (horizontal bar); drilldown approximated via simultaneous - facets per ggplot2 static-library guidance - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Bar chart, value labels, hierarchical data (2 levels), alpha emphasis, - department totals present; breadcrumbs/click/animation absent (library limitation) - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Items on y-axis, budget on x-axis (after coord_flip); all 12 data - points visible; free scales per facet - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title matches {Descriptive} · bar-drilldown · r · ggplot2 · anyplot.ai; - no legend needed (color conveyed by facet headers) - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: 4 departments x 3 items; 2 hierarchy levels; varied values; minor - deduction for only 2 of possible 3 hierarchy levels - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Company annual budget — real neutral business scenario; all department - and expense category names are authentic - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Values $0.40M-$1.85M per line item; $1.80M-$4.20M per department - — realistic for mid-sized company team budgets - code_quality: - score: 9 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear: imports -> tokens -> data -> summaries -> plot -> save; - no functions or classes' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Fully deterministic; all data hardcoded - - id: CQ-03 - name: Clean Imports - score: 1 - max: 2 - passed: false - comment: library(scales) imported but unused — sprintf() used for labels, - expansion() is ggplot2-native - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean dplyr pipes; unique_item factor trick solves independent facet - ordering elegantly; setNames() for labeller is idiomatic - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png via ragg::agg_png; modern ggplot2 3.5.1 - API - library_mastery: - score: 9 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: 'Full grammar-of-graphics pipeline: geom_col + geom_text + facet_wrap(scales=free) - + coord_flip + scale_*_manual + scale_alpha_manual + custom labeller' - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: as_labeller with setNames for rich strip headers; unique_item factor - workaround for independent facet ordering — both ggplot2-distinctive - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - faceting - - layer-composition - patterns: - - groupby-aggregation - dataprep: [] - styling: - - alpha-blending diff --git a/plots/bar-drilldown/specification.md b/plots/bar-drilldown/specification.md deleted file mode 100644 index d612cefb46..0000000000 --- a/plots/bar-drilldown/specification.md +++ /dev/null @@ -1,31 +0,0 @@ -# bar-drilldown: Column Chart with Hierarchical Drilling - -## Description - -A column/bar chart with hierarchical drilldown functionality that displays one level of categorical data at a time. Clicking on any column reveals a detailed breakdown of that category's subcategories as a new bar chart at the next level. Breadcrumb navigation allows users to traverse back up the hierarchy. This interactive visualization excels at exploring multi-level categorical data while maintaining precise value comparisons that bar charts provide. - -## Applications - -- Time-based drill navigation (year to quarter to month) -- Geographic data exploration (region to country to city) -- Product hierarchy exploration (category to subcategory to product) -- Financial statement breakdown (department to team to expense type) - -## Data - -- `id` (string) - unique identifier for each node -- `name` (string) - display label for the category -- `value` (numeric) - data value determining column height -- `parent` (string) - id of parent node (null/empty for root level) -- Size: 3-8 categories per level, 2-3 hierarchy levels deep -- Structure: hierarchical/tree with parent-child relationships - -## Notes - -- Click on a column to drill into its sub-level breakdown -- Display breadcrumb trail for navigation (e.g., "All > Region > Country") -- Provide a back button or clickable breadcrumbs to drill up -- Animate transitions between hierarchy levels for smooth UX -- Maintain consistent color mapping across drill levels when possible -- Show visual indicator (cursor change or icon) that columns are clickable -- Display value labels on columns for precise comparison diff --git a/plots/bar-drilldown/specification.yaml b/plots/bar-drilldown/specification.yaml deleted file mode 100644 index 612762bda1..0000000000 --- a/plots/bar-drilldown/specification.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# Specification-level metadata for bar-drilldown -# Auto-synced to PostgreSQL on push to main - -spec_id: bar-drilldown -title: Column Chart with Hierarchical Drilling - -# Specification tracking -created: 2026-01-11T22:53:03Z -updated: 2026-01-11T22:53:03Z -issue: 3783 -suggested: MarkusNeusinger - -# Classification tags (applies to all library implementations) -# See docs/reference/tagging-system.md for detailed guidelines -tags: - plot_type: - - bar - data_type: - - categorical - - hierarchical - - numeric - domain: - - general - - business - features: - - interactive - - drilldown - - animated diff --git a/plots/bar-race-animated/implementations/python/altair.py b/plots/bar-race-animated/implementations/python/altair.py deleted file mode 100644 index 3a683f2cf0..0000000000 --- a/plots/bar-race-animated/implementations/python/altair.py +++ /dev/null @@ -1,179 +0,0 @@ -""" anyplot.ai -bar-race-animated: Animated Bar Chart Race -Library: altair 6.1.0 | Python 3.13.13 -Quality: 89/100 | Updated: 2026-05-19 -""" - -import os - -import altair as alt -import numpy as np -import pandas as pd - - -# 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" - -# Okabe-Ito palette (first series always #009E73) + accessible extensions for 10 entities -COLORS = [ - "#009E73", # Okabe-Ito pos 1 — brand green, always first - "#C475FD", # Okabe-Ito pos 2 — vermillion - "#4467A3", # Okabe-Ito pos 3 — blue - "#BD8233", # Okabe-Ito pos 4 — reddish purple - "#AE3030", # Okabe-Ito pos 5 — orange - "#2ABCCD", # Okabe-Ito pos 6 — sky blue - "#954477", # Okabe-Ito pos 7 — yellow - "#332288", # accessible extension — deep indigo - "#117733", # accessible extension — deep green - "#882255", # accessible extension — deep crimson -] - -# Data: Simulated streaming platform subscribers (millions) over time -np.random.seed(42) - -platforms = [ - "StreamFlix", - "ViewMax", - "PlayNow", - "WatchHub", - "MediaGo", - "CineCloud", - "ShowTime", - "PrimeView", - "FlixBox", - "CloudTV", -] -years = list(range(2015, 2025)) - -base_values = { - "StreamFlix": 40, - "ViewMax": 35, - "PlayNow": 30, - "WatchHub": 25, - "MediaGo": 20, - "CineCloud": 15, - "ShowTime": 10, - "PrimeView": 5, - "FlixBox": 8, - "CloudTV": 12, -} -growth_rates = { - "StreamFlix": 1.15, - "ViewMax": 1.12, - "PlayNow": 1.25, - "WatchHub": 1.08, - "MediaGo": 1.20, - "CineCloud": 1.10, - "ShowTime": 1.05, - "PrimeView": 1.30, - "FlixBox": 1.18, - "CloudTV": 1.06, -} - -data = [] -for platform in platforms: - value = base_values[platform] - for year in years: - noise = np.random.uniform(0.9, 1.1) - data.append({"Platform": platform, "Year": year, "Subscribers": round(value * noise, 1)}) - value *= growth_rates[platform] - -df = pd.DataFrame(data) - -# Select key years for small multiples -key_years = [2015, 2018, 2021, 2024] -df_key = df[df["Year"].isin(key_years)].copy() -df_key["Rank"] = df_key.groupby("Year")["Subscribers"].rank(ascending=True, method="first").astype(int) - -# Bar chart layer -bar_chart = ( - alt.Chart(df_key) - .mark_bar(cornerRadiusEnd=6, height=45) - .encode( - x=alt.X( - "Subscribers:Q", - title="Subscribers (millions)", - axis=alt.Axis(labelFontSize=16, titleFontSize=20, grid=True, gridOpacity=0.25), - ), - y=alt.Y("Rank:O", title=None, axis=None, sort="descending"), - color=alt.Color( - "Platform:N", - scale=alt.Scale(domain=platforms, range=COLORS), - legend=alt.Legend( - title="Platform", labelFontSize=16, titleFontSize=18, symbolSize=160, padding=14, cornerRadius=4 - ), - ), - tooltip=[ - alt.Tooltip("Platform:N", title="Platform"), - alt.Tooltip("Subscribers:Q", format=".1f", title="Subscribers (M)"), - alt.Tooltip("Year:O", title="Year"), - ], - ) -) - -# Platform name labels outside bars -text_labels = ( - alt.Chart(df_key) - .mark_text(align="left", dx=8, fontSize=14, fontWeight="bold") - .encode( - x=alt.X("Subscribers:Q"), - y=alt.Y("Rank:O", sort="descending"), - text=alt.Text("Platform:N"), - color=alt.value(INK), - ) -) - -# Value labels inside bars -value_labels = ( - alt.Chart(df_key) - .mark_text(align="right", dx=-8, fontSize=16, fontWeight="normal") - .encode( - x=alt.X("Subscribers:Q"), - y=alt.Y("Rank:O", sort="descending"), - text=alt.Text("Subscribers:Q", format=".0f"), - color=alt.value("white"), - ) -) - -# Combine layers and facet by year -combined = (bar_chart + text_labels + value_labels).properties(width=340, height=750) - -chart = ( - combined.facet( - column=alt.Column("Year:O", header=alt.Header(labelFontSize=26, title=None, labelPadding=15, labelColor=INK)), - spacing=20, - ) - .properties( - title=alt.Title( - "bar-race-animated · python · altair · anyplot.ai", - fontSize=34, - anchor="middle", - subtitle="Streaming Platform Subscribers Over Time (millions)", - subtitleFontSize=22, - dy=-10, - ), - background=PAGE_BG, - ) - .configure_view(strokeWidth=0, fill=PAGE_BG) - .configure_axis( - labelFontSize=18, - titleFontSize=22, - domainColor=INK_SOFT, - tickColor=INK_SOFT, - gridColor=INK_SOFT, - gridOpacity=0.20, - labelColor=INK_SOFT, - titleColor=INK, - ) - .configure_title(color=INK, subtitleColor=INK_SOFT) - .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK) - .resolve_scale(x="independent") -) - -# Save -chart.save(f"plot-{THEME}.png", scale_factor=3.0) -chart.save(f"plot-{THEME}.html") diff --git a/plots/bar-race-animated/implementations/python/bokeh.py b/plots/bar-race-animated/implementations/python/bokeh.py deleted file mode 100644 index adde994932..0000000000 --- a/plots/bar-race-animated/implementations/python/bokeh.py +++ /dev/null @@ -1,200 +0,0 @@ -""" anyplot.ai -bar-race-animated: Animated Bar Chart Race -Library: bokeh 3.9.0 | Python 3.13.13 -Quality: 87/100 | Updated: 2026-05-19 -""" - -import os -import time -from pathlib import Path - -import numpy as np -import pandas as pd -from bokeh.io import output_file, save -from bokeh.layouts import column, gridplot -from bokeh.models import ColumnDataSource, HoverTool, Range1d, Title -from bokeh.plotting import figure -from selenium import webdriver -from selenium.webdriver.chrome.options import Options - - -# Theme tokens -THEME = os.getenv("ANYPLOT_THEME", "light") -PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" -INK = "#1A1A17" if THEME == "light" else "#F0EFE8" -INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" - -# Okabe-Ito palette — first series always #009E73 -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD"] - -# Data: Streaming platform subscriber counts (millions) over 8 years -np.random.seed(42) - -platforms = ["StreamMax", "ViewHub", "FlixNet", "WatchNow", "CineCloud", "MediaFlow"] -years = list(range(2016, 2024)) -platform_colors = dict(zip(platforms, IMPRINT, strict=True)) - -# Generate realistic growth patterns -data_rows = [] -base_values = [50, 80, 120, 30, 20, 40] -growth_rates = [1.35, 1.15, 1.08, 1.45, 1.55, 1.25] - -for i, platform in enumerate(platforms): - value = base_values[i] - for year in years: - noise = np.random.uniform(0.9, 1.1) - value = value * growth_rates[i] * noise - data_rows.append({"platform": platform, "year": year, "subscribers": round(value, 1)}) - -df = pd.DataFrame(data_rows) - -# 4 key snapshots for the small multiples grid -snapshot_years = [2016, 2018, 2021, 2023] -max_subscribers = df["subscribers"].max() * 1.15 - -# Build individual plots for each snapshot year -plots = [] -for idx, year in enumerate(snapshot_years): - year_data = df[df["year"] == year].copy() - year_data = year_data.sort_values("subscribers", ascending=True) - - y_positions = list(range(len(platforms))) - bar_colors = [platform_colors[p] for p in year_data["platform"]] - - source = ColumnDataSource( - data={ - "y": y_positions, - "right": year_data["subscribers"].tolist(), - "platform": year_data["platform"].tolist(), - "color": bar_colors, - } - ) - - p = figure( - width=2400, - height=1120, - x_range=Range1d(0, max_subscribers), - y_range=(-0.5, len(platforms) - 0.5), - x_axis_label="Subscribers (millions)" if idx >= 2 else None, - tools="", - toolbar_location=None, - ) - - # Year title above each panel - p.add_layout(Title(text=str(year), text_font_size="36pt", text_font_style="bold", text_color=INK), "above") - - # Horizontal bars - bars = p.hbar( - y="y", right="right", height=0.65, source=source, color="color", alpha=0.9, line_color=PAGE_BG, line_width=2 - ) - - # HoverTool for HTML interactivity - hover = HoverTool(renderers=[bars], tooltips=[("Platform", "@platform"), ("Subscribers", "@right{0.0}M")]) - p.add_tools(hover) - - # Platform labels inside bars - platform_list = year_data["platform"].tolist() - for _, row in year_data.iterrows(): - y_pos = platform_list.index(row["platform"]) - p.text( - x=[row["subscribers"] * 0.03], - y=[y_pos], - text=[row["platform"]], - text_font_size="18pt", - text_font_style="bold", - text_color="white", - text_baseline="middle", - ) - # Value labels at bar end - p.text( - x=[row["subscribers"] + max_subscribers * 0.01], - y=[y_pos], - text=[f"{row['subscribers']:.0f}M"], - text_font_size="16pt", - text_color=INK_SOFT, - text_baseline="middle", - ) - - # Theme-adaptive chrome - p.background_fill_color = PAGE_BG - p.border_fill_color = PAGE_BG - p.outline_line_color = None - p.yaxis.visible = False - p.xaxis.axis_label_text_font_size = "22pt" - p.xaxis.axis_label_text_color = INK - p.xaxis.major_label_text_font_size = "18pt" - p.xaxis.major_label_text_color = INK_SOFT - p.xaxis.axis_line_color = INK_SOFT - p.xaxis.major_tick_line_color = INK_SOFT - p.xgrid.grid_line_color = INK - p.xgrid.grid_line_alpha = 0.08 - p.ygrid.grid_line_color = None - p.min_border_right = 110 - - plots.append(p) - -# 2×2 grid -grid = gridplot([[plots[0], plots[1]], [plots[2], plots[3]]], merge_tools=False) - -# Overall title figure -title_fig = figure(width=4800, height=130, tools="", toolbar_location=None, x_range=(0, 1), y_range=(0, 1)) -title_fig.background_fill_color = PAGE_BG -title_fig.border_fill_color = PAGE_BG -title_fig.outline_line_color = None -title_fig.xaxis.visible = False -title_fig.yaxis.visible = False -title_fig.xgrid.grid_line_color = None -title_fig.ygrid.grid_line_color = None -title_fig.text( - x=[0.5], - y=[0.5], - text=["bar-race-animated · python · bokeh · anyplot.ai"], - text_font_size="32pt", - text_font_style="bold", - text_align="center", - text_baseline="middle", - text_color=INK, -) - -# Color legend strip at the bottom -n = len(platforms) -legend_fig = figure(width=4800, height=105, tools="", toolbar_location=None, x_range=(0, n), y_range=(0, 1)) -legend_fig.background_fill_color = PAGE_BG -legend_fig.border_fill_color = PAGE_BG -legend_fig.outline_line_color = None -legend_fig.xaxis.visible = False -legend_fig.yaxis.visible = False -legend_fig.xgrid.grid_line_color = None -legend_fig.ygrid.grid_line_color = None - -for i, (platform, color) in enumerate(zip(platforms, IMPRINT, strict=True)): - legend_fig.rect(x=[i + 0.12], y=[0.5], width=0.17, height=0.5, color=color, alpha=0.9) - legend_fig.text( - x=[i + 0.25], y=[0.5], text=[platform], text_font_size="20pt", text_color=INK_SOFT, text_baseline="middle" - ) - -# Full layout -layout = column(title_fig, grid, legend_fig) - -# Save HTML -output_file(f"plot-{THEME}.html") -save(layout) - -# Screenshot with headless Chrome -W, H = 4800, 2700 -opts = Options() -for arg in ( - "--headless=new", - "--no-sandbox", - "--disable-dev-shm-usage", - "--disable-gpu", - f"--window-size={W},{H}", - "--hide-scrollbars", -): - opts.add_argument(arg) -driver = webdriver.Chrome(options=opts) -driver.set_window_size(W, H) -driver.get(f"file://{Path(f'plot-{THEME}.html').resolve()}") -time.sleep(3) -driver.save_screenshot(f"plot-{THEME}.png") -driver.quit() diff --git a/plots/bar-race-animated/implementations/python/letsplot.py b/plots/bar-race-animated/implementations/python/letsplot.py deleted file mode 100644 index 1cdf262b1c..0000000000 --- a/plots/bar-race-animated/implementations/python/letsplot.py +++ /dev/null @@ -1,94 +0,0 @@ -""" anyplot.ai -bar-race-animated: Animated Bar Chart Race -Library: letsplot 4.9.0 | Python 3.13.13 -Quality: 86/100 | Updated: 2026-05-19 -""" - -import os - -import numpy as np -import pandas as pd -from lets_plot import * - - -LetsPlot.setup_html() - -THEME = os.getenv("ANYPLOT_THEME", "light") -PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" -INK = "#1A1A17" if THEME == "light" else "#F0EFE8" -INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" - -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD"] - -np.random.seed(42) - -platforms = ["StreamMax", "ViewHub", "FlixNet", "WatchNow", "CineCloud", "MediaFlow"] -years = list(range(2016, 2024)) - -# Plausible growth: FlixNet dominates early, WatchNow/CineCloud surge to overtake -base_values = [60, 100, 155, 28, 18, 65] -growth_rates = [1.16, 1.08, 1.03, 1.26, 1.30, 1.10] - -data_rows = [] -for i, platform in enumerate(platforms): - value = base_values[i] - for year in years: - noise = np.random.uniform(0.9, 1.1) - value = value * growth_rates[i] * noise - data_rows.append({"platform": platform, "year": year, "subscribers": round(value, 1)}) - -df = pd.DataFrame(data_rows) - -platform_colors = dict(zip(platforms, IMPRINT)) -snapshot_years = [2016, 2018, 2021, 2023] - -anyplot_theme = theme( - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - panel_background=element_rect(fill=PAGE_BG), - panel_grid_major=element_blank(), - panel_grid_minor=element_blank(), - axis_title=element_text(color=INK), - axis_text=element_text(color=INK_SOFT), - axis_line=element_line(color=INK_SOFT), - plot_title=element_text(color=INK), - legend_position="none", -) - -plots = [] -for year in snapshot_years: - year_data = df[df["year"] == year].copy() - year_data = year_data.sort_values("subscribers", ascending=True) - year_data["platform"] = pd.Categorical( - year_data["platform"], categories=year_data["platform"].tolist(), ordered=True - ) - - plot = ( - ggplot(year_data, aes(x="platform", y="subscribers", fill="platform")) - + geom_bar(stat="identity", width=0.7, alpha=0.9) - + coord_flip() - + scale_fill_manual(values=platform_colors) - + labs(title=str(year), x="", y="Subscribers (millions)") - + theme_minimal() - + anyplot_theme - + theme( - plot_title=element_text(size=28, face="bold", color=INK), - axis_title_x=element_text(size=18, color=INK), - axis_title_y=element_blank(), - axis_text_x=element_text(size=16, color=INK_SOFT), - axis_text_y=element_text(size=18, color=INK_SOFT), - ) - ) - plots.append(plot) - -grid = ( - gggrid(plots, ncol=2) - + labs(title="bar-race-animated · python · letsplot · anyplot.ai") - + theme( - plot_title=element_text(size=32, face="bold", hjust=0.5, color=INK), - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - ) - + ggsize(1600, 900) -) - -ggsave(grid, f"plot-{THEME}.png", path=".", scale=3) -ggsave(grid, f"plot-{THEME}.html", path=".") diff --git a/plots/bar-race-animated/implementations/python/matplotlib.py b/plots/bar-race-animated/implementations/python/matplotlib.py deleted file mode 100644 index d8b614f69f..0000000000 --- a/plots/bar-race-animated/implementations/python/matplotlib.py +++ /dev/null @@ -1,146 +0,0 @@ -""" anyplot.ai -bar-race-animated: Animated Bar Chart Race -Library: matplotlib 3.10.9 | Python 3.13.13 -Quality: 85/100 | Created: 2026-05-19 -""" - -import os - -import matplotlib.patheffects as pe -import matplotlib.pyplot as plt -from matplotlib.gridspec import GridSpec - - -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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477"] - -# Data: Tech Company Market Cap ($B) — year-end snapshots (2019–2024) -companies = ["Alphabet", "Amazon", "Apple", "Meta", "Microsoft", "Nvidia", "Tesla"] -company_colors = {c: IMPRINT[i] for i, c in enumerate(companies)} - -market_cap = { - "Alphabet": [920, 1200, 1960, 1140, 1800, 2150], - "Amazon": [940, 1640, 1730, 855, 1570, 2170], - "Apple": [1050, 2250, 2950, 2070, 2990, 3750], - "Meta": [580, 780, 890, 335, 940, 1440], - "Microsoft": [1200, 1680, 2530, 1790, 2800, 3130], - "Nvidia": [145, 325, 745, 365, 1220, 3280], - "Tesla": [75, 670, 1060, 388, 790, 695], -} -years = [2019, 2020, 2021, 2022, 2023, 2024] - -max_val = max(v for vlist in market_cap.values() for v in vlist) - -# GridSpec gives precise control over subplot spacing -fig = plt.figure(figsize=(20, 11), facecolor=PAGE_BG) -gs = GridSpec(2, 3, figure=fig) -axes = [fig.add_subplot(gs[i // 3, i % 3]) for i in range(6)] - -for idx, (year, ax) in enumerate(zip(years, axes, strict=False)): - ax.set_facecolor(PAGE_BG) - - # Sort ascending so highest-value company tops the chart - snapshot = {c: market_cap[c][idx] for c in companies} - sorted_items = sorted(snapshot.items(), key=lambda x: x[1]) - names = [item[0] for item in sorted_items] - values = [item[1] for item in sorted_items] - bar_colors = [company_colors[n] for n in names] - - bars = ax.barh(names, values, color=bar_colors, height=0.72, edgecolor=PAGE_BG, linewidth=0.8) - - # Nvidia tracked across all panels with a distinctive colored border - for name, bar in zip(names, bars, strict=False): - if name == "Nvidia": - bar.set_edgecolor(company_colors["Nvidia"]) - bar.set_linewidth(2.0) - - # Value labels — Nvidia's label rendered in brand color with stroke for legibility - offset = max_val * 0.015 - for i, (name, val) in enumerate(zip(names, values, strict=False)): - is_nvidia = name == "Nvidia" - txt = ax.text( - val + offset, - i, - f"${val:,}B", - va="center", - ha="left", - fontsize=13, - color=company_colors["Nvidia"] if is_nvidia else INK_SOFT, - fontweight="bold" if is_nvidia else "normal", - ) - if is_nvidia: - txt.set_path_effects([pe.withStroke(linewidth=2, foreground=PAGE_BG), pe.Normal()]) - - ax.set_title(str(year), fontsize=20, fontweight="bold", color=INK, pad=8) - ax.set_xlim(0, max_val * 1.38) - ax.set_xlabel("Market Cap ($B)", fontsize=14, color=INK_MUTED, labelpad=4) - ax.set_xticks([]) - ax.tick_params(axis="y", labelsize=16, colors=INK_SOFT, length=0) - for spine in ("top", "right", "bottom"): - ax.spines[spine].set_visible(False) - ax.spines["left"].set_color(INK_SOFT) - - # Final panel: annotate Nvidia's historic rise + inset sparkline - if idx == 5 and "Nvidia" in names: - nv_pos = names.index("Nvidia") - nv_val = values[nv_pos] - nv_color = company_colors["Nvidia"] - growth = nv_val / market_cap["Nvidia"][0] - - # Arrow annotation pointing to Nvidia's bar from safe empty space between rows - ax.annotate( - f" ×{growth:.0f} since 2019 ", - xy=(nv_val, nv_pos), - xytext=(max_val * 0.72, 2.5), - fontsize=14, - fontweight="bold", - color=nv_color, - arrowprops={"arrowstyle": "-|>", "color": nv_color, "lw": 2.0, "connectionstyle": "arc3,rad=-0.25"}, - bbox={ - "boxstyle": "round,pad=0.35", - "facecolor": ELEVATED_BG, - "edgecolor": nv_color, - "linewidth": 1.5, - "alpha": 0.92, - }, - ) - - # Inset sparkline in the empty lower-right area: Nvidia 2019→2024 trajectory - ax_spark = ax.inset_axes([0.35, 0.04, 0.60, 0.20]) - ax_spark.set_facecolor(ELEVATED_BG) - nv_vals = [market_cap["Nvidia"][y_idx] for y_idx in range(len(years))] - ax_spark.plot(years, nv_vals, color=nv_color, lw=2.0, marker="o", markersize=4.5, solid_capstyle="round") - ax_spark.fill_between(years, nv_vals, alpha=0.15, color=nv_color) - ax_spark.set_xticks(years) - ax_spark.set_xticklabels([str(y) for y in years], fontsize=9, color=INK_MUTED, rotation=30, ha="right") - ax_spark.set_yticks([]) - ax_spark.tick_params(length=0) - for sp in ax_spark.spines.values(): - sp.set_color(INK_SOFT) - sp.set_linewidth(0.5) - ax_spark.text( - 0.5, - 1.08, - "Nvidia $145B → $3,280B", - transform=ax_spark.transAxes, - fontsize=9, - ha="center", - color=INK_MUTED, - fontweight="bold", - ) - -fig.suptitle( - "Tech Giant Market Cap · bar-race-animated · python · matplotlib · anyplot.ai", - fontsize=24, - fontweight="medium", - color=INK, -) -plt.tight_layout(rect=[0, 0, 1, 0.96]) -plt.savefig(f"plot-{THEME}.png", dpi=300, bbox_inches="tight", facecolor=PAGE_BG) -plt.close() diff --git a/plots/bar-race-animated/implementations/python/plotly.py b/plots/bar-race-animated/implementations/python/plotly.py deleted file mode 100644 index 3b3814643e..0000000000 --- a/plots/bar-race-animated/implementations/python/plotly.py +++ /dev/null @@ -1,203 +0,0 @@ -""" anyplot.ai -bar-race-animated: Animated Bar Chart Race -Library: plotly 6.7.0 | Python 3.13.13 -Quality: 88/100 | Updated: 2026-05-19 -""" - -import os - -import numpy as np -import pandas as pd -import plotly.graph_objects as go - - -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.25)" if THEME == "light" else "rgba(240,239,232,0.25)" - -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477"] - -# Data: Major economy GDP rankings (approximate, in trillion USD, 1995–2023) -np.random.seed(42) - -countries = ["USA", "China", "Japan", "Germany", "UK", "France", "Brazil"] -years = list(range(1995, 2024)) - -base_gdp = {"USA": 7.70, "China": 0.73, "Japan": 5.45, "Germany": 2.60, "UK": 1.28, "France": 1.60, "Brazil": 0.77} -color_map = dict(zip(countries, IMPRINT)) - -data_rows = [] -for country in countries: - gdp = base_gdp[country] - for year in years: - noise = np.random.uniform(0.99, 1.01) - data_rows.append({"Country": country, "Year": year, "GDP": round(gdp * noise, 3)}) - if country == "USA": - rate = -0.020 if year == 2009 else 0.047 - elif country == "China": - rate = 0.115 if year < 2005 else (0.138 if year < 2015 else (0.072 if year < 2020 else 0.056)) - elif country == "Japan": - rate = -0.012 if year in [2009, 2011] else 0.014 - elif country == "Germany": - rate = -0.050 if year == 2009 else 0.040 - elif country == "UK": - rate = -0.040 if year == 2009 else 0.042 - elif country == "France": - rate = -0.030 if year == 2009 else 0.037 - else: # Brazil - rate = 0.083 if year < 2011 else (0.025 if year < 2015 else (-0.005 if year < 2019 else 0.038)) - gdp = gdp * (1 + rate) - -df = pd.DataFrame(data_rows) -max_gdp = df["GDP"].max() * 1.18 -title_text = "GDP Rankings · bar-race-animated · python · plotly · anyplot.ai" - -shared_xaxis = dict( - title=dict(text="GDP (Trillion USD)", font=dict(size=22, color=INK)), - tickfont=dict(size=18, color=INK_SOFT), - range=[0, max_gdp], - gridcolor=GRID, - linecolor=INK_SOFT, - zerolinecolor=INK_SOFT, -) -shared_yaxis = dict(tickfont=dict(size=20, color=INK_SOFT), linecolor=INK_SOFT, showgrid=False) -shared_margin = dict(l=150, r=160, t=140, b=160) - -# Animation frames: bars sorted by GDP value each year -frames = [] -slider_steps = [] -for year in years: - year_data = df[df["Year"] == year].sort_values("GDP", ascending=True) - frames.append( - go.Frame( - data=[ - go.Bar( - x=year_data["GDP"], - y=year_data["Country"], - orientation="h", - text=[f"{v:.1f}T" for v in year_data["GDP"]], - textposition="outside", - textfont=dict(size=18, color=INK), - marker=dict(color=[color_map[c] for c in year_data["Country"]], line=dict(width=0)), - ) - ], - name=str(year), - ) - ) - slider_steps.append( - dict( - args=[ - [str(year)], - dict(frame=dict(duration=600, redraw=True), mode="immediate", transition=dict(duration=300)), - ], - label=str(year), - method="animate", - ) - ) - -# Initial state: 1995 -init_data = df[df["Year"] == years[0]].sort_values("GDP", ascending=True) - -fig_anim = go.Figure( - data=[ - go.Bar( - x=init_data["GDP"], - y=init_data["Country"], - orientation="h", - text=[f"{v:.1f}T" for v in init_data["GDP"]], - textposition="outside", - textfont=dict(size=18, color=INK), - marker=dict(color=[color_map[c] for c in init_data["Country"]], line=dict(width=0)), - ) - ], - layout=go.Layout( - title=dict(text=title_text, font=dict(size=28, color=INK), x=0.5, xanchor="center"), - xaxis=shared_xaxis, - yaxis=shared_yaxis, - paper_bgcolor=PAGE_BG, - plot_bgcolor=PAGE_BG, - margin=shared_margin, - showlegend=False, - updatemenus=[ - dict( - type="buttons", - showactive=False, - y=1.08, - x=0.0, - xanchor="left", - buttons=[ - dict( - label="▶ Play", - method="animate", - args=[ - None, - dict( - frame=dict(duration=600, redraw=True), fromcurrent=True, transition=dict(duration=300) - ), - ], - ), - dict( - label="⏸ Pause", - method="animate", - args=[ - [None], - dict(frame=dict(duration=0, redraw=False), mode="immediate", transition=dict(duration=0)), - ], - ), - ], - ) - ], - sliders=[ - dict( - active=0, - currentvalue=dict(font=dict(size=24, color=INK), prefix="Year: ", visible=True, xanchor="center"), - font=dict(size=16, color=INK_SOFT), - bgcolor=ELEVATED_BG, - bordercolor=INK_SOFT, - steps=slider_steps, - pad=dict(b=10, t=50), - ) - ], - ), - frames=frames, -) - -fig_anim.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn") - -# Static PNG: final year (2023) snapshot showing the race outcome -final_data = df[df["Year"] == years[-1]].sort_values("GDP", ascending=True) - -fig_png = go.Figure( - data=[ - go.Bar( - x=final_data["GDP"], - y=final_data["Country"], - orientation="h", - text=[f"{v:.1f}T" for v in final_data["GDP"]], - textposition="outside", - textfont=dict(size=18, color=INK), - marker=dict(color=[color_map[c] for c in final_data["Country"]], line=dict(width=0)), - ) - ] -) -fig_png.update_layout( - title=dict(text=title_text, font=dict(size=28, color=INK), x=0.5, xanchor="center"), - xaxis=dict( - title=dict(text="GDP (Trillion USD) — 2023 snapshot", font=dict(size=22, color=INK)), - tickfont=dict(size=18, color=INK_SOFT), - range=[0, max_gdp], - gridcolor=GRID, - linecolor=INK_SOFT, - zerolinecolor=INK_SOFT, - ), - yaxis=shared_yaxis, - paper_bgcolor=PAGE_BG, - plot_bgcolor=PAGE_BG, - margin=dict(l=150, r=160, t=120, b=100), - showlegend=False, -) - -fig_png.write_image(f"plot-{THEME}.png", width=1600, height=900, scale=3) diff --git a/plots/bar-race-animated/implementations/python/plotnine.py b/plots/bar-race-animated/implementations/python/plotnine.py deleted file mode 100644 index 50021dae22..0000000000 --- a/plots/bar-race-animated/implementations/python/plotnine.py +++ /dev/null @@ -1,149 +0,0 @@ -""" anyplot.ai -bar-race-animated: Animated Bar Chart Race -Library: plotnine 0.15.4 | Python 3.13.13 -Quality: 82/100 | Updated: 2026-05-19 -""" - -import os - -import numpy as np -import pandas as pd -from plotnine import ( - aes, - coord_flip, - element_blank, - element_line, - element_rect, - element_text, - facet_wrap, - geom_col, - geom_text, - ggplot, - labs, - scale_fill_manual, - scale_y_continuous, - theme, - theme_minimal, -) - - -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" - -NEUTRAL = "#1A1A1A" if THEME == "light" else "#E8E8E0" - -PALETTE = [ - "#009E73", # TechCorp - bluish green - "#C475FD", # DataFlow - vermillion - "#4467A3", # CloudBase - blue - "#BD8233", # NetSys - reddish purple - "#AE3030", # CodeLab - orange - "#2ABCCD", # AppStream - sky blue - "#954477", # ByteWorks - yellow - NEUTRAL, # DigiCore - adaptive neutral -] - -# Data - Tech companies revenue over time (billions USD) -np.random.seed(42) - -companies = ["TechCorp", "DataFlow", "CloudBase", "NetSys", "CodeLab", "AppStream", "ByteWorks", "DigiCore"] - -years = [2018, 2020, 2022, 2024] - -base_values = { - "TechCorp": 150, - "DataFlow": 80, - "CloudBase": 60, - "NetSys": 120, - "CodeLab": 40, - "AppStream": 90, - "ByteWorks": 70, - "DigiCore": 55, -} - -growth_rates = { - "TechCorp": 1.15, - "DataFlow": 1.35, - "CloudBase": 1.45, - "NetSys": 1.08, - "CodeLab": 1.50, - "AppStream": 1.12, - "ByteWorks": 1.25, - "DigiCore": 1.40, -} - -data = [] -for year in years: - year_idx = years.index(year) - for company in companies: - value = base_values[company] * (growth_rates[company] ** year_idx) - value += np.random.normal(0, value * 0.05) - data.append({"company": company, "year": year, "revenue": max(10, value)}) - -df = pd.DataFrame(data) - -df["rank"] = df.groupby("year")["revenue"].rank(ascending=False, method="first").astype(int) -df["year_label"] = df["year"].apply(lambda x: f"Year {x}") -df = df.sort_values(["year", "rank"]) -df["bar_order"] = df.groupby("year")["revenue"].rank(ascending=True, method="first").astype(int) - -# Rank change 2018 → 2024: positive = climbed, negative = fell -ranks = df.pivot_table(index="company", columns="year", values="rank") -rank_chg = (ranks[2018] - ranks[2024]).astype(int).to_dict() - - -def make_bar_label(row): - base = f"{row['company']} ${row['revenue']:.0f}B" - if row["year"] == 2024: - chg = rank_chg.get(row["company"], 0) - if chg >= 3: - return f"{base} ↑{chg}" - elif chg <= -3: - return f"{base} ↓{abs(chg)}" - return base - - -df["bar_label"] = df.apply(make_bar_label, axis=1) - -colors = {company: PALETTE[i] for i, company in enumerate(companies)} - -# Plot -plot = ( - ggplot(df, aes(x="bar_order", y="revenue", fill="company")) - + geom_col(width=0.8, show_legend=False) - + geom_text(aes(label="bar_label"), ha="left", nudge_y=5, size=12, color=INK_SOFT) - + coord_flip() - + facet_wrap("~year_label", ncol=2) - + scale_fill_manual(values=colors) - + scale_y_continuous(expand=(0.02, 0, 0.25, 0)) - + labs( - title="Tech Company Revenue Race · bar-race-animated · python · plotnine · anyplot.ai", - x="", - y="Revenue (Billions USD)", - ) - + theme_minimal() - + theme( - figure_size=(16, 10), - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - panel_background=element_rect(fill=PAGE_BG), - plot_title=element_text(size=24, weight="bold", ha="center", margin={"b": 15}, color=INK), - axis_title_x=element_text(size=20, margin={"t": 10}, color=INK), - axis_title_y=element_blank(), - axis_text_x=element_text(size=16, color=INK_SOFT), - axis_text_y=element_blank(), - strip_text=element_text(size=20, weight="bold", color=INK), - strip_background=element_rect(fill=ELEVATED_BG, color=None), - panel_border=element_blank(), - panel_spacing=0.15, - panel_grid_major_y=element_line(color=INK, size=0.3, alpha=0.10), - panel_grid_major_x=element_blank(), - panel_grid_minor=element_blank(), - plot_margin=0.02, - ) -) - -# Save -plot.save(f"plot-{THEME}.png", dpi=300) diff --git a/plots/bar-race-animated/implementations/python/pygal.py b/plots/bar-race-animated/implementations/python/pygal.py deleted file mode 100644 index f026744148..0000000000 --- a/plots/bar-race-animated/implementations/python/pygal.py +++ /dev/null @@ -1,180 +0,0 @@ -""" anyplot.ai -bar-race-animated: Animated Bar Chart Race -Library: pygal 3.1.0 | Python 3.13.13 -Quality: 80/100 | Updated: 2026-05-19 -""" - -import os -import sys -from io import BytesIO - - -# Remove the script's own directory from sys.path so "pygal.py" doesn't shadow the installed package. -sys.path.pop(0) - -import cairosvg -import pygal -from PIL import Image, ImageDraw, ImageFont -from pygal.style import Style - - -# Theme tokens -THEME = os.getenv("ANYPLOT_THEME", "light") -PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" -INK = "#1A1A17" if THEME == "light" else "#F0EFE8" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -# Okabe-Ito palette — consistent entity colors across all frames -IMPRINT = ("#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477") - -# Data — Country GDP rankings (approximate, trillion USD) -countries = ["USA", "China", "Japan", "Germany", "UK", "India", "France"] -years = [2000, 2005, 2010, 2015, 2020, 2023] - -gdp_data = { - "USA": [10.2, 13.0, 14.9, 18.2, 20.9, 26.9], - "China": [1.2, 2.3, 6.1, 11.1, 14.7, 17.5], - "Japan": [4.9, 4.8, 5.7, 4.4, 5.0, 4.2], - "Germany": [1.9, 2.9, 3.4, 3.4, 3.9, 4.5], - "UK": [1.6, 2.5, 2.4, 2.9, 2.7, 3.1], - "India": [0.5, 0.8, 1.7, 2.1, 2.7, 3.7], - "France": [1.4, 2.2, 2.7, 2.4, 2.6, 2.9], -} - -country_colors = {country: IMPRINT[i] for i, country in enumerate(countries)} - -# Create individual pygal charts for each snapshot year -charts = [] -for year_idx, year in enumerate(years): - year_data = sorted([(c, gdp_data[c][year_idx]) for c in countries], key=lambda x: x[1], reverse=True) - - snap_style = Style( - background=PAGE_BG, - plot_background=PAGE_BG, - foreground=INK, - foreground_strong=INK, - foreground_subtle=INK_MUTED, - title_font_size=52, - label_font_size=36, - major_label_font_size=32, - legend_font_size=32, - value_font_size=28, - ) - - chart = pygal.HorizontalBar( - width=1500, - height=950, - style=snap_style, - show_legend=False, - title=str(year), - x_title="GDP (Trillion USD)", - print_values=True, - print_values_position="middle", - value_formatter=lambda x: f"${x:.1f}T" if x is not None else "", - margin=40, - spacing=12, - truncate_label=-1, - show_x_labels=True, - x_label_rotation=0, - show_minor_x_labels=False, - ) - - chart.x_labels = [c for c, _ in year_data] - num_countries = len(year_data) - for idx, (country, value) in enumerate(year_data): - slot = [None] * num_countries - slot[idx] = {"value": value, "color": country_colors[country]} - chart.add(country, slot) - - charts.append(chart) - -# Render each chart to PNG -chart_images = [] -for chart in charts: - svg_data = chart.render() - png_data = cairosvg.svg2png(bytestring=svg_data, output_width=1500, output_height=950) - img = Image.open(BytesIO(png_data)) - chart_images.append(img) - -# Composite 3×2 grid at 4800 × 2700 -grid_width = 4800 -grid_height = 2700 -title_height = 160 -legend_height = 120 -content_height = grid_height - title_height - legend_height -cell_width = grid_width // 3 -cell_height = content_height // 2 - -combined = Image.new("RGB", (grid_width, grid_height), PAGE_BG) -draw = ImageDraw.Draw(combined) - -try: - title_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 72) - legend_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 36) -except OSError: - title_font = ImageFont.load_default() - legend_font = ImageFont.load_default() - -title_text = "GDP Country Rankings · bar-race-animated · python · pygal · anyplot.ai" -bbox = draw.textbbox((0, 0), title_text, font=title_font) -title_width = bbox[2] - bbox[0] -draw.text(((grid_width - title_width) // 2, 40), title_text, fill=INK, font=title_font) - -positions = [(0, 0), (1, 0), (2, 0), (0, 1), (1, 1), (2, 1)] -for idx, (col, row) in enumerate(positions): - if idx < len(chart_images): - img = chart_images[idx].resize((cell_width, cell_height), Image.Resampling.LANCZOS) - x = col * cell_width - y = title_height + row * cell_height - combined.paste(img, (x, y)) - -# Legend bar at bottom -legend_y = grid_height - legend_height + 25 -box_size = 40 -spacing_between = grid_width // len(countries) -legend_x_start = spacing_between // 2 - 80 - -for i, country in enumerate(countries): - x_pos = legend_x_start + i * spacing_between - draw.rectangle([x_pos, legend_y, x_pos + box_size, legend_y + box_size], fill=country_colors[country]) - draw.text((x_pos + box_size + 12, legend_y - 2), country, fill=INK, font=legend_font) - -combined.save(f"plot-{THEME}.png", dpi=(300, 300)) - -# HTML: interactive 2023 snapshot with pygal tooltips -html_style = Style( - background=PAGE_BG, - plot_background=PAGE_BG, - foreground=INK, - foreground_strong=INK, - foreground_subtle=INK_MUTED, - colors=IMPRINT, - title_font_size=28, - label_font_size=18, - major_label_font_size=16, - legend_font_size=16, - value_font_size=14, -) - -html_chart = pygal.HorizontalBar( - width=1200, - height=800, - style=html_style, - show_legend=True, - title="GDP Country Rankings 2023 · bar-race-animated · python · pygal · anyplot.ai", - x_title="GDP (Trillion USD)", - print_values=True, - value_formatter=lambda x: f"${x:.1f}T" if x is not None else "", -) - -final_data = sorted([(c, gdp_data[c][-1]) for c in countries], key=lambda x: x[1], reverse=True) - -html_chart.x_labels = [c for c, _ in final_data] -num_final = len(final_data) -for idx, (country, value) in enumerate(final_data): - slot = [None] * num_final - slot[idx] = {"value": value, "color": country_colors[country]} - html_chart.add(country, slot) - -with open(f"plot-{THEME}.html", "wb") as f: - f.write(html_chart.render()) diff --git a/plots/bar-race-animated/implementations/python/seaborn.py b/plots/bar-race-animated/implementations/python/seaborn.py deleted file mode 100644 index 9a96c68c66..0000000000 --- a/plots/bar-race-animated/implementations/python/seaborn.py +++ /dev/null @@ -1,152 +0,0 @@ -""" anyplot.ai -bar-race-animated: Animated Bar Chart Race -Library: seaborn 0.13.2 | Python 3.13.13 -Quality: 86/100 | Created: 2026-05-19 -""" - -import os -import sys - - -sys.path.pop(0) - -import matplotlib.pyplot as plt -import pandas as pd -import seaborn as sns - - -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" - -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD"] - -sns.set_theme( - style="ticks", - rc={ - "figure.facecolor": PAGE_BG, - "axes.facecolor": PAGE_BG, - "axes.edgecolor": INK_SOFT, - "axes.labelcolor": INK, - "text.color": INK, - "xtick.color": INK_SOFT, - "ytick.color": INK_SOFT, - "grid.color": INK, - "grid.alpha": 0.10, - "legend.facecolor": ELEVATED_BG, - "legend.edgecolor": INK_SOFT, - }, -) - -# Synthetic market cap ($ billions) with sector rotations, corrections, and breakouts -companies = ["Alpha Corp", "Beta Tech", "Gamma Systems", "Delta Networks", "Epsilon Data", "Zeta Cloud"] -years = [2014, 2016, 2018, 2020, 2022, 2024] - -market_cap_data = { - "Alpha Corp": [520, 610, 720, 680, 750, 820], # correction in 2020 - "Beta Tech": [380, 430, 510, 600, 820, 1100], # breakout surge to #1 by 2022 - "Gamma Systems": [290, 370, 460, 530, 490, 620], # peak then dip in 2022 - "Delta Networks": [210, 280, 390, 550, 700, 880], # consistent riser - "Epsilon Data": [160, 250, 420, 380, 540, 670], # volatile: rises, dips, recovers - "Zeta Cloud": [120, 180, 260, 310, 410, 580], # steady late-stage grower -} - -color_map = {company: IMPRINT[i] for i, company in enumerate(companies)} - -rows = [ - {"company": company, "year": year, "market_cap": value} - for company, values in market_cap_data.items() - for year, value in zip(years, values, strict=True) -] -df = pd.DataFrame(rows) -df["rank"] = df.groupby("year")["market_cap"].rank(ascending=False, method="first").astype(int) - -fig, axes = plt.subplots(2, 3, figsize=(16, 9), facecolor=PAGE_BG) -axes = axes.flatten() - -prev_rank = None - -for idx, year in enumerate(years): - ax = axes[idx] - ax.set_facecolor(PAGE_BG) - - year_data = df[df["year"] == year].sort_values("market_cap", ascending=True).reset_index(drop=True) - order = list(year_data["company"]) - curr_rank = dict(zip(year_data["company"], year_data["rank"], strict=True)) - - # Rank change: positive = moved up in ranking - rank_delta = {} - if prev_rank is not None: - for co in companies: - rank_delta[co] = prev_rank.get(co, 6) - curr_rank.get(co, 6) - - # Use sns.barplot — the idiomatic seaborn horizontal bar function - sns.barplot( - data=year_data, - x="market_cap", - y="company", - hue="company", - palette=color_map, - order=order, - dodge=False, - legend=False, - ax=ax, - ) - - max_val = year_data["market_cap"].max() - ax.set_xlim(0, max_val * 1.45) - - # Highlight rank movers: thicker colored edge for companies up ≥2 positions - bar_patches = sorted([p for p in ax.patches if p.get_width() > 0], key=lambda p: p.get_y()) - for patch, company in zip(bar_patches, order, strict=True): - delta = rank_delta.get(company, 0) - if delta >= 2: - patch.set_edgecolor(color_map[company]) - patch.set_linewidth(2.5) - else: - patch.set_edgecolor(PAGE_BG) - patch.set_linewidth(0.5) - - # Value labels with rank-change badge on movers - for patch, company in zip(bar_patches, order, strict=True): - val = year_data.loc[year_data["company"] == company, "market_cap"].values[0] - delta = rank_delta.get(company, 0) - badge = f" ▲{delta}" if delta >= 2 else (f" ▼{-delta}" if delta <= -2 else "") - label_color = color_map[company] if abs(delta) >= 2 else INK_SOFT - ax.text( - patch.get_width() + max_val * 0.012, - patch.get_y() + patch.get_height() / 2, - f"${val}B{badge}", - va="center", - ha="left", - fontsize=14, - color=label_color, - fontweight="bold" if abs(delta) >= 2 else "normal", - ) - - ax.set_title(str(year), fontsize=20, fontweight="bold", color=INK, pad=6) - ax.tick_params(axis="x", labelsize=16, colors=INK_SOFT) - ax.tick_params(axis="y", labelsize=16, colors=INK_SOFT) - ax.spines["top"].set_visible(False) - ax.spines["right"].set_visible(False) - ax.spines["left"].set_color(INK_SOFT) - ax.spines["bottom"].set_color(INK_SOFT) - - ax.xaxis.grid(True, alpha=0.10, linewidth=0.8, color=INK) - ax.set_xlabel("Market Cap ($ Billion)", fontsize=20, color=INK_SOFT) - ax.set_ylabel("") - - prev_rank = curr_rank - -fig.suptitle( - "Tech Company Rankings · bar-race-animated · python · seaborn · anyplot.ai", - fontsize=24, - fontweight="medium", - color=INK, - y=0.99, -) - -plt.tight_layout(rect=[0, 0, 1, 0.96]) -plt.savefig(f"plot-{THEME}.png", dpi=300, bbox_inches="tight", facecolor=PAGE_BG) diff --git a/plots/bar-race-animated/implementations/r/ggplot2.R b/plots/bar-race-animated/implementations/r/ggplot2.R deleted file mode 100644 index 024608604e..0000000000 --- a/plots/bar-race-animated/implementations/r/ggplot2.R +++ /dev/null @@ -1,98 +0,0 @@ -#' anyplot.ai -#' bar-race-animated: Animated Bar Chart Race -#' Library: ggplot2 3.5.1 | R 4.4.1 -#' Quality: 87/100 | Created: 2026-05-19 - -library(ggplot2) -library(dplyr) -library(scales) -library(ragg) -library(gapminder) - -set.seed(42) - -# Theme tokens -THEME <- Sys.getenv("ANYPLOT_THEME", "light") -PAGE_BG <- if (THEME == "light") "#FAF8F1" else "#1A1A17" -ELEVATED_BG <- if (THEME == "light") "#FFFDF6" else "#242420" -INK <- if (THEME == "light") "#1A1A17" else "#F0EFE8" -INK_SOFT <- if (THEME == "light") "#4A4A44" else "#B8B7B0" -IMPRINT <- c("#009E73", "#C475FD", "#4467A3", "#BD8233", - "#AE3030", "#2ABCCD", "#954477") - -# Data — top 10 countries by GDP per capita at 6 key snapshots -years_snap <- c(1952, 1967, 1977, 1987, 1997, 2007) - -df_snap <- gapminder::gapminder |> - filter(year %in% years_snap) |> - group_by(year) |> - slice_max(gdpPercap, n = 10, with_ties = FALSE) |> - ungroup() |> - mutate( - cntry_yr = paste0(country, "___", year), - cntry_yr = reorder(cntry_yr, gdpPercap) - ) - -continent_colors <- setNames( - IMPRINT[1:5], - c("Africa", "Americas", "Asia", "Europe", "Oceania") -) - -# Plot — small multiples replacing animation; each facet is one time snapshot -p <- ggplot(df_snap, aes(x = cntry_yr, y = gdpPercap, fill = continent)) + - geom_col(width = 0.8, alpha = 0.9) + - geom_text( - aes(label = label_dollar(scale = 1e-3, suffix = "K", accuracy = 1)(gdpPercap)), - hjust = -0.1, - color = INK_SOFT, - size = 4.5 - ) + - coord_flip() + - scale_x_discrete(labels = function(x) sub("___.*", "", x)) + - scale_y_continuous( - labels = label_dollar(scale = 1e-3, suffix = "K"), - expand = expansion(mult = c(0, 0.28)) - ) + - scale_fill_manual(values = continent_colors, name = "Continent") + - facet_wrap(~year, scales = "free", nrow = 2) + - labs( - title = "GDP per Capita Rankings · bar-race-animated · r · ggplot2 · anyplot.ai", - subtitle = "Kuwait leads in 1952 on oil wealth; European economies — especially Norway — rise to the top by 2007", - x = NULL, - y = "GDP per Capita (USD)" - ) + - theme_minimal(base_size = 14) + - theme( - plot.background = element_rect(fill = PAGE_BG, color = PAGE_BG), - panel.background = element_rect(fill = PAGE_BG, color = NA), - panel.grid.major.y = element_blank(), - panel.grid.major.x = element_line(color = INK_SOFT, linewidth = 0.25), - panel.grid.minor = element_blank(), - axis.title.x = element_text(color = INK, size = 20), - axis.title.y = element_blank(), - axis.text.x = element_text(color = INK_SOFT, size = 16), - axis.text.y = element_text(color = INK, size = 16), - plot.title = element_text(color = INK, size = 24, face = "bold", - margin = margin(b = 5)), - plot.subtitle = element_text(color = INK_SOFT, size = 18, - margin = margin(b = 15)), - legend.background = element_rect(fill = ELEVATED_BG, color = INK_SOFT, - linewidth = 0.3), - legend.text = element_text(color = INK_SOFT, size = 16), - legend.title = element_text(color = INK, size = 18), - legend.position = "bottom", - strip.text = element_text(color = INK, size = 18, face = "bold"), - strip.background = element_rect(fill = ELEVATED_BG, color = NA), - plot.margin = margin(15, 15, 10, 15) - ) - -# Save -ggsave( - filename = sprintf("plot-%s.png", THEME), - plot = p, - device = ragg::agg_png, - width = 16, - height = 9, - units = "in", - dpi = 300 -) diff --git a/plots/bar-race-animated/metadata/python/altair.yaml b/plots/bar-race-animated/metadata/python/altair.yaml deleted file mode 100644 index 87b6e3325e..0000000000 --- a/plots/bar-race-animated/metadata/python/altair.yaml +++ /dev/null @@ -1,268 +0,0 @@ -library: altair -language: python -specification_id: bar-race-animated -created: '2026-01-11T00:22:58Z' -updated: '2026-05-19T02:18:21Z' -generated_by: claude-sonnet -workflow_run: 26071184942 -issue: 3653 -language_version: 3.13.13 -library_version: 6.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/altair/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/altair/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/altair/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/altair/plot-dark.html -quality_score: 89 -review: - strengths: - - Clean small-multiples design with four key-year facets that clearly shows ranking - progression over time - - Triple-layer Altair composition (bars + value labels + platform name labels) adds - data richness without clutter - - 'Correct Okabe-Ito palette with #009E73 as first series, plus accessible extensions - for 10 entities' - - 'Full theme-adaptive chrome: backgrounds, text, grid, and legend all correctly - flip between light (#FAF8F1) and dark (#1A1A17)' - - Independent x-axis scales per facet (resolve_scale) let each year use its own - range — avoids compressing 2015 data against 2024 max - - Rounded bar corners (cornerRadiusEnd=6) and hidden Y-axis give a polished, modern - look - - Code is clean KISS structure with np.random.seed(42) for reproducibility and all - explicitly-set font sizes - weaknesses: - - 'DE-01 (5/8): Lacks a stand-out design flourish — e.g., a subtle gradient on the - leading bar, a callout annotation on the rank-1 crossover year, or a bold highlight - on the platform that overtook the leader' - - 'DE-03 (3/6): The rank-change story (PlayNow rising from #3 to #1) is visible - but not emphasised; consider a rank-delta annotation or bolder colour contrast - on the biggest mover to guide the viewer''s eye' - - 'DE-02 (4/6): Grid opacity 0.25 (encoding-level) overrides the configure_axis - value of 0.20; unifying to 0.15 would produce a subtler grid closer to the style-guide - target' - - 'LM-02 (3/5): Altair''s concat/resolve and selection-driven animation (via params) - are not used; even a simple step-slider param could demonstrate a more Altair-distinctive - capability' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white consistent with #FAF8F1 — not pure white, correct surface tone. - Chrome: Title "bar-race-animated · python · altair · anyplot.ai" clearly readable in dark ink at top centre. Subtitle "Streaming Platform Subscribers Over Time (millions)" visible below in slightly lighter ink. Year column headers (2015, 2018, 2021, 2024) rendered at 26px — sharp and readable. X-axis tick labels and "Subscribers (millions)" titles are legible at 16/20px respectively. Legend on the right shows all 10 platforms with coloured symbols; label and title text is readable. - Data: Bars use Okabe-Ito starting with #009E73 (StreamFlix). Each bar has a white value label (subscriber count) inside and a bold dark platform-name label just beyond the bar end. Rounded bar corners add polish. Independent x-scales correctly widen each facet to its year's data range. Rank ordering descends top-to-bottom per year. - Legibility verdict: PASS — all text elements are readable against the warm off-white background; no light-on-light issues detected. - - Dark render (plot-dark.png): - Background: Near-black consistent with #1A1A17 — not pure black, correct dark surface. - Chrome: Title, subtitle, and year headers rendered in light text (#F0EFE8 / INK token) — clearly readable against the dark surface. Axis tick labels and axis titles appear in the lighter INK_SOFT token (#B8B7B0). Legend label and title text is light-coloured and readable. No dark-on-dark failures detected. - Data: Bar colours are identical to the light render — Okabe-Ito palette unchanged across themes (only chrome flips). White value labels inside bars remain readable on coloured bars. Platform name labels outside bars use the INK token which correctly resolves to the light value in dark mode. - Legibility verdict: PASS — all text readable against the dark background; data colours are theme-consistent with the light render. - criteria_checklist: - visual_quality: - score: 29 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: Most sizes explicitly set (title 34px, subtitle 22px, year headers - 26px, axis title 20px, axis labels 16px, legend 16-18px). text_labels layer - at 14px is marginally below the 16px minimum but readable at scale_factor=3.0 - output. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: Clean faceted layout with no overlapping text; bar labels and value - labels are well separated. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Bars at height=45 are clearly visible; cornerRadiusEnd adds definition; - colours well differentiated. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette is CVD-safe; adequate contrast between bar colours - and backgrounds. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Four-panel facet fills canvas well; legend placed to the right; balanced - margins. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: X-axis titled 'Subscribers (millions)' with units; Y-axis omitted - intentionally (ranks are labelled on bars). - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series StreamFlix = #009E73; multi-series follows Okabe-Ito - order with accessible extensions for positions 8-10. Backgrounds #FAF8F1 - / #1A1A17. Chrome fully theme-adaptive in both renders.' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Clearly above defaults: rounded corners, multi-layer labels, clean - faceting. Falls short of strong-design (6) — no standout emphasis or focal-point - technique.' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Hidden Y-axis, no view borders, subtle grid, facet spacing, legend - padding/cornerRadius are all good touches. Grid opacity mismatch (0.25 encoding - vs 0.20 configure) is a minor inconsistency. - - id: DE-03 - name: Data Storytelling - score: 3 - max: 6 - passed: true - comment: Time-progression story is present (4 key years, rank ordering). Rank-change - narrative (PlayNow rise) is visible but not visually emphasised — no colour - highlight, delta annotation, or focal-point technique. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct small-multiples implementation for a library without native - animation support, as explicitly permitted by spec. - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Bars sorted by value at each snapshot; entity labels on bars; visible - time indicator (year headers); colour consistent per entity across frames. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X = Subscribers (value driving bar length and ranking), Y = Rank - (ordinal, sorted descending). All 10 platforms shown across 4 years. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title: ''bar-race-animated · python · altair · anyplot.ai'' — exact - format. Subtitle descriptive. Legend labels match all 10 platforms.' - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'Shows all aspects: rank ordering, value-driven bar length, time - progression, colour coding, diverse growth trajectories across 10 entities.' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Streaming platform subscribers (millions) — realistic, neutral, business - domain. Fictional platform names are plausible. No sensitive or political - content. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: 2015 base values 5-40M plausible for early streaming era; 2024 range - 10-200M reflects realistic decade-long growth with differentiated rates. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear flow: imports → tokens → data → chart layers → facet → save. - No functions or classes.' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set before synthetic data generation. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: os, altair, numpy, pandas — all used. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean, Pythonic Altair composition. No over-engineering. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png (scale_factor=3.0) and plot-{THEME}.html. - Uses current Altair 6.1.0 API. - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: 'Expert use of Altair high-level API: mark_bar, encoding types (Q/N/O), - layer composition (+), facet(), configure_* chain, resolve_scale.' - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: 'Uses Altair-distinctive features: facet(column=...) for small multiples, - layer composition (bar + text + value), resolve_scale(x=''independent''). - No use of params/selection for interactivity which would further differentiate.' - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - faceting - - layer-composition - - hover-tooltips - - html-export - patterns: - - data-generation - - groupby-aggregation - dataprep: [] - styling: - - alpha-blending diff --git a/plots/bar-race-animated/metadata/python/bokeh.yaml b/plots/bar-race-animated/metadata/python/bokeh.yaml deleted file mode 100644 index a04db844fc..0000000000 --- a/plots/bar-race-animated/metadata/python/bokeh.yaml +++ /dev/null @@ -1,257 +0,0 @@ -library: bokeh -language: python -specification_id: bar-race-animated -created: '2026-01-11T00:55:51Z' -updated: '2026-05-19T02:14:44Z' -generated_by: claude-sonnet -workflow_run: 26071113117 -issue: 3653 -language_version: 3.13.13 -library_version: 3.9.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/bokeh/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/bokeh/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/bokeh/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/bokeh/plot-dark.html -quality_score: 87 -review: - strengths: - - 'Perfect data quality: realistic streaming domain with full coverage of rank changes - and sensible growth trajectories' - - Consistent Okabe-Ito entity colors across all four panels make competitive movement - immediately trackable - - Clean typography hierarchy with bold year headers, white bar labels, and themed - value labels - - Flawless theme adaptation — both light and dark renders fully readable with no - dark-on-dark issues - - Idiomatic Bokeh code with ColumnDataSource, HoverTool, and gridplot - weaknesses: - - Very short bars (CineCloud, WatchNow in 2016) have platform labels overflowing - the bar boundary — clamp label x-position or render outside in INK_SOFT for bars - below a width threshold - - Bokeh HTML artifact could include a Slider + CustomJS animation to deliver the - actual bar race experience instead of only a static screenshot - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct theme surface - Chrome: Title "bar-race-animated · python · bokeh · anyplot.ai" in bold dark ink at top, readable. Year headers (2016, 2018, 2021, 2023) bold dark ink above each panel, readable. X-axis labels "Subscribers (millions)" in dark ink on bottom panels, readable. Tick labels in INK_SOFT (#4A4A44), readable. - Data: Six platforms colored with full Okabe-Ito palette — StreamMax #009E73 (green, first entity), ViewHub #D55E00 (orange), FlixNet #0072B2 (blue), WatchNow #CC79A7 (pink), CineCloud #E69F00 (yellow-orange), MediaFlow #56B4E9 (sky blue). White platform labels inside bars, INK_SOFT value labels outside bars. Legend strip at bottom. - Legibility verdict: PASS — all text readable; minor: very short bars in 2016 cause label overflow beyond bar boundary but remain legible - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct theme surface - Chrome: Title visible in light ink (#F0EFE8). Year headers visible in light ink. Axis labels and tick labels visible in light/soft ink (#F0EFE8 / #B8B7B0). Subtle x-grid as faint light lines (INK alpha=0.08). Legend text in #B8B7B0, readable. - Data: All six Okabe-Ito colors identical to light render — StreamMax still #009E73, ViewHub #D55E00, FlixNet #0072B2, WatchNow #CC79A7, CineCloud #E69F00, MediaFlow #56B4E9. White platform labels inside colored bars remain legible on dark surface. - Legibility verdict: PASS — no dark-on-dark failures detected; all chrome correctly flipped to light tokens - criteria_checklist: - visual_quality: - score: 28 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: 'All sizes explicitly set: title 32pt, year headers 36pt, axis labels - 22pt, tick labels 18pt, bar labels 18pt bold, value labels 16pt' - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: Minor overflow for very short bars in 2016 (CineCloud 28M, WatchNow - 43M) — platform labels extend beyond bar boundary into empty space; value - labels unobstructed - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Bars at height=0.65 with alpha=0.9 and page-color borders are crisp - and clearly distinguishable - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Full Okabe-Ito palette, CVD-safe. White text on colored bars provides - strong contrast - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: 2x2 grid fills canvas well; minor unused vertical space (~225px) - at bottom of 4800x2700 frame - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Subscribers (millions) with units on bottom row panels; title in - correct format - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'StreamMax first entity uses #009E73; subsequent platforms follow - Okabe-Ito order; backgrounds #FAF8F1/#1A1A17; all chrome theme-adaptive' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Above baseline defaults: platform labels inside bars, value labels - at bar ends, clean year headers, consistent entity colors enabling cross-panel - comparison' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Y-axis hidden, subtle x-grid alpha=0.08, no outline, line_color=PAGE_BG - bar borders for definition - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: '4-panel progression tells clear competitive story: FlixNet leads - early, StreamMax overtakes by 2021, CineCloud surges to #1 by 2023. Consistent - colors make rank changes readable' - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 4 - max: 5 - passed: true - comment: Small multiples is valid static fallback per spec. Bokeh's JS-driven - Slider+CustomJS animation could have been used in HTML artifact for the - full bar race experience - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Bars sorted by value in each panel, entity labels on bars, year header - time indicator, consistent entity colors across panels - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Horizontal bars correctly map subscriber count to bar length; y-positions - represent sorted rank - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title exactly matches required format; bottom legend strip shows - all 6 platforms with correct Okabe-Ito colors - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Covers ranking changes, trajectory crossings, dramatic late-stage - surge (CineCloud 28M to 696M), and gradual relative decline of early leaders - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: 'Streaming platform subscriber counts: neutral, comprehensible, business-relevant - scenario with realistic fictional names' - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Values range 28M (2016) to 696M (2023). Growth rates 8-55%/yr plausible - for streaming era. No impossible proportions - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear: imports -> tokens -> data generation -> plot loop -> layout - -> save. No functions or classes' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports are used; no dead imports - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean Pythonic code. zip(..., strict=True) is elegant. No fake UI - elements - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.html and plot-{THEME}.png using current Bokeh - 3.x API + Selenium screenshot pattern - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Correctly uses ColumnDataSource, HoverTool, gridplot, Range1d, Title - model, and add_layout — idiomatic Bokeh patterns throughout - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: HTML output with interactive HoverTool tooltips, gridplot for multi-panel - composition, headless-Chrome screenshot. Does not use Bokeh's most distinctive - capability for this type (CustomJS/Slider animation) - verdict: APPROVED -impl_tags: - dependencies: - - selenium - techniques: - - hover-tooltips - - html-export - - subplots - patterns: - - data-generation - - columndatasource - - iteration-over-groups - dataprep: [] - styling: - - alpha-blending - - minimal-chrome - - edge-highlighting diff --git a/plots/bar-race-animated/metadata/python/letsplot.yaml b/plots/bar-race-animated/metadata/python/letsplot.yaml deleted file mode 100644 index bb9adc0aed..0000000000 --- a/plots/bar-race-animated/metadata/python/letsplot.yaml +++ /dev/null @@ -1,247 +0,0 @@ -library: letsplot -language: python -specification_id: bar-race-animated -created: '2026-01-11T00:21:50Z' -updated: '2026-05-19T02:40:51Z' -generated_by: claude-sonnet -workflow_run: 26071447018 -issue: 3653 -language_version: 3.13.13 -library_version: 4.9.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/letsplot/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/letsplot/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/letsplot/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/letsplot/plot-dark.html -quality_score: 86 -review: - strengths: - - Correct small-multiples approach for a static library; spec explicitly permits - this and the implementation executes it cleanly - - 'Okabe-Ito palette perfectly applied: StreamMax mapped to #009E73 first, remaining - entries follow canonical order with consistent entity coloring across all panels' - - 'Full theme-adaptive chrome in both renders: correct backgrounds, INK/INK_SOFT - tokens threaded through all text elements' - - np.random.seed(42) ensures reproducibility; plausible streaming data with meaningful - ranking reversals - - gggrid() used idiomatically with proper per-subplot pd.Categorical ordering to - maintain sorted bars - weaknesses: - - No subtle X-axis grid lines on bar charts — readers cannot easily compare bar - lengths across panels without reference lines; adding panel_grid_major_x would - help - - gggrid panel borders render as bright white outlines in dark mode; consider panel_border=element_blank() - or styling to match the dark surface - - No value labels on bars (geom_text) — would enhance readability of exact subscriber - counts without needing grid lines - - 'Design storytelling is minimal: no visual emphasis on the leading bar per panel, - no annotations marking key competitive moments' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct - Chrome: Main title "bar-race-animated · python · letsplot · anyplot.ai" bold dark text; year labels (2016/2018/2021/2023) bold dark size-28; X-axis label "Subscribers (millions)" visible; Y-axis platform names readable - Data: StreamMax=#009E73 (green), ViewHub=#D55E00 (orange), FlixNet=#0072B2 (blue), WatchNow=#CC79A7 (pink), CineCloud=#E69F00 (gold), MediaFlow=#56B4E9 (sky blue) — full Okabe-Ito order; first entity correctly green - Legibility verdict: PASS - - Dark render (plot-dark.png): - Background: Near-black #1A1A17 — correct - Chrome: Title and year labels rendered in light #F0EFE8; tick labels in muted #B8B7B0; all text readable against dark surface. Minor: gggrid panel borders appear as slightly bright white outlines around each subplot panel but do not obscure data - Data: Colors visually identical to light render — all six Okabe-Ito bars maintain their identity; no color shift between themes - Legibility verdict: PASS - criteria_checklist: - visual_quality: - score: 29 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: Title and year labels bold and well-sized; axis tick labels at size - 16 slightly compressed within 2x2 panel layout - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No text or element collisions across any of the four panels - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: All bars and labels clearly visible in both themes - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette used throughout; CVD-safe - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 2x2 grid at 1600x900 scaled 3x = 4800x2700; nothing clipped - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Subscribers (millions) on X-axis; year as subplot title; required - title format correct - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'StreamMax correctly maps to #009E73; full Okabe-Ito order; backgrounds - #FAF8F1/#1A1A17 correct' - design_excellence: - score: 11 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Clean Okabe-Ito palette, proper theme adaptation, bold year labels - create focal points. Functional and professional but no standout design - choices - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: false - comment: Grid completely removed and legend omitted. For a horizontal bar - chart, subtle X-axis grid lines would help compare bar lengths. Panel borders - slightly intrusive in dark mode - - id: DE-03 - name: Data Storytelling - score: 3 - max: 6 - passed: false - comment: Temporal sequence tells the streaming race story. Rankings shift - visibly. But no annotations, no emphasis on winner per panel, no visual - cues guiding reader - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Horizontal bar chart race implemented as small multiples (explicitly - sanctioned by spec for static libraries) - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Bars sorted by value per panel; entity labels on Y-axis; year as - time indicator; consistent entity colors across all panels - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X = subscribers (value), Y = platform (entity); all data ranges displayed - correctly - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title matches required format; no legend needed with platform names - on Y-axis - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: Six platforms across four meaningful time points with ranking changes - visible. Slightly reduced for only 4 snapshots from 8 years of data - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Streaming platform subscribers in millions is neutral and real-world-plausible - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Values range ~15M to ~190M with realistic growth trajectories and - noise - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: No functions or classes; flat procedural script - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only os, numpy, pandas, lets_plot imported; all used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean iteration; pd.Categorical used correctly for axis ordering - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png (scale=3) and plot-{THEME}.html - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Correct ggplot grammar; gggrid() for multi-panel, coord_flip(), scale_fill_manual(), - proper theme layering - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: gggrid() is distinctive to letsplot; HTML export generated; could - further leverage geom_text for value labels - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - subplots - - html-export - patterns: - - data-generation - - iteration-over-groups - dataprep: [] - styling: - - alpha-blending diff --git a/plots/bar-race-animated/metadata/python/matplotlib.yaml b/plots/bar-race-animated/metadata/python/matplotlib.yaml deleted file mode 100644 index 504c62f151..0000000000 --- a/plots/bar-race-animated/metadata/python/matplotlib.yaml +++ /dev/null @@ -1,257 +0,0 @@ -library: matplotlib -language: python -specification_id: bar-race-animated -created: '2026-05-19T01:52:40Z' -updated: '2026-05-19T02:23:59Z' -generated_by: claude-sonnet -workflow_run: 26071101066 -issue: 3653 -language_version: 3.13.13 -library_version: 3.10.9 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/matplotlib/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/matplotlib/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 85 -review: - strengths: - - 'Excellent Nvidia storytelling: sparkline inset, arrow annotation with ×23 multiplier, - and path-effect value labels create a clear focal point across all 6 panels' - - Correct use of small multiples as the approved static alternative to animation, - covering all 6 time snapshots (2019–2024) - - Strong matplotlib feature usage — path_effects for text stroke, inset_axes for - the embedded sparkline, GridSpec for precise panel layout - - 'Perfect theme implementation: #FAF8F1/#1A1A17 backgrounds, all chrome tokens - (INK/INK_SOFT/INK_MUTED) applied correctly, data colors identical across both - renders' - - Clean code structure with proper Okabe-Ito palette assignment, all spec-required - static features present (sorted bars, time indicator, consistent entity colors) - weaknesses: - - Value labels are fontsize=13 and x-axis 'Market Cap ($B)' labels are fontsize=14 - — both below the recommended minimums (16pt for tick/value labels, 20pt for axis - labels) for the 4800×2700px canvas; at full resolution these will be noticeably - small - - No grid lines present — even subtle y-axis grid lines (alpha=0.10) would aid bar-length - comparison across the sorted rankings in each panel - - figsize=(20, 11) deviates from the standard landscape canvas (16×9 → 4800×2700px); - while close in pixel count the non-standard size may affect rendering consistency - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 across all 6 panels — correct. - Chrome: Suptitle "Tech Giant Market Cap · bar-race-animated · python · matplotlib · anyplot.ai" at 24pt in dark ink, clearly readable. Panel year titles (2019–2024) at 20pt bold dark ink, clearly readable. Y-axis company name labels at 16pt in INK_SOFT (#4A4A44), readable. X-axis "Market Cap ($B)" labels at 14pt in INK_MUTED — present but slightly small for the canvas. Value labels at 13pt — legible at display size but below guide recommendations. - Data: 7 companies colored in Okabe-Ito order by alphabetical assignment — Alphabet=#009E73 (green), Amazon=#D55E00 (vermillion), Apple=#0072B2 (blue), Meta=#CC79A7 (pink), Microsoft=#E69F00 (orange), Nvidia=#56B4E9 (sky blue), Tesla=#F0E442 (yellow). First Okabe-Ito color (#009E73) correctly assigned. Bars sorted descending by value (highest at top) in each panel. 2024 panel has arrow annotation "×23 since 2019" pointing to Nvidia's bar and a sparkline inset showing Nvidia's 2019→2024 trajectory. - Legibility verdict: PASS — all text readable against the warm off-white background. - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 across all 6 panels — correct. - Chrome: Suptitle, panel year titles, company labels, and axis labels all rendered in light-ink adaptive tokens (INK=#F0EFE8, INK_SOFT=#B8B7B0, INK_MUTED=#A8A79F) — clearly visible against the dark background. No dark-on-dark failures detected. The annotation box uses ELEVATED_BG (#242420) with appropriate light text. Sparkline uses INK_MUTED for x-tick labels. - Data: Bar colors are identical to the light render (Okabe-Ito positions 1–7 unchanged). Nvidia sky-blue bars and value labels maintain the same brand color styling. The sparkline fill_between area (alpha=0.15) is subtle but visible. - Legibility verdict: PASS — all text readable against the warm near-black background, no dark-on-dark issues. - criteria_checklist: - visual_quality: - score: 24 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 5 - max: 8 - passed: true - comment: 'Suptitle 24pt and panel titles 20pt meet guidelines; x-axis labels - 14pt (guide: 20pt) and value labels 13pt (guide: 16pt) are below recommended - sizes for 4800x2700px canvas. Both themes readable.' - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: Clean layout overall; minor space pressure in 2024 panel with sparkline - and annotation competing for space with value labels. - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: All bars clearly visible; Tesla yellow (#F0E442) on bars is acceptable; - Nvidia's $145B bar in 2019 is very short but labeled. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette is CVD-safe by design. - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: 6-panel 2x3 grid is clean and well-proportioned. figsize=(20,11) - slightly non-standard vs 16x9 guideline. Nothing cut off. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Market Cap ($B) on each panel; suptitle in correct format. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Alphabet gets #009E73 (first Okabe-Ito position). Backgrounds #FAF8F1 - light / #1A1A17 dark. Colors identical across both renders.' - design_excellence: - score: 14 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Above default (4): path_effects on Nvidia labels, sparkline inset, - deliberate Nvidia narrative thread, intentional color emphasis. Not at max - due to small multiples being the fallback approach.' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: 'Above default (2): top/right/bottom spines removed, tick length=0, - bar edges set to PAGE_BG for clean definition. No grid lines — a missed - opportunity for bar comparison.' - - id: DE-03 - name: Data Storytelling - score: 5 - max: 6 - passed: true - comment: 'Strong: Nvidia''s ×23 rise is the clear focal point, arrow annotation - with multiplier, sparkline showing full trajectory. The reordering across - panels communicates the race narrative effectively.' - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Small multiples grid is the correct static alternative for animated - bar chart race per spec note and library guide. - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Bars sorted by value at each frame (ascending, highest at top). Entity - labels on y-axis. Year as time indicator. Consistent entity colors across - all panels. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: 'X: market cap values. Y: companies sorted by value. All 7 entities - and 6 time points shown.' - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title: ''Tech Giant Market Cap · bar-race-animated · python · matplotlib - · anyplot.ai'' matches the required format. Y-axis labels serve as legend.' - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: 'Shows ranking changes, value changes, race dynamics across 6 time - points. Nvidia narrative adds depth. Minor deduction: only 6 discrete snapshots - limit smoothness.' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Tech company market cap data is realistic, well-known, neutral, and - approximately matches real figures. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Billions of dollars, realistic range $75B–$3,750B across the period. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: No functions or classes, linear flat structure. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Hardcoded data, fully deterministic. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: 'All 4 imports used: os, patheffects, pyplot, GridSpec.' - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: No fake UI. Clean iteration. strict=False on zip is defensive but - not harmful. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png. No deprecated APIs. - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Axes methods throughout. GridSpec for multi-panel. inset_axes for - embedded sparkline. Modern patterns. - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: matplotlib.patheffects (withStroke + Normal) for Nvidia label stroke - — specialized and distinctive. inset_axes, fill_between, annotate with arrowprops - — strong matplotlib feature set. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - annotations - - subplots - - inset-axes - - manual-ticks - patterns: - - iteration-over-groups - - explicit-figure - dataprep: [] - styling: - - alpha-blending - - edge-highlighting diff --git a/plots/bar-race-animated/metadata/python/plotly.yaml b/plots/bar-race-animated/metadata/python/plotly.yaml deleted file mode 100644 index 8b76c8059a..0000000000 --- a/plots/bar-race-animated/metadata/python/plotly.yaml +++ /dev/null @@ -1,242 +0,0 @@ -library: plotly -language: python -specification_id: bar-race-animated -created: '2026-01-11T00:19:26Z' -updated: '2026-05-19T02:20:56Z' -generated_by: claude-sonnet -workflow_run: 26071044074 -issue: 3653 -language_version: 3.13.13 -library_version: 6.7.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/plotly/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/plotly/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/plotly/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/plotly/plot-dark.html -quality_score: 88 -review: - strengths: - - 'Full Plotly animation API used correctly: go.Frame array, updatemenus play/pause, - sliders timeline scrubber — distinctly Plotly features that cannot be replicated - in static libraries' - - 'Perfect spec compliance: sorted bars per frame, consistent entity colors, visible - year indicator via slider, configurable animation speed (600ms frame, 300ms transition)' - - Both renders theme-adapt correctly; GRID, INK, INK_SOFT, PAGE_BG, and ELEVATED_BG - tokens all wired through to every relevant layout element in both the animated - and PNG figures - - Realistic GDP data with accurate proportions and genuine rank changes (China's - rise from last to 2nd, Germany overtaking Japan) makes for compelling animation - content - weaknesses: - - 'Grid opacity set to 0.25 instead of style guide-specified 0.10 — grid lines are - 2.5x more prominent than intended; fix: GRID = "rgba(26,26,23,0.10)" if THEME - == "light" else "rgba(240,239,232,0.10)"' - - 'DE-01 remains at configured-default level: no typographic hierarchy beyond font - sizes, no visual emphasis on the leading bar or fastest-rising entity to create - a focal point' - - Only 7 entities in the dataset; specification recommends 10-20 for richer competitive - dynamics — consider adding India, Canada, South Korea, Italy - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct theme surface, not pure white - Chrome: Title "GDP Rankings · bar-race-animated · python · plotly · anyplot.ai" in 28px dark INK text, centered; subtitle in smaller gray INK_SOFT text; X-axis label "GDP (Trillion USD) — 2023 snapshot" in 22px; tick labels 18px — all clearly readable against light background - Data: 7 horizontal bars sorted descending (USA top, Brazil bottom); colors follow Okabe-Ito order — USA #009E73 (brand green), China #D55E00, Germany #CC79A7, Japan #0072B2, UK #E69F00, France #56B4E9, Brazil #F0E442; rank labels (#1–#7) and GDP values outside bars in dark INK text - Legibility verdict: PASS - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct dark theme surface, not pure black - Chrome: Title, subtitle, axis labels, tick labels, and rank/value labels all render in light cream/off-white INK token; no dark-on-dark failures observed; all text elements clearly distinguishable against near-black surface - Data: Identical bar colors to light render — #009E73, #D55E00, #CC79A7, #0072B2, #E69F00, #56B4E9, #F0E442 all unchanged confirming correct Okabe-Ito immutability across themes; brand green #009E73 remains vivid on dark surface - Legibility verdict: PASS - criteria_checklist: - visual_quality: - score: 29 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: All font sizes explicitly set (title 28px, axis 22px, ticks/labels - 18px); readable in both themes; minor deduction for subtitle size - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping elements in either render - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: All bars clearly visible; rank/value labels positioned outside bars - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito is CVD-safe; no red-green-only encoding - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 4800x2700px effective; balanced margins; nothing cut off - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: X-axis has descriptive label with units; title correctly formatted - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73; Okabe-Ito order; correct backgrounds #FAF8F1/#1A1A17; - chrome tokens flip correctly' - design_excellence: - score: 11 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: false - comment: Well-configured defaults; correct palette and rank labels; no typographic - or visual hierarchy innovation beyond defaults - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: false - comment: Generous margins and clean layout; grid opacity 0.25 exceeds recommended - 0.10 - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: false - comment: Subtitle explicitly identifies narrative; rank labels provide hierarchy; - animation delivers the race story dynamically - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Animated horizontal bar chart race with per-frame sorted bars - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Sorted frames, consistent colors, year slider, play/pause controls, - configurable speed - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Horizontal orientation, GDP on x-axis, countries on y-axis, 29 time - points - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Correct title format; no legend needed; entity identity via y-axis - labels - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Multiple entities competing with rank changes; dominant leader and - dramatic risers - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Major economy GDP — real, comprehensible, politically neutral - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: GDP values (USA ~26T, China ~13T, Japan ~4T in 2023) match approximately - correct real-world proportions - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Flat script: constants -> data -> animation frames -> figures -> - save' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) present - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: os, numpy, pandas, plotly.graph_objects — all used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Appropriate complexity; clean separation of animation and PNG figures; - no fake UI - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html with correct dimensions - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: go.Frame, go.Figure(frames=...), updatemenus, sliders — all recommended - Plotly animation patterns - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: Plotly animation with timeline slider and play/pause is genuinely - distinctive; animated hover templates not customized - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - html-export - patterns: - - data-generation - - iteration-over-groups - dataprep: [] - styling: [] diff --git a/plots/bar-race-animated/metadata/python/plotnine.yaml b/plots/bar-race-animated/metadata/python/plotnine.yaml deleted file mode 100644 index df07aa6890..0000000000 --- a/plots/bar-race-animated/metadata/python/plotnine.yaml +++ /dev/null @@ -1,243 +0,0 @@ -library: plotnine -language: python -specification_id: bar-race-animated -created: '2026-01-11T00:19:37Z' -updated: '2026-05-19T02:12:27Z' -generated_by: claude-sonnet -workflow_run: 26071252542 -issue: 3653 -language_version: 3.13.13 -library_version: 0.15.4 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/plotnine/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/plotnine/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 82 -review: - strengths: - - 'Perfect spec compliance: correctly uses small multiples as the static alternative - to animation, with all required features (sorted bars, entity labels, time indicators, - consistent colors)' - - Both themes fully adapted with correct token usage — no dark-on-dark or light-on-light - failures - - Rank-change annotations in the 2024 panel (↑3, ↓3, ↓5) add genuine storytelling - value beyond the basic spec - - 'Okabe-Ito palette applied correctly with TechCorp as brand green #009E73 first' - weaknesses: - - Bar labels use size=12; should be at least size=14–16 to meet the secondary-text - size guideline at full resolution - - Excessive vertical whitespace between the top and bottom facet rows reduces canvas - efficiency — tighter panel_spacing or explicit figure_size adjustment needed - - make_bar_label helper function violates KISS; could be an inline lambda - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct, not pure white - Chrome: Title "Tech Company Revenue Race · bar-race-animated · python · plotnine · anyplot.ai" bold and readable. Strip labels ("Year 2018", etc.) bold. X-axis label "Revenue (Billions USD)" visible. Bar labels in dark #4A4A44 ink beside each bar. - Data: 8 horizontal bars per panel, colored with Okabe-Ito palette. TechCorp = brand green #009E73 (first). DataFlow = vermillion, CloudBase = blue, NetSys = reddish purple, CodeLab = orange, AppStream = sky blue, ByteWorks = yellow, DigiCore = adaptive dark neutral. Colors consistent across all 4 panels. - Legibility verdict: PASS — all text is readable; no light-on-light issues. - - Dark render (plot-dark.png): - Background: Near-black #1A1A17 — correct, not pure black - Chrome: Title and all labels render in light cream/gray tones (#F0EFE8 / #B8B7B0). Strip backgrounds use elevated dark #242420. Bar labels in #B8B7B0 secondary ink — clearly readable against dark surface. No dark-on-dark failures observed. - Data: Bar colors are identical to light render — brand green, vermillion, blue, etc. are all unchanged as expected (only chrome flips). Brand green #009E73 clearly visible on dark surface. - Legibility verdict: PASS — all text readable; no dark-on-dark failures. - criteria_checklist: - visual_quality: - score: 25 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 6 - max: 8 - passed: true - comment: Font sizes explicitly set (24pt title, 20pt axis/strip, 16pt ticks). - Bar label size=12 is below 16pt recommendation for secondary text at full - resolution. - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: Mostly clean; 2024 panel rank indicators add horizontal width but - no actual collision. - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: All bars and labels clearly visible in both themes. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette, CVD-safe. No red-green sole-signal issues. - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: 2x2 grid works but has excessive vertical dead zone between facet - rows. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title includes spec metadata. X-axis labeled with units. Y-axis ticks - suppressed appropriately. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series TechCorp = #009E73. Okabe-Ito order followed. Backgrounds - #FAF8F1 / #1A1A17. Both themes correct.' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Above default (4). Rank-change indicators add storytelling. Theme-adaptive - chrome throughout. Elevated strip backgrounds add hierarchy. - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: true - comment: Above default (2). No panel borders, Y-axis ticks removed, minimal - grid. Deduction for excessive inter-row whitespace. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Above default (2). Sorted bars communicate ranking clearly. 2024 - rank-change annotations highlight movers effectively. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Small multiples grid is the correct static alternative for animation - spec. - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Sorted bars, entity labels, time indicators, consistent colors — - all present. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Revenue to bar length, rank to vertical position, year to facet panel - — all correct. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title follows required format: descriptive · spec-id · language - · library · anyplot.ai.' - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: '8 entities across 4 time snapshots. Shows ranking evolution. Minor - deduction: 4 time points is at the low end of spec recommendation.' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Tech company revenue in billions USD — realistic, neutral, relatable - domain. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Values ~40B-222B over 6 years with varied growth trajectories. Sensible - domain. - code_quality: - score: 9 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 2 - max: 3 - passed: true - comment: make_bar_label standalone function violates KISS; could be inline - lambda. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imported symbols are used. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean structure, no fake UI, no workarounds. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png. - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Above default (3). Idiomatic use of facet_wrap, coord_flip, geom_col, - geom_text, scale_fill_manual, scale_y_continuous. - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Above default (1). Multi-layer grammar-of-graphics composition (bars - + text + facets) effectively simulates animated race in static form. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - faceting - - annotations - patterns: - - data-generation - - groupby-aggregation - dataprep: [] - styling: - - grid-styling diff --git a/plots/bar-race-animated/metadata/python/pygal.yaml b/plots/bar-race-animated/metadata/python/pygal.yaml deleted file mode 100644 index aac9dc9716..0000000000 --- a/plots/bar-race-animated/metadata/python/pygal.yaml +++ /dev/null @@ -1,251 +0,0 @@ -library: pygal -language: python -specification_id: bar-race-animated -created: '2026-01-11T00:21:12Z' -updated: '2026-05-19T02:25:43Z' -generated_by: claude-sonnet -workflow_run: 26071319187 -issue: 3653 -language_version: 3.13.13 -library_version: 3.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/pygal/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/pygal/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/pygal/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/pygal/plot-dark.html -quality_score: 80 -review: - strengths: - - Key-trend narrative subtitle provides immediate data insight without requiring - the viewer to analyze all six panels - - 'Per-panel ranking labels (e.g. ''China: #6'') create a clear visual arc across - the small multiples' - - Consistent Okabe-Ito color coding allows entity tracking across all time panels - - 'Correct theme-adaptive chrome: backgrounds, text, and grid all flip appropriately - in both renders' - - Factually accurate GDP data with correct relative proportions across the entire - time range - weaknesses: - - Individual pygal chart panels show default grid lines and frame borders; set show_x_guides=False - or show_y_guides=False to improve DE-02 visual refinement - - HTML output covers only the 2023 snapshot with no temporal interactivity — adding - multiple year panels or reusing the sorted charts list would better serve the - bar-race concept - - In-bar value labels are slightly small in the 4800x2700 composite; increasing - value_font_size to 34-40 would improve legibility at full resolution - - HTML chart creation duplicates the data-and-style logic from the PNG loop; reuse - the final chart from the charts list - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct, not pure white - Chrome: Main title "GDP Country Rankings · bar-race-animated · python · pygal · anyplot.ai" in large bold dark text; trend subtitle in smaller dark text; panel titles with year and ranking (e.g. "2000 · China: #6") in dark text; Y-axis country labels in dark text; X-axis "GDP (Trillion USD)" label readable; value labels ("$10.2T") inside bars in dark text - Data: USA bars in #009E73 (brand green, first series correct); China #D55E00; Japan #0072B2; Germany #CC79A7; UK #E69F00; India #56B4E9; France #F0E442 — full Okabe-Ito order; bars sized proportionally to GDP values; bars sorted by value in each panel - Legibility verdict: PASS — all text readable against light background; no light-on-light failures - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct, not pure black - Chrome: Title and subtitle in light #F0EFE8 text, clearly visible; panel titles in light text; Y-axis country labels in light text; X-axis labels in light text; value labels inside bars visible (slightly small at composite resolution but no dark-on-dark failure) - Data: All entity colors identical to light render — USA #009E73, China #D55E00, Japan #0072B2, Germany #CC79A7, UK #E69F00, India #56B4E9, France #F0E442; only chrome flips between themes - Legibility verdict: PASS — all text readable against dark background; no dark-on-dark failures observed - criteria_checklist: - visual_quality: - score: 25 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 6 - max: 8 - passed: true - comment: Fonts explicitly set (title 72px, panel title 52px, label 36px, value - 28px); all readable in both themes; in-bar value labels slightly small in - final 4800x2700 composite - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: Minimal overlap; country labels clean on Y-axis; value labels positioned - without collision - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: Bars clearly visible with distinct Okabe-Ito colors; proportional - bar lengths accurately encode GDP; slightly thin at composite scale - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito is CVD-safe; adequate contrast between all entities - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: 3x2 grid fills canvas well; title and legend properly placed; minor - inter-panel margin wasted - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: X-axis labeled GDP (Trillion USD) with units; main title descriptive - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First entity (USA) uses #009E73; multi-series follows canonical - Okabe-Ito order; backgrounds are #FAF8F1 / #1A1A17; chrome flips correctly - in both renders' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 6 - max: 8 - passed: true - comment: 'Strong design: key-trend subtitle, per-panel ranking labels, consistent - entity color tracking; clearly above library defaults; individual chart - panels could be more refined' - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: false - comment: Moderate refinement; theme tokens applied; pygal default grid and - frame borders visible in panels; no spine removal possible via pygal API - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: 'Good storytelling: key trend narrative and per-panel ranking callouts - create clear visual arc; entity color consistency enables cross-panel tracking' - spec_compliance: - score: 13 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 4 - max: 5 - passed: true - comment: 'Correct chart type: horizontal bar chart; small multiples is appropriate - static alternative per spec; not animated' - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Sorted bars per frame, entity labels, time indicator, consistent - entity colors all present; HTML only shows 2023 snapshot - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: GDP on X-axis with correct scale; entities on Y-axis; all 7 countries - across 6 time points - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title matches required format; legend correctly labels all 7 entities - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: Shows ranked progression, different growth rates, rank changes across - 6 snapshots; covers the bar-race concept well - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: 'Country GDP over decades: real-world, well-known, neutral domain' - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Values approximate real data (USA ~$10T in 2000, ~$27T in 2023; China - rise from $1.2T to $17.5T); proportions factually correct - code_quality: - score: 9 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Linear imports -> data -> chart loop -> PIL composite -> save; no - classes or functions - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Deterministic hardcoded data; no random elements - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports used; cairosvg, PIL, pygal, os, sys, BytesIO all exercised - - id: CQ-04 - name: Code Elegance - score: 1 - max: 2 - passed: false - comment: HTML section duplicates chart creation logic; some verbosity in slot - construction per bar - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html; no deprecated API calls - library_mastery: - score: 6 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 3 - max: 5 - passed: true - comment: Uses pygal HorizontalBar with Style theming, SVG->PNG pipeline, and - value_formatter; heavy PIL compositing reduces pygal's share of the work - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: 'Per-bar color via pygal slot dict ({value: v, color: hex}) is a - distinctive pygal pattern; value_formatter for custom labels; interactive - HTML output' - verdict: APPROVED -impl_tags: - dependencies: - - cairosvg - - pillow - techniques: - - html-export - - custom-legend - - subplots - patterns: - - iteration-over-groups - dataprep: [] - styling: [] diff --git a/plots/bar-race-animated/metadata/python/seaborn.yaml b/plots/bar-race-animated/metadata/python/seaborn.yaml deleted file mode 100644 index 7a2c077ae9..0000000000 --- a/plots/bar-race-animated/metadata/python/seaborn.yaml +++ /dev/null @@ -1,251 +0,0 @@ -library: seaborn -language: python -specification_id: bar-race-animated -created: '2026-05-19T01:59:08Z' -updated: '2026-05-19T02:17:29Z' -generated_by: claude-sonnet -workflow_run: 26071243720 -issue: 3653 -language_version: 3.13.13 -library_version: 0.13.2 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/seaborn/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/python/seaborn/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 86 -review: - strengths: - - 'Perfect spec compliance: small multiples correctly substitutes for animation - per seaborn static-only nature, with all required features (sorted bars, consistent - entity colors, visible time indicators)' - - Rank-change highlighting (colored bar edges plus up/down value badges) adds genuine - storytelling value beyond a plain bar chart grid - - Theme adaptation is flawlessly implemented — all chrome tokens (INK, INK_SOFT, - PAGE_BG) thread through every text and spine element - - Fully flat, deterministic, readable code with no unnecessary abstractions - weaknesses: - - 'Library mastery score limited: rank-badge and edge-highlighting operate at raw - matplotlib ax.patches level rather than leveraging seaborn-specific higher-level - capabilities beyond a single barplot() call per panel' - - Value labels at fontsize=14 are slightly undersized for the 4800x2700 px canvas - (guideline suggests 16pt for secondary text) - - 6 entities/6 time points is on the low end; the spec recommends 10-20 entities - for the bar race format — adding more entities would better showcase the ranking - dynamics - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct theme surface - Chrome: Main title "Tech Company Rankings · bar-race-animated · python · seaborn · anyplot.ai" in dark #1A1A17 ink, clearly readable. Panel year titles (2014–2024) in bold dark text. X-axis labels "Market Cap ($ Billion)" in INK_SOFT. Y-axis company name labels and tick labels in INK_SOFT — all readable. - Data: Six Okabe-Ito colors: Alpha Corp #009E73 (brand green, first series correct), Beta Tech #D55E00 (vermillion), Gamma Systems #0072B2 (blue), Delta Networks #CC79A7 (pink/purple), Epsilon Data #E69F00 (orange), Zeta Cloud #56B4E9 (sky blue). Value labels ($120B–$1100B) right-aligned. Rank-change badge "▲2" visible in Delta Networks 2020 panel in entity color. - Legibility verdict: PASS - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct dark theme surface - Chrome: Main title in light #F0EFE8 ink, readable against dark background. Panel year titles in white/off-white. Axis labels and tick labels in light secondary ink (#B8B7B0). No dark-on-dark text failures observed — all chrome tokens adapt correctly. - Data: Bar colors identical to light render (Okabe-Ito positions 1–6 unchanged: #009E73, #D55E00, #0072B2, #CC79A7, #E69F00, #56B4E9). Value labels and rank badges readable against dark background. - Legibility verdict: PASS - criteria_checklist: - visual_quality: - score: 28 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: All font sizes explicitly set (title 24pt, panel titles 20pt, axis - labels 20pt, tick labels 16pt, value labels 14pt); readable in both themes. - Value labels at 14pt slightly small for high-DPI canvas. - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: Labels fit cleanly in all panels; rank badge in 2020 panel positioned - correctly. Minor crowding in 6-panel layout but no collisions. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: All bars clearly distinguishable; distinct Okabe-Ito colors; rank-mover - highlighted edges visible. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette is CVD-safe by design. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 16x9 figsize, tight_layout applied; nothing cut off. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: X-axis labelled 'Market Cap ($ Billion)' with units; main title includes - spec-id, library, language, and anyplot.ai. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First entity (Alpha Corp) = #009E73; Okabe-Ito order followed for - all 6 entities; backgrounds are #FAF8F1 / #1A1A17; both themes correct.' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Rank-change edge highlighting and up/down badges with entity-colored - labels add genuine polish above defaults. Intentional visual hierarchy per - panel. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Top/right spines removed; x-axis grid at alpha=0.10; explicit spine - and tick color tokens. 6-panel grid slightly cramped but well-managed. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Rank-change badges guide viewer to notable shifts. Consistent color - identity across panels enables easy entity tracking. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Small multiples of sorted horizontal bar charts is the correct static - alternative for a bar race; spec explicitly endorses this approach. - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Bars sorted by value per frame; entity labels attached; time indicator - as panel title; entity color consistent across all panels. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X = market cap, Y = company names; all 6 entities visible in every - panel. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title format: ''Tech Company Rankings · bar-race-animated · python - · seaborn · anyplot.ai'' — correctly prefixed with descriptive title.' - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: Shows sorted rankings, value progression, rank-change indicators, - consistent entity colors; 6 snapshots across a decade. 6 entities slightly - below spec's recommended 10-20. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Fictional tech company names are neutral; market cap values ($120B-$1100B) - are plausible for large-cap tech over 2014-2024. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Growth trajectories are realistic (corrections, surges, steady risers); - no implausible outliers. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Flat script, no functions or classes. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Data is fully hardcoded; deterministic output. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only os, sys, matplotlib, pandas, seaborn; all used. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clear data structure; rank delta computation is concise; no fake - UI elements. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png; uses current seaborn 0.13 API with hue+palette+legend=False. - library_mastery: - score: 6 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: sns.barplot() with hue, palette, and order parameters is idiomatic - seaborn horizontal bars; sns.set_theme() with rc dict for global theming - is correct. - - id: LM-02 - name: Distinctive Features - score: 2 - max: 5 - passed: false - comment: Rank-badge and edge-highlighting logic operates at raw matplotlib - patch level rather than seaborn-specific higher-level features. Seaborn - usage limited to a single barplot call per panel. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - annotations - - subplots - patterns: - - data-generation - - iteration-over-groups - - explicit-figure - dataprep: [] - styling: - - edge-highlighting diff --git a/plots/bar-race-animated/metadata/r/ggplot2.yaml b/plots/bar-race-animated/metadata/r/ggplot2.yaml deleted file mode 100644 index f08a8560f7..0000000000 --- a/plots/bar-race-animated/metadata/r/ggplot2.yaml +++ /dev/null @@ -1,264 +0,0 @@ -library: ggplot2 -language: r -specification_id: bar-race-animated -created: '2026-05-19T02:10:39Z' -updated: '2026-05-19T02:29:50Z' -generated_by: claude-sonnet -workflow_run: 26071722470 -issue: 3653 -language_version: 4.4.1 -library_version: 3.5.1 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/r/ggplot2/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/bar-race-animated/r/ggplot2/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 87 -review: - strengths: - - Correctly implements small multiples (facet_wrap) as the static alternative to - animation, per library spec and spec notes - - 'All font sizes properly set to recommended values: title 24pt, subtitle 18pt, - axis title 20pt, tick labels 16pt, legend 16pt, strip 18pt' - - Value labels on bars add meaningful data context without clutter - - Informative subtitle provides clear narrative insight about the GDP race - - Full theme-adaptive chrome (PAGE_BG, INK, INK_SOFT, ELEVATED_BG) correctly applied - to both themes - - 'Correct Okabe-Ito palette: Africa=#009E73 as first categorical series, full order - for 5 continents' - - The '___' separator trick for per-facet sorted bars is elegant and idiomatic ggplot2 - - set.seed(42) present; all imports used; clean linear script - weaknesses: - - geom_text value labels at size=4.5 are slightly small for a 4800x2700 canvas; - size 5.5-6.5 would be more legible at full resolution - - Design lacks a focal point — no highlighted country trajectory or callout annotation - to guide viewer attention across the chronological facets - - panel.border is not explicitly removed, leaving a box frame around each facet - panel; adding panel.border = element_blank() would add refinement - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct, fills the full canvas including between facets - Chrome: Title "GDP per Capita Rankings · bar-race-animated · r · ggplot2 · anyplot.ai" in bold dark ink at 24pt — clearly readable. Subtitle at 18pt in secondary dark ink readable. Strip year labels (1952, 1967, 1977, 1987, 1997, 2007) in bold 18pt on elevated off-white backgrounds. X-axis label "GDP per Capita (USD)" at 20pt readable. Country names on y-axis at 16pt readable. Dollar tick labels on x-axis at 16pt readable. Legend at bottom shows continent colors with readable text. - Data: Horizontal bars colored by continent — Africa (#009E73 green), Americas (#D55E00 orange-red), Asia (#0072B2 blue), Europe (#CC79A7 reddish-purple), Oceania (#E69F00 orange-yellow). 6 facets in 2x3 grid show top-10 countries for years 1952, 1967, 1977, 1987, 1997, 2007. Value labels at bar ends (e.g. $108K for Kuwait in 1952). Bars sorted descending by value. Kuwait dominates 1952; Norway rises to top by 2007. - Legibility verdict: PASS — all text is readable against the light background, no light-on-light failures. - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct, fills canvas completely - Chrome: Title in primary light ink (#F0EFE8) — fully readable. Subtitle in secondary light ink — readable. Strip year labels in bright light text on dark elevated (#242420) backgrounds. Country names on y-axis in primary light ink — readable. X-axis tick labels in secondary ink token (#B8B7B0) — readable. Legend shows identical continent colors with light text against elevated dark background. No dark-on-dark failures detected; chrome adapts correctly. - Data: Bar colors are identical to the light render — Africa green, Americas orange, Asia blue, Europe purple, Oceania yellow-orange. Consistency is correct. Value labels at bar ends visible in INK_SOFT (#B8B7B0). Subtle x-axis vertical grid lines visible against dark background. - Legibility verdict: PASS — all text is readable against the dark background, chrome adapts correctly. - criteria_checklist: - visual_quality: - score: 27 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: Title 24pt, subtitle 18pt, axis title 20pt, tick labels 16pt, legend - 16pt, strip 18pt all correctly sized; geom_text value labels at size=4.5 - slightly below optimal for 4800x2700 canvas but readable - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: No collisions; slight crowding in facets with many short bars but - expand=c(0,0.28) gives adequate space for value labels - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: All bars clearly visible at alpha=0.9; five continents well-differentiated; - value labels at bar ends add clarity - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito is CVD-safe; five continents well-differentiated; no red-green - as sole signal - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: 2x3 facet grid works well for 6 time snapshots on landscape canvas; - slight internal crowding with 10 bars per panel - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: GDP per Capita (USD) with units on x-axis; no y-axis label needed - since country names are self-explanatory - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Africa=#009E73 as first categorical series; full Okabe-Ito order - for 5 continents; background #FAF8F1 light / #1A1A17 dark; both renders - theme-correct' - design_excellence: - score: 14 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 6 - max: 8 - passed: true - comment: 'Above default (4): intentional continent-based color hierarchy, - bold title with narrative subtitle, strip backgrounds using elevated colors, - consistent typographic hierarchy; limited by no highlighted trajectory or - focal annotations' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: 'Above default (2): y-axis grid removed, x-axis grid very subtle - at 0.25 linewidth, no minor grid, strip backgrounds use ELEVATED_BG; panel.border - not explicitly removed' - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: 'Above default (2): narrative subtitle gives clear insight; chronological - facet progression tells the arc; continent colors reveal geographic patterns; - no highlighted trajectory or callout annotations' - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Small multiples via facet_wrap is the correct and specified static - alternative for ggplot2; correctly renders each time snapshot as a sorted - bar chart - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Bars sorted by value per time point via slice_max+reorder; entity - labels on y-axis; time indicator via facet strip labels; consistent entity - colors across all frames - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X=GDP per capita value, Y=ranked countries, fill=continent, facets=year; - all data correctly mapped - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title 'GDP per Capita Rankings · bar-race-animated · r · ggplot2 - · anyplot.ai' matches required format; legend labels are continent names - with correct colors - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: Shows ranked bars, time evolution, entity tracking, category coloring, - and value annotations; individual country trajectory hard to follow across - facets by nature of format - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Uses real gapminder historical data; GDP per capita values are authentic - and politically neutral - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: $0K-$108K range is realistic; dollar formatting with K suffix is - appropriate for the domain - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Linear script with no functions or classes - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: set.seed(42) present; gapminder is a fixed deterministic dataset - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All 5 imports (ggplot2, dplyr, scales, ragg, gapminder) are actually - used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: The '___' separator trick for per-facet sorted bars is a genuine - idiomatic ggplot2 pattern; clean dplyr piping; no fake UI elements - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png via sprintf; uses ragg::agg_png device - correctly - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: 'Excellent grammar-of-graphics: aes mapping, facet_wrap with free - scales, coord_flip, scale_fill_manual, scale_x_discrete with label function, - expansion(), proper theme layering; not full 5 due to no panel.border removal' - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: reorder(cntry_yr, gdpPercap) with '___' separator is a genuine ggplot2-specific - pattern for sorted faceted bars; scales::label_dollar with suffix; expansion(mult) - for label room; above default (1) but not maximally distinctive - verdict: APPROVED -impl_tags: - dependencies: - - gapminder - techniques: - - faceting - - annotations - patterns: - - dataset-loading - - groupby-aggregation - dataprep: [] - styling: - - alpha-blending diff --git a/plots/bar-race-animated/specification.md b/plots/bar-race-animated/specification.md deleted file mode 100644 index 927ae90ece..0000000000 --- a/plots/bar-race-animated/specification.md +++ /dev/null @@ -1,30 +0,0 @@ -# bar-race-animated: Animated Bar Chart Race - -## Description - -An animated horizontal bar chart that visualizes how rankings and values change over time. Bars smoothly transition positions as their values update, with the chart reordering to maintain a sorted ranking at each time step. This dynamic visualization format is highly engaging for storytelling with time-series data, making it popular for showing the rise and fall of entities like countries, companies, or products over extended periods. - -## Applications - -- Visualizing how country GDP rankings have shifted over decades, with bars racing as economies grow and overtake one another -- Tracking brand market share evolution throughout the year, showing competitive dynamics in an engaging animated format -- Displaying historical sports team standings or player statistics over multiple seasons, revealing dynasty periods and surprising reversals - -## Data - -- `entity` (categorical) - The items being compared and ranked (countries, companies, teams, products) -- `time` (datetime or numeric) - Time points for each snapshot, driving the animation sequence -- `value` (numeric) - The metric determining bar length and ranking at each time point -- `color` (categorical, optional) - Category or group for coloring bars consistently -- Size: 10-20 entities with 20-100 time points recommended for smooth, comprehensible animation -- Example: Country GDP by year, streaming platform subscribers by month, brand revenue by quarter - -## Notes - -- Bars should be sorted by value at each frame, with smooth transitions for position changes -- Entity labels should remain attached to their bars throughout the animation -- A visible time indicator (counter, axis label, or title) should update during playback -- Animation speed should be configurable or use a reasonable default duration -- For static output or libraries without animation support, show a small multiples grid of key time snapshots -- Consider including play/pause controls and a timeline scrubber for interactive versions -- Color should remain consistent for each entity across all frames for tracking diff --git a/plots/bar-race-animated/specification.yaml b/plots/bar-race-animated/specification.yaml deleted file mode 100644 index 584b2cb0c3..0000000000 --- a/plots/bar-race-animated/specification.yaml +++ /dev/null @@ -1,29 +0,0 @@ -# Specification-level metadata for bar-race-animated -# Auto-synced to PostgreSQL on push to main - -spec_id: bar-race-animated -title: Animated Bar Chart Race - -# Specification tracking -created: 2026-01-11T00:09:16Z -updated: 2026-01-11T00:09:16Z -issue: 3653 -suggested: MarkusNeusinger - -# Classification tags (applies to all library implementations) -# See docs/reference/tagging-system.md for detailed guidelines -tags: - plot_type: - - bar - data_type: - - categorical - - numeric - - timeseries - domain: - - general - - business - features: - - animated - - horizontal - - ranking - - temporal diff --git a/plots/dashboard-synchronized-crosshair/implementations/julia/makie.jl b/plots/dashboard-synchronized-crosshair/implementations/julia/makie.jl deleted file mode 100644 index 6b346c43f5..0000000000 --- a/plots/dashboard-synchronized-crosshair/implementations/julia/makie.jl +++ /dev/null @@ -1,231 +0,0 @@ -# anyplot.ai -# dashboard-synchronized-crosshair: Synchronized Multi-Chart Dashboard -# Library: makie 0.22.10 | Julia 1.11.9 -# Quality: 86/100 | Created: 2026-05-23 - -using CairoMakie -using Colors -using Random -using Statistics - -Random.seed!(42) - -# Theme tokens -const THEME = get(ENV, "ANYPLOT_THEME", "light") -const PAGE_BG = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17" -const INK = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8" -const INK_SOFT = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0" -const INK_MUTED = THEME == "light" ? colorant"#6B6A63" : colorant"#A8A79F" - -const IMPRINT = [ - colorant"#009E73", - colorant"#C475FD", - colorant"#AE3030", - colorant"#4467A3", - colorant"#99B314", - colorant"#954477", - colorant"#BD8233", -] - -# Data: 200 simulated trading days (stock price, volume, RSI) -n = 200 -days = Float64.(1:n) - -log_returns = 0.0008 .+ 0.012 .* randn(n) -price = 100.0 .* cumprod(1.0 .+ log_returns) - -volume = 800_000.0 .+ 400_000.0 .* abs.(randn(n)) - -# RSI (14-period) computed inline -period = 14 -delta = diff(price) -gains_raw = max.(delta, 0.0) -losses_raw = abs.(min.(delta, 0.0)) -avg_gain = zeros(n) -avg_loss = zeros(n) -for i in 1:(n - 1) - s = max(1, i - period + 1) - avg_gain[i + 1] = mean(gains_raw[s:i]) - avg_loss[i + 1] = mean(losses_raw[s:i]) -end -rsi = 100.0 .- 100.0 ./ (1.0 .+ avg_gain ./ (avg_loss .+ 1e-10)) -rsi[1:period] .= NaN - -valid_idx = findall(!isnan, rsi) -rsi_days = days[valid_idx] -rsi_vals = rsi[valid_idx] - -# Crosshair at RSI overbought peak — narrative snapshot of when RSI peaked above 70 -overbought = filter(i -> !isnan(rsi[i]) && rsi[i] > 70, 1:n) -cx = isempty(overbought) ? valid_idx[argmax(rsi_vals)] : overbought[argmax(rsi[overbought])] -cx_price = price[cx] -cx_volume = volume[cx] -cx_rsi = rsi[cx] - -# Price / volume bounds for panel label positioning -price_lo, price_hi = extrema(price) -vol_hi = maximum(volume) - -# Shared grid color (10% opacity INK) -grid_col = RGBAf(INK.r, INK.g, INK.b, 0.10) - -# Zone fill colors — very low alpha so data is not obscured -overbought_fill = RGBAf(IMPRINT[3].r, IMPRINT[3].g, IMPRINT[3].b, 0.10) -oversold_fill = RGBAf(IMPRINT[1].r, IMPRINT[1].g, IMPRINT[1].b, 0.10) - -# Figure -fig = Figure( - size = (1600, 900), - fontsize = 14, - backgroundcolor = PAGE_BG, -) - -# Axis 1: Price -ax1 = Axis( - fig[1, 1]; - title = "dashboard-synchronized-crosshair · julia · makie · anyplot.ai", - titlesize = 19, - titlecolor = INK, - ylabel = "Price (USD)", - ylabelsize = 13, - ylabelcolor = INK, - xticklabelcolor = INK_SOFT, - yticklabelcolor = INK_SOFT, - xtickcolor = INK_SOFT, - ytickcolor = INK_SOFT, - backgroundcolor = PAGE_BG, - topspinevisible = false, - rightspinevisible = false, - leftspinecolor = INK_SOFT, - bottomspinecolor = INK_SOFT, - xgridvisible = false, - ygridcolor = grid_col, - ygridwidth = 1.0, -) -hidexdecorations!(ax1; ticklabels = true, ticks = false, label = false, - grid = false, minorgrid = false, minorticks = false) - -# Axis 2: Volume -ax2 = Axis( - fig[2, 1]; - ylabel = "Volume", - ylabelsize = 13, - ylabelcolor = INK, - xticklabelcolor = INK_SOFT, - yticklabelcolor = INK_SOFT, - xtickcolor = INK_SOFT, - ytickcolor = INK_SOFT, - backgroundcolor = PAGE_BG, - topspinevisible = false, - rightspinevisible = false, - leftspinecolor = INK_SOFT, - bottomspinecolor = INK_SOFT, - xgridvisible = false, - ygridcolor = grid_col, - ygridwidth = 1.0, - ytickformat = vs -> ["$(round(Int, v / 1000))K" for v in vs], -) -hidexdecorations!(ax2; ticklabels = true, ticks = false, label = false, - grid = false, minorgrid = false, minorticks = false) - -# Axis 3: RSI -ax3 = Axis( - fig[3, 1]; - xlabel = "Trading Day", - ylabel = "RSI (14)", - xlabelsize = 13, - ylabelsize = 13, - xlabelcolor = INK, - ylabelcolor = INK, - xticklabelcolor = INK_SOFT, - yticklabelcolor = INK_SOFT, - xtickcolor = INK_SOFT, - ytickcolor = INK_SOFT, - backgroundcolor = PAGE_BG, - topspinevisible = false, - rightspinevisible = false, - leftspinecolor = INK_SOFT, - bottomspinecolor = INK_SOFT, - xgridvisible = false, - ygridcolor = grid_col, - ygridwidth = 1.0, - yticks = [30, 50, 70], - limits = (nothing, (0.0, 100.0)), -) - -linkxaxes!(ax1, ax2, ax3) - -# Price line — palette position 1 -lines!(ax1, days, price; color = IMPRINT[1], linewidth = 2.0) - -# Volume bars — palette position 2 (canonical order) -barplot!(ax2, days, volume; color = IMPRINT[2], strokewidth = 0, width = 0.9) - -# RSI overbought/oversold zone fills using band! — highlights the threshold regions -band!(ax3, days, fill(70.0, n), fill(100.0, n); color = overbought_fill) -band!(ax3, days, fill(0.0, n), fill(30.0, n); color = oversold_fill) - -# RSI line — palette position 3 (canonical order) -lines!(ax3, rsi_days, rsi_vals; color = IMPRINT[3], linewidth = 2.0) - -# RSI overbought / oversold reference lines -hlines!(ax3, [30.0, 70.0]; - color = RGBAf(INK_MUTED.r, INK_MUTED.g, INK_MUTED.b, 0.5), - linewidth = 1.0, - linestyle = :dash, -) - -# RSI zone labels — anchored inside the zone, left side -text!(ax3, 5.0, 85.0; - text = "Overbought", color = INK_MUTED, fontsize = 10, align = (:left, :center)) -text!(ax3, 5.0, 15.0; - text = "Oversold", color = INK_MUTED, fontsize = 10, align = (:left, :center)) - -# Panel labels in top-left corner of each axis for quick scannability -text!(ax1, 5.0, price_lo + 0.93 * (price_hi - price_lo); - text = "PRICE", color = INK_SOFT, fontsize = 12, font = :bold, align = (:left, :top)) -text!(ax2, 5.0, 0.93 * vol_hi; - text = "VOLUME", color = INK_SOFT, fontsize = 12, font = :bold, align = (:left, :top)) -text!(ax3, 5.0, 95.0; - text = "RSI", color = INK_SOFT, fontsize = 12, font = :bold, align = (:left, :top)) - -# Synchronized crosshair at RSI overbought peak — vertical line spans all three panels -cx_line_col = RGBAf(INK.r, INK.g, INK.b, 0.45) -vlines!(ax1, [Float64(cx)]; color = cx_line_col, linewidth = 1.5, linestyle = :dash) -vlines!(ax2, [Float64(cx)]; color = cx_line_col, linewidth = 1.5, linestyle = :dash) -vlines!(ax3, [Float64(cx)]; color = cx_line_col, linewidth = 1.5, linestyle = :dash) - -# Value markers at the crosshair position -scatter!(ax1, [Float64(cx)], [cx_price]; - color = IMPRINT[1], markersize = 10, - strokewidth = 1.5, strokecolor = PAGE_BG) -scatter!(ax2, [Float64(cx)], [cx_volume]; - color = IMPRINT[2], markersize = 10, - strokewidth = 1.5, strokecolor = PAGE_BG) -scatter!(ax3, [Float64(cx)], [cx_rsi]; - color = IMPRINT[3], markersize = 10, - strokewidth = 1.5, strokecolor = PAGE_BG) - -# Value annotations at crosshair — offset right; for RSI clamp downward when near ceiling -text!(ax1, cx + 4.0, cx_price; - text = "$(round(cx_price, digits=1))", - color = INK_SOFT, fontsize = 12, align = (:left, :center)) -text!(ax2, cx + 4.0, cx_volume; - text = "$(round(Int, cx_volume / 1000))K", - color = INK_SOFT, fontsize = 12, align = (:left, :center)) - -# RSI annotation: position below dot when near ceiling, label the peak clearly -rsi_ann_y = cx_rsi > 82.0 ? cx_rsi - 10.0 : cx_rsi + 3.0 -rsi_ann_va = cx_rsi > 82.0 ? :top : :bottom -text!(ax3, cx + 4.0, rsi_ann_y; - text = "$(round(cx_rsi, digits=1)) ← peak", - color = INK_SOFT, fontsize = 11, align = (:left, rsi_ann_va)) - -# Row proportions: price panel tallest, volume and RSI smaller -rowsize!(fig.layout, 1, Relative(0.42)) -rowsize!(fig.layout, 2, Relative(0.30)) -rowsize!(fig.layout, 3, Relative(0.28)) -rowgap!(fig.layout, 5) - -# Save -save("plot-$(THEME).png", fig; px_per_unit = 2) diff --git a/plots/dashboard-synchronized-crosshair/implementations/python/altair.py b/plots/dashboard-synchronized-crosshair/implementations/python/altair.py deleted file mode 100644 index 30442237f3..0000000000 --- a/plots/dashboard-synchronized-crosshair/implementations/python/altair.py +++ /dev/null @@ -1,172 +0,0 @@ -""" anyplot.ai -dashboard-synchronized-crosshair: Synchronized Multi-Chart Dashboard -Library: altair 6.1.0 | Python 3.13.13 -Quality: 87/100 | Updated: 2026-05-23 -""" - -import os - -import altair as alt -import numpy as np -import pandas as pd -from PIL import Image - - -# 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" - -# Imprint palette positions 1–3 -COLOR_PRICE = "#009E73" -COLOR_VOLUME = "#C475FD" -COLOR_INDICATOR = "#AE3030" - -# Data — stock market: price, volume, momentum indicator -np.random.seed(42) -n_points = 200 -dates = pd.date_range("2024-01-01", periods=n_points, freq="B") - -returns = np.random.normal(0.001, 0.02, n_points) -price = 100 * np.cumprod(1 + returns) - -raw_volume = np.random.uniform(1e6, 3e6, n_points) -volume = raw_volume * (1 + np.abs(returns) * 20) - -raw_momentum = 50 + np.cumsum(np.random.normal(0, 5, n_points)) -indicator = np.clip(raw_momentum, 0, 100) - -df = pd.DataFrame({"date": dates, "price": price, "volume": volume, "indicator": indicator}) - -# Shared selections -nearest = alt.selection_point(nearest=True, on="pointerover", fields=["date"], empty=False) -# Synchronized zoom/pan — same parameter name across all views links them in Vega-Lite -zoom = alt.selection_interval(bind="scales", encodings=["x"]) - -# X encodings: axis hidden on top charts, labeled on bottom chart only -x_hidden = alt.X("date:T", axis=alt.Axis(labels=False, ticks=False, domain=False, title=None)) -x_labeled = alt.X("date:T", title="Trading Date", axis=alt.Axis(format="%b %Y", labelFontSize=10, titleFontSize=12)) - -# Price chart (top) — green line, zero=False so actual range fills the height -base_p = alt.Chart(df).encode(x=x_hidden) - -price_chart = alt.layer( - base_p.mark_line(color=COLOR_PRICE, strokeWidth=2) - .encode( - y=alt.Y( - "price:Q", title="Price ($)", scale=alt.Scale(zero=False), axis=alt.Axis(labelFontSize=10, titleFontSize=12) - ), - tooltip=[ - alt.Tooltip("date:T", format="%Y-%m-%d", title="Date"), - alt.Tooltip("price:Q", format="$.2f", title="Price"), - ], - ) - .add_params(zoom), - base_p.mark_point(color=COLOR_PRICE, size=80, filled=True).encode( - y=alt.Y("price:Q", scale=alt.Scale(zero=False)), opacity=alt.condition(nearest, alt.value(1), alt.value(0)) - ), - base_p.mark_rule(color=INK_SOFT, strokeWidth=1.5) - .encode(opacity=alt.condition(nearest, alt.value(0.7), alt.value(0))) - .add_params(nearest), -).properties(width=620, height=90, title=alt.Title("Price", anchor="start", fontSize=12)) - -# Volume chart (middle) — purple bars with synchronized crosshair -base_v = alt.Chart(df).encode(x=x_hidden) - -volume_chart = alt.layer( - base_v.mark_bar(color=COLOR_VOLUME, opacity=0.7) - .encode( - y=alt.Y("volume:Q", title="Volume", axis=alt.Axis(labelFontSize=10, titleFontSize=12, format="~s")), - tooltip=[ - alt.Tooltip("date:T", format="%Y-%m-%d", title="Date"), - alt.Tooltip("volume:Q", format=",.0f", title="Volume"), - ], - ) - .add_params(zoom), - base_v.mark_rule(color=INK_SOFT, strokeWidth=1.5) - .encode(opacity=alt.condition(nearest, alt.value(0.7), alt.value(0))) - .add_params(nearest), -).properties(width=620, height=70, title=alt.Title("Volume", anchor="start", fontSize=12)) - -# Momentum indicator chart (bottom) — red line with reference zones and labeled x-axis -base_i = alt.Chart(df).encode(x=x_labeled) - -# Semi-transparent zone shading: oversold (<30) and overbought (>70) bands for storytelling -zone_df = pd.DataFrame({"x1": [dates[0], dates[0]], "x2": [dates[-1], dates[-1]], "y1": [0, 70], "y2": [30, 100]}) -zone_bg = ( - alt.Chart(zone_df).mark_rect(color=COLOR_INDICATOR, opacity=0.08).encode(x="x1:T", x2="x2:T", y="y1:Q", y2="y2:Q") -) - -overbought_line = ( - alt.Chart(pd.DataFrame({"y": [70]})).mark_rule(color=INK_SOFT, strokeDash=[4, 4], strokeWidth=1.0).encode(y="y:Q") -) -oversold_line = ( - alt.Chart(pd.DataFrame({"y": [30]})).mark_rule(color=INK_SOFT, strokeDash=[4, 4], strokeWidth=1.0).encode(y="y:Q") -) - -indicator_chart = alt.layer( - zone_bg, - base_i.mark_line(color=COLOR_INDICATOR, strokeWidth=2) - .encode( - y=alt.Y( - "indicator:Q", - title="Momentum", - scale=alt.Scale(domain=[0, 100]), - axis=alt.Axis(labelFontSize=10, titleFontSize=12), - ), - tooltip=[ - alt.Tooltip("date:T", format="%Y-%m-%d", title="Date"), - alt.Tooltip("indicator:Q", format=".1f", title="Momentum"), - ], - ) - .add_params(zoom), - base_i.mark_point(color=COLOR_INDICATOR, size=80, filled=True).encode( - y=alt.Y("indicator:Q"), opacity=alt.condition(nearest, alt.value(1), alt.value(0)) - ), - overbought_line, - oversold_line, - base_i.mark_rule(color=INK_SOFT, strokeWidth=1.5) - .encode(opacity=alt.condition(nearest, alt.value(0.7), alt.value(0))) - .add_params(nearest), -).properties(width=620, height=70, title=alt.Title("Momentum Indicator", anchor="start", fontSize=12)) - -# Compose and configure -TITLE = "dashboard-synchronized-crosshair · python · altair · anyplot.ai" - -chart = ( - alt.vconcat(price_chart, volume_chart, indicator_chart, spacing=10) - .properties(background=PAGE_BG, title=alt.Title(TITLE, fontSize=16, anchor="middle")) - .configure_view(fill=PAGE_BG, strokeWidth=0) - .configure_axis( - domainColor=INK_SOFT, - tickColor=INK_SOFT, - gridColor=INK, - gridOpacity=0.10, - labelColor=INK_SOFT, - titleColor=INK, - grid=True, - ) - .configure_axisX(grid=False) - .configure_title(color=INK) - .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK) -) - -# Save PNG and pad to exact 3200 × 1800 target -chart.save(f"plot-{THEME}.png", scale_factor=4.0) - -TW, TH = 3200, 1800 -_img = Image.open(f"plot-{THEME}.png").convert("RGB") -_w, _h = _img.size -if _w > TW or _h > TH: - raise SystemExit( - f"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. " - f"Shrink chart .properties(width=, height=) values and re-render." - ) -if _w < TW or _h < TH: - _canvas = Image.new("RGB", (TW, TH), PAGE_BG) - _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2)) - _canvas.save(f"plot-{THEME}.png") - -chart.save(f"plot-{THEME}.html") diff --git a/plots/dashboard-synchronized-crosshair/implementations/python/bokeh.py b/plots/dashboard-synchronized-crosshair/implementations/python/bokeh.py deleted file mode 100644 index 4da5ff6580..0000000000 --- a/plots/dashboard-synchronized-crosshair/implementations/python/bokeh.py +++ /dev/null @@ -1,193 +0,0 @@ -""" anyplot.ai -dashboard-synchronized-crosshair: Synchronized Multi-Chart Dashboard -Library: bokeh 3.9.0 | Python 3.13.13 -Quality: 92/100 | Updated: 2026-05-23 -""" - -import base64 -import os -import sys -import time -from pathlib import Path - - -# Prevent this file (bokeh.py) from shadowing the installed bokeh package. -# Python inserts the script's directory as sys.path[0]; remove it so that -# 'import bokeh' resolves to the package, not this script. -_here = str(Path(__file__).resolve().parent) -sys.path = [p for p in sys.path if Path(p).resolve() != Path(_here)] - -import numpy as np -import pandas as pd -from bokeh.io import output_file, save -from bokeh.layouts import column -from bokeh.models import ColumnDataSource, CrosshairTool, HoverTool -from bokeh.plotting import figure -from selenium import webdriver -from selenium.webdriver.chrome.options import Options - - -# 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" - -# Imprint palette — assigned across the three metric series -C_PRICE = "#009E73" # green (position 1) -C_VOLUME = "#C475FD" # purple (position 2) -C_RSI = "#4467A3" # sky blue (position 4) -C_OVERBOUGHT = "#AE3030" # red — semantic: danger / sell zone - -# Data — stock data over 200 trading days -np.random.seed(42) -n_points = 200 -dates = pd.date_range("2024-01-01", periods=n_points, freq="B") - -price_changes = np.random.randn(n_points) * 2 + 0.05 -price = 100 + np.cumsum(price_changes) - -volume = np.abs(price_changes) * 1e6 + np.random.uniform(1e6, 3e6, n_points) - -rsi = 50 + np.cumsum(np.random.randn(n_points) * 3) -rsi = np.clip(rsi, 20, 80) - -source = ColumnDataSource( - data={"date": dates, "price": price, "volume": volume / 1e6, "rsi": rsi, "date_str": dates.strftime("%Y-%m-%d")} -) - -# Canvas: landscape 3200 × 1800 — three charts, heights sum to 1800 -W, H = 3200, 1800 - -crosshair_opts = {"dimensions": "height", "line_color": INK_SOFT, "line_alpha": 0.85, "line_width": 2} -common_opts = {"width": W, "toolbar_location": None, "x_axis_type": "datetime", "min_border_left": 180, "min_border_right": 60} - -# Chart 1: Price -p1 = figure( - **common_opts, - height=600, - title="dashboard-synchronized-crosshair · python · bokeh · anyplot.ai", - y_axis_label="Price ($)", - min_border_top=110, - min_border_bottom=20, -) -p1.line("date", "price", source=source, line_width=3, color=C_PRICE, legend_label="Price ($)") -p1.add_tools(CrosshairTool(**crosshair_opts)) -p1.add_tools(HoverTool(tooltips=[("Date", "@date_str"), ("Price", "$@price{0.00}")], mode="vline")) - -# Chart 2: Volume -p2 = figure( - **common_opts, height=540, y_axis_label="Volume (M)", x_range=p1.x_range, min_border_top=30, min_border_bottom=20 -) -p2.vbar( - "date", - top="volume", - source=source, - width=60 * 60 * 1000 * 18, - color=C_VOLUME, - alpha=0.75, - legend_label="Volume (M)", -) -p2.add_tools(CrosshairTool(**crosshair_opts)) -p2.add_tools(HoverTool(tooltips=[("Date", "@date_str"), ("Volume", "@volume{0.00}M")], mode="vline")) - -# Chart 3: RSI with overbought / oversold reference lines -p3 = figure( - **common_opts, - height=660, - y_axis_label="RSI", - x_axis_label="Date", - x_range=p1.x_range, - min_border_top=30, - min_border_bottom=160, -) -p3.line("date", "rsi", source=source, line_width=3, color=C_RSI, legend_label="RSI") -p3.line( - dates, - [70] * n_points, - line_dash="dashed", - line_width=2, - color=C_OVERBOUGHT, - alpha=0.85, - legend_label="Overbought (70)", -) -p3.line( - dates, [30] * n_points, line_dash="dashed", line_width=2, color=C_PRICE, alpha=0.6, legend_label="Oversold (30)" -) -p3.add_tools(CrosshairTool(**crosshair_opts)) -p3.add_tools(HoverTool(tooltips=[("Date", "@date_str"), ("RSI", "@rsi{0.0}")], mode="vline")) - -# Style all charts — theme-adaptive chrome -for p in [p1, p2, p3]: - p.title.text_font_size = "50pt" - p.xaxis.axis_label_text_font_size = "42pt" - p.yaxis.axis_label_text_font_size = "42pt" - p.xaxis.major_label_text_font_size = "34pt" - p.yaxis.major_label_text_font_size = "34pt" - p.legend.label_text_font_size = "28pt" - p.legend.location = "top_left" - p.legend.background_fill_color = ELEVATED_BG - p.legend.border_line_color = INK_SOFT - p.legend.label_text_color = INK_SOFT - p.legend.padding = 12 - p.legend.spacing = 6 - - p.background_fill_color = PAGE_BG - p.border_fill_color = PAGE_BG - p.outline_line_color = INK_SOFT - - p.title.text_color = INK - p.xaxis.axis_label_text_color = INK - p.yaxis.axis_label_text_color = INK - p.xaxis.major_label_text_color = INK_SOFT - p.yaxis.major_label_text_color = INK_SOFT - p.xaxis.axis_line_color = INK_SOFT - p.yaxis.axis_line_color = INK_SOFT - p.xaxis.major_tick_line_color = INK_SOFT - p.yaxis.major_tick_line_color = INK_SOFT - - p.xgrid.grid_line_color = INK - p.ygrid.grid_line_color = INK - p.xgrid.grid_line_alpha = 0.10 - p.ygrid.grid_line_alpha = 0.10 - -# Hide x-axis on upper charts for cleaner stacked look -p1.xaxis.visible = False -p2.xaxis.visible = False - -# Stacked layout — heights sum to 1800 exactly -layout = column(p1, p2, p3, spacing=0, sizing_mode="fixed") - -# Save interactive HTML -output_file(f"plot-{THEME}.html") -save(layout) - -# Screenshot with headless Chrome — use CDP Page.captureScreenshot with -# captureBeyondViewport so the canvas dimensions are exact regardless of -# the browser's implicit window-chrome offset (~139 px on headless Chrome). -opts = Options() -for arg in ( - "--headless=new", - "--no-sandbox", - "--disable-dev-shm-usage", - "--disable-gpu", - f"--window-size={W},{H + 200}", - "--hide-scrollbars", - "--force-device-scale-factor=1", -): - opts.add_argument(arg) -driver = webdriver.Chrome(options=opts) -driver.get(f"file://{Path(f'plot-{THEME}.html').resolve()}") -time.sleep(3) -result = driver.execute_cdp_cmd( - "Page.captureScreenshot", - { - "format": "png", - "clip": {"x": 0.0, "y": 0.0, "width": float(W), "height": float(H), "scale": 1.0}, - "captureBeyondViewport": True, - }, -) -with open(f"plot-{THEME}.png", "wb") as fh: - fh.write(base64.b64decode(result["data"])) -driver.quit() diff --git a/plots/dashboard-synchronized-crosshair/implementations/python/letsplot.py b/plots/dashboard-synchronized-crosshair/implementations/python/letsplot.py deleted file mode 100644 index 00972f5506..0000000000 --- a/plots/dashboard-synchronized-crosshair/implementations/python/letsplot.py +++ /dev/null @@ -1,181 +0,0 @@ -""" anyplot.ai -dashboard-synchronized-crosshair: Synchronized Multi-Chart Dashboard -Library: letsplot 4.10.1 | Python 3.13.13 -Quality: 84/100 | Updated: 2026-05-23 -""" - -import os - -import numpy as np -import pandas as pd -from lets_plot import ( - LetsPlot, - aes, - element_blank, - element_line, - element_rect, - element_text, - geom_hline, - geom_line, - geom_path, - geom_point, - geom_segment, - geom_text, - gggrid, - ggplot, - ggsize, - ggtitle, - labs, - layer_tooltips, - scale_y_continuous, - theme, - theme_minimal, -) -from lets_plot.export import ggsave - - -LetsPlot.setup_html() - -# 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" -GRID_COLOR = "#E1DFD9" if THEME == "light" else "#32322F" - -IMPRINT = ["#009E73", "#C475FD", "#AE3030", "#4467A3", "#99B314", "#954477", "#BD8233"] -CROSSHAIR_COLOR = IMPRINT[5] # #954477 pink — visually distinct from all 3 series - -# Data — stock dashboard: price, volume, RSI over 200 trading days -np.random.seed(42) -n_points = 200 - -dates = pd.date_range("2024-01-01", periods=n_points, freq="B") -returns = np.random.normal(0.001, 0.02, n_points) -price = 100 * np.cumprod(1 + returns) -base_volume = 1_000_000 -volume = base_volume * (1 + 2 * np.abs(returns)) * np.random.uniform(0.8, 1.2, n_points) -rsi = 50 + 20 * np.sin(np.linspace(0, 8 * np.pi, n_points)) + np.random.normal(0, 5, n_points) -rsi = np.clip(rsi, 0, 100) - -df = pd.DataFrame({"date": dates, "price": price, "volume": volume / 1e6, "rsi": rsi, "zero": 0.0}) - -# Crosshair position at index 100 (mid-point) -crosshair_idx = 100 -crosshair_date = df["date"].iloc[crosshair_idx] -crosshair_price = df["price"].iloc[crosshair_idx] -crosshair_volume = df["volume"].iloc[crosshair_idx] -crosshair_rsi = df["rsi"].iloc[crosshair_idx] - -# Annotation data for each chart -crosshair_price_df = pd.DataFrame( - {"date": [crosshair_date], "price": [crosshair_price], "label": [f"${crosshair_price:.2f}"]} -) -crosshair_volume_df = pd.DataFrame( - {"date": [crosshair_date], "volume": [crosshair_volume], "label": [f"{crosshair_volume:.2f}M"]} -) -crosshair_rsi_df = pd.DataFrame({"date": [crosshair_date], "rsi": [crosshair_rsi], "label": [f"{crosshair_rsi:.1f}"]}) - -# Vertical crosshair line data (geom_path needs two points per line) -vline_price_df = pd.DataFrame( - {"date": [crosshair_date, crosshair_date], "y": [df["price"].min() * 0.98, df["price"].max() * 1.02]} -) -vline_volume_df = pd.DataFrame({"date": [crosshair_date, crosshair_date], "y": [0.0, df["volume"].max() * 1.1]}) -vline_rsi_df = pd.DataFrame({"date": [crosshair_date, crosshair_date], "y": [0.0, 100.0]}) - -# Shared theme for all sub-charts -common_theme = theme( - axis_title=element_text(size=18, color=INK), - axis_text=element_text(size=14, color=INK_SOFT), - plot_title=element_text(size=15, color=INK), - legend_text=element_text(size=14, color=INK_SOFT), - legend_title=element_text(size=14, color=INK), - axis_line=element_line(color=INK_SOFT, size=1), - panel_grid_major=element_line(color=GRID_COLOR, size=0.4), - panel_grid_minor=element_blank(), - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - panel_background=element_rect(fill=PAGE_BG), - legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT), -) - -# Chart 1: Price -price_chart = ( - ggplot(df, aes(x="date", y="price")) - + geom_line(color=IMPRINT[0], size=1.5, tooltips=layer_tooltips().line("@date").line("Price|$@price")) - + geom_path(data=vline_price_df, mapping=aes(x="date", y="y"), color=CROSSHAIR_COLOR, size=1.2, alpha=0.85) - + geom_point(data=crosshair_price_df, mapping=aes(x="date", y="price"), color=CROSSHAIR_COLOR, size=6) - + geom_text( - data=crosshair_price_df, - mapping=aes(x="date", y="price", label="label"), - hjust=0.5, - vjust=-1.2, - color=CROSSHAIR_COLOR, - size=12, - ) - + labs(x="", y="Price ($)", title="Price") - + theme_minimal() - + common_theme -) - -# Chart 2: Volume — geom_segment renders color= reliably vs geom_bar fill= quirk -volume_chart = ( - ggplot(df, aes(x="date", xend="date", y="zero", yend="volume")) - + geom_segment( - color=IMPRINT[1], size=2.0, alpha=0.85, tooltips=layer_tooltips().line("@date").line("Volume|@volume M") - ) - + geom_path(data=vline_volume_df, mapping=aes(x="date", y="y"), color=CROSSHAIR_COLOR, size=1.2, alpha=0.85) - + geom_point(data=crosshair_volume_df, mapping=aes(x="date", y="volume"), color=CROSSHAIR_COLOR, size=6) - + geom_text( - data=crosshair_volume_df, - mapping=aes(x="date", y="volume", label="label"), - hjust=0.5, - vjust=-1.2, - color=CROSSHAIR_COLOR, - size=12, - ) - + labs(x="", y="Volume (M)", title="Volume") - + theme_minimal() - + common_theme -) - -# Chart 3: RSI Indicator -rsi_chart = ( - ggplot(df, aes(x="date", y="rsi")) - + geom_line(color=IMPRINT[2], size=1.5, tooltips=layer_tooltips().line("@date").line("RSI|@rsi")) - + geom_hline(yintercept=70, linetype="dashed", color=INK_SOFT, size=0.8) - + geom_hline(yintercept=30, linetype="dashed", color=INK_SOFT, size=0.8) - + geom_path(data=vline_rsi_df, mapping=aes(x="date", y="y"), color=CROSSHAIR_COLOR, size=1.2, alpha=0.85) - + geom_point(data=crosshair_rsi_df, mapping=aes(x="date", y="rsi"), color=CROSSHAIR_COLOR, size=6) - + geom_text( - data=crosshair_rsi_df, - mapping=aes(x="date", y="rsi", label="label"), - hjust=0.5, - vjust=-1.2, - color=CROSSHAIR_COLOR, - size=12, - ) - + scale_y_continuous(limits=[0, 100]) - + labs(x="Date", y="RSI", title="RSI Indicator") - + theme_minimal() - + common_theme -) - -# Combine into stacked dashboard with 2:1:1 height ratio -TITLE = "dashboard-synchronized-crosshair · python · letsplot · anyplot.ai" -n = len(TITLE) -title_fontsize = round(18 * (67 / n)) if n > 67 else 18 - -combined = ( - gggrid([price_chart, volume_chart, rsi_chart], ncol=1, heights=[2, 1, 1]) - + ggsize(800, 450) - + ggtitle(TITLE) - + theme( - plot_title=element_text(size=title_fontsize, color=INK), - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - ) -) - -# Save PNG (scale=4 → 3200×1800 px) and HTML -ggsave(combined, filename=f"plot-{THEME}.png", path=".", scale=4) -ggsave(combined, filename=f"plot-{THEME}.html", path=".") diff --git a/plots/dashboard-synchronized-crosshair/implementations/python/matplotlib.py b/plots/dashboard-synchronized-crosshair/implementations/python/matplotlib.py deleted file mode 100644 index 0fc9fbb960..0000000000 --- a/plots/dashboard-synchronized-crosshair/implementations/python/matplotlib.py +++ /dev/null @@ -1,130 +0,0 @@ -""" anyplot.ai -dashboard-synchronized-crosshair: Synchronized Multi-Chart Dashboard -Library: matplotlib 3.10.9 | Python 3.13.13 -Quality: 88/100 | Updated: 2026-05-23 -""" - -import os -import sys - - -# Prevent this file from shadowing the installed matplotlib package -_here = os.path.dirname(os.path.abspath(__file__)) -sys.path = [p for p in sys.path if p and os.path.abspath(p) != _here] - -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd -from matplotlib.dates import DateFormatter, MonthLocator - - -# 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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" -BRAND = "#009E73" # Imprint palette position 1 — always first series -COLOR_2 = "#C475FD" # position 2 — RSI line -COLOR_RED = "#AE3030" # position 3 — semantic: down / overbought - -# Data — stock-like time series: price, volume, RSI -np.random.seed(42) -n_points = 200 -dates = pd.date_range("2024-01-01", periods=n_points, freq="B") - -price = 100 + np.cumsum(np.random.randn(n_points) * 2 + 0.05) -volume = np.abs(np.diff(price, prepend=price[0])) * 1e6 + np.random.uniform(0.5e6, 2e6, n_points) -rsi_raw = 50 + np.cumsum(np.random.randn(n_points) * 3) -rsi_raw = np.clip(rsi_raw, 10, 90) -rsi = 20 + (rsi_raw - rsi_raw.min()) / (rsi_raw.max() - rsi_raw.min()) * 60 - -# Canvas — square 2400×2400 for 3-panel vertical stack -fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(6, 6), dpi=400, facecolor=PAGE_BG, sharex=True) -fig.subplots_adjust(left=0.13, right=0.97, top=0.93, bottom=0.12, hspace=0.08) - -for ax in (ax1, ax2, ax3): - ax.set_facecolor(PAGE_BG) - ax.spines["top"].set_visible(False) - ax.spines["right"].set_visible(False) - ax.spines["left"].set_color(INK_SOFT) - ax.tick_params(axis="y", labelsize=8, colors=INK_SOFT, length=0) - ax.yaxis.grid(True, alpha=0.12, linewidth=0.6, color=INK) - ax.set_axisbelow(True) - -# Chart 1: Price -ax1.plot(dates, price, color=BRAND, linewidth=2.0, label="Price") -ax1.fill_between(dates, price, alpha=0.10, color=BRAND) -price_pad = (price.max() - price.min()) * 0.12 -ax1.set_ylim(price.min() - price_pad, price.max() + price_pad) -ax1.set_ylabel("Price ($)", fontsize=8, color=INK) -leg1 = ax1.legend(fontsize=7, loc="upper left") -leg1.get_frame().set_facecolor(ELEVATED_BG) -leg1.get_frame().set_edgecolor(INK_SOFT) -plt.setp(leg1.get_texts(), color=INK_SOFT) -ax1.spines["bottom"].set_visible(False) -ax1.set_title( - "dashboard-synchronized-crosshair · python · matplotlib · anyplot.ai", - fontsize=10, - fontweight="medium", - color=INK, - pad=6, -) - -# Chart 2: Volume — semantic coloring (green=up, red=down) -vol_colors = [BRAND if price[i] >= price[max(0, i - 1)] else COLOR_RED for i in range(n_points)] -ax2.bar(dates, volume / 1e6, color=vol_colors, alpha=0.75, width=1.3, label="Volume") -ax2.set_ylabel("Volume (M)", fontsize=8, color=INK) -leg2 = ax2.legend(fontsize=7, loc="upper left") -leg2.get_frame().set_facecolor(ELEVATED_BG) -leg2.get_frame().set_edgecolor(INK_SOFT) -plt.setp(leg2.get_texts(), color=INK_SOFT) -ax2.spines["bottom"].set_visible(False) - -# Chart 3: RSI -ax3.plot(dates, rsi, color=COLOR_2, linewidth=2.0, label="RSI") -ax3.axhline(y=70, color=COLOR_RED, linestyle="--", linewidth=1.0, alpha=0.7) -ax3.axhline(y=30, color=BRAND, linestyle="--", linewidth=1.0, alpha=0.7) -ax3.fill_between(dates, 30, 70, alpha=0.06, color=INK_SOFT) -ax3.text(dates[-1], 70.5, "Overbought", fontsize=6, color=COLOR_RED, ha="right", va="bottom") -ax3.text(dates[-1], 29.5, "Oversold", fontsize=6, color=BRAND, ha="right", va="top") -ax3.set_ylabel("RSI", fontsize=8, color=INK) -ax3.set_xlabel("Date", fontsize=8, color=INK) -ax3.set_ylim(10, 90) -ax3.spines["bottom"].set_color(INK_SOFT) -ax3.tick_params(axis="x", labelsize=8, colors=INK_SOFT, length=0) -ax3.xaxis.set_major_formatter(DateFormatter("%b '%y")) -ax3.xaxis.set_major_locator(MonthLocator(bymonth=[1, 4, 7, 10])) -leg3 = ax3.legend(fontsize=7, loc="upper left") -leg3.get_frame().set_facecolor(ELEVATED_BG) -leg3.get_frame().set_edgecolor(INK_SOFT) -plt.setp(leg3.get_texts(), color=INK_SOFT) - -# Synchronized crosshair demonstration — static snapshot at a meaningful date -crosshair_idx = 120 -crosshair_date = dates[crosshair_idx] -crosshair_price = price[crosshair_idx] -crosshair_vol = volume[crosshair_idx] / 1e6 -crosshair_rsi = rsi[crosshair_idx] - -for ax in (ax1, ax2, ax3): - ax.axvline(x=crosshair_date, color=INK_MUTED, linestyle="-", linewidth=1.0, alpha=0.6) - -ann_kw = { - "xytext": (6, 4), - "textcoords": "offset points", - "fontsize": 7, - "fontweight": "bold", - "color": INK, - "bbox": {"boxstyle": "round,pad=0.2", "facecolor": ELEVATED_BG, "edgecolor": INK_SOFT, "alpha": 0.92}, -} -ax1.annotate(f"${crosshair_price:.1f}", xy=(crosshair_date, crosshair_price), **ann_kw) -ax1.scatter([crosshair_date], [crosshair_price], color=BRAND, s=40, zorder=5, edgecolors=PAGE_BG, linewidths=0.5) - -ax2.annotate(f"{crosshair_vol:.1f}M", xy=(crosshair_date, crosshair_vol), **ann_kw) - -ax3.annotate(f"RSI {crosshair_rsi:.1f}", xy=(crosshair_date, crosshair_rsi), **ann_kw) -ax3.scatter([crosshair_date], [crosshair_rsi], color=COLOR_2, s=40, zorder=5, edgecolors=PAGE_BG, linewidths=0.5) - -plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG) diff --git a/plots/dashboard-synchronized-crosshair/implementations/python/plotly.py b/plots/dashboard-synchronized-crosshair/implementations/python/plotly.py deleted file mode 100644 index bbb4a3d0bc..0000000000 --- a/plots/dashboard-synchronized-crosshair/implementations/python/plotly.py +++ /dev/null @@ -1,177 +0,0 @@ -""" anyplot.ai -dashboard-synchronized-crosshair: Synchronized Multi-Chart Dashboard -Library: plotly 6.7.0 | Python 3.13.13 -Quality: 94/100 | Updated: 2026-05-23 -""" - -import os - -import numpy as np -import pandas as pd -import plotly.graph_objects as go -from plotly.subplots import make_subplots - - -# 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" -GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)" - -# Imprint palette -PRICE_COLOR = "#009E73" # position 1 - green -VOLUME_COLOR = "#C475FD" # position 2 - purple -RSI_COLOR = "#4467A3" # position 4 - sky blue - -# Data - Stock data: price, volume, and 14-period RSI over 200 trading days -np.random.seed(42) -n_points = 200 -dates = pd.date_range("2024-01-01", periods=n_points, freq="B") - -price_returns = np.random.normal(0.0005, 0.015, n_points) -price = 100 * np.cumprod(1 + price_returns) - -base_volume = 1_000_000 -volume = (base_volume * (1 + 2 * np.abs(price_returns) + np.random.uniform(0, 0.5, n_points))).astype(int) - -# Standard 14-period RSI using Wilder's smoothing method -period = 14 -price_changes = np.diff(price, prepend=np.nan) -gains = np.where(price_changes > 0, price_changes, 0.0) -losses = np.where(price_changes < 0, -price_changes, 0.0) - -avg_gain = np.full(n_points, np.nan) -avg_loss = np.full(n_points, np.nan) -avg_gain[period] = np.mean(gains[1 : period + 1]) -avg_loss[period] = np.mean(losses[1 : period + 1]) -for i in range(period + 1, n_points): - avg_gain[i] = (avg_gain[i - 1] * (period - 1) + gains[i]) / period - avg_loss[i] = (avg_loss[i - 1] * (period - 1) + losses[i]) / period - -rs = np.where(avg_loss == 0, np.inf, avg_gain / avg_loss) -rsi = np.where(np.isinf(rs), 100.0, 100.0 - 100.0 / (1 + rs)) - -df = pd.DataFrame({"date": dates, "price": price, "volume": volume, "rsi": rsi}) - -# Plot - 3 vertically stacked panels with shared x-axis -fig = make_subplots( - rows=3, - cols=1, - shared_xaxes=True, - vertical_spacing=0.08, - row_heights=[0.5, 0.25, 0.25], - subplot_titles=("Price (USD)", "Volume", "RSI (14-period)"), -) - -fig.add_trace( - go.Scatter( - x=df["date"], - y=df["price"], - mode="lines", - name="Price", - line=dict(color=PRICE_COLOR, width=2.5), - hovertemplate="$%{y:.2f}Price", - ), - row=1, - col=1, -) - -fig.add_trace( - go.Bar( - x=df["date"], - y=df["volume"], - name="Volume", - marker=dict(color=VOLUME_COLOR, opacity=0.8), - hovertemplate="%{y:,.0f}Volume", - ), - row=2, - col=1, -) - -fig.add_trace( - go.Scatter( - x=df["date"], - y=df["rsi"], - mode="lines", - name="RSI", - line=dict(color=RSI_COLOR, width=2.5), - hovertemplate="%{y:.1f}RSI", - ), - row=3, - col=1, -) - -# RSI reference lines — distinct dash patterns for colorblind accessibility -fig.add_hline(y=70, line_dash="dash", line_color="#AE3030", line_width=1.5, row=3, col=1) -fig.add_hline(y=30, line_dash="dot", line_color="#009E73", line_width=1.5, row=3, col=1) - -# Style -fig.update_layout( - autosize=False, - template="plotly_white", - paper_bgcolor=PAGE_BG, - plot_bgcolor=PAGE_BG, - title=dict( - text="dashboard-synchronized-crosshair · python · plotly · anyplot.ai", - font=dict(size=16, color=INK), - x=0.5, - xanchor="center", - ), - hovermode="x unified", - hoverlabel=dict(font_size=10, bgcolor=ELEVATED_BG, bordercolor=INK_SOFT, font=dict(color=INK)), - showlegend=False, - margin=dict(l=100, r=40, t=80, b=60), - font=dict(color=INK), -) - -for annotation in fig["layout"]["annotations"]: - annotation["font"] = dict(size=12, color=INK_SOFT) - -fig.update_yaxes( - title_text="Price (USD)", - title_font=dict(size=12, color=INK), - tickfont=dict(size=10, color=INK_SOFT), - gridcolor=GRID, - linecolor=INK_SOFT, - zerolinecolor=GRID, - row=1, - col=1, -) -fig.update_yaxes( - title_text="Volume", - title_font=dict(size=12, color=INK), - tickfont=dict(size=10, color=INK_SOFT), - gridcolor=GRID, - linecolor=INK_SOFT, - zerolinecolor=GRID, - row=2, - col=1, -) -fig.update_yaxes( - title_text="RSI", - title_font=dict(size=12, color=INK), - tickfont=dict(size=10, color=INK_SOFT), - gridcolor=GRID, - linecolor=INK_SOFT, - zerolinecolor=GRID, - range=[0, 100], - row=3, - col=1, -) - -fig.update_xaxes(gridcolor=GRID, linecolor=INK_SOFT) -fig.update_xaxes( - title_text="Date", title_font=dict(size=12, color=INK), tickfont=dict(size=10, color=INK_SOFT), row=3, col=1 -) - -# Spike lines for synchronized crosshair across all panels -fig.update_xaxes( - showspikes=True, spikemode="across", spikesnap="cursor", spikethickness=1.5, spikecolor=INK_SOFT, spikedash="solid" -) -fig.update_yaxes(showspikes=True, spikethickness=1, spikecolor=INK_SOFT, spikedash="dot") - -# Save -fig.write_image(f"plot-{THEME}.png", width=800, height=450, scale=4) -fig.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn") diff --git a/plots/dashboard-synchronized-crosshair/implementations/python/plotnine.py b/plots/dashboard-synchronized-crosshair/implementations/python/plotnine.py deleted file mode 100644 index 09b53f6689..0000000000 --- a/plots/dashboard-synchronized-crosshair/implementations/python/plotnine.py +++ /dev/null @@ -1,131 +0,0 @@ -""" anyplot.ai -dashboard-synchronized-crosshair: Synchronized Multi-Chart Dashboard -Library: plotnine 0.15.4 | Python 3.13.13 -Quality: 87/100 | Updated: 2026-05-23 -""" - -import os - -import numpy as np -import pandas as pd -from plotnine import ( - aes, - element_blank, - element_line, - element_rect, - element_text, - facet_wrap, - geom_label, - geom_line, - geom_point, - geom_vline, - ggplot, - labs, - scale_x_datetime, - scale_y_continuous, - theme, - theme_minimal, -) - - -# 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" - -BRAND = "#009E73" # Imprint palette 1 — data lines -ACCENT = "#AE3030" # Imprint palette 3 — crosshair marker - -# Data — seasonal stock: trend + annual cycle + quarterly earnings ripple + mild noise -np.random.seed(42) -n_points = 200 -dates = pd.date_range("2024-01-01", periods=n_points, freq="B") - -t = np.arange(n_points) / n_points -annual_cycle = 8 * np.sin(2 * np.pi * t - np.pi / 2) # Q2 trough, Q4 peak -quarterly_cycle = 3 * np.sin(8 * np.pi * t) # earnings-season ripples -trend = 20 * t -noise = np.random.normal(0, 0.4, n_points).cumsum() -price = 100 + trend + annual_cycle + quarterly_cycle + noise - -earnings_effect = 0.5 * np.abs(np.sin(4 * np.pi * t)) -vol_noise = np.abs(np.random.normal(0, 0.15, n_points)) -volume = 1.0 + earnings_effect + vol_noise # millions - -momentum = np.zeros(n_points) -momentum[14:] = price[14:] - price[:-14] -rsi = 50 + 30 * np.tanh(momentum / (2 * momentum.std() + 1e-8)) -rsi = np.clip(rsi, 10, 90) - -metric_order = pd.CategoricalDtype(categories=["Price ($)", "Volume (M)", "RSI"], ordered=True) - -df = pd.DataFrame( - { - "date": np.tile(dates, 3), - "value": np.concatenate([price, volume, rsi]), - "metric": ["Price ($)"] * n_points + ["Volume (M)"] * n_points + ["RSI"] * n_points, - } -) -df["metric"] = df["metric"].astype(metric_order) - -# Crosshair at a local seasonal inflection point -crosshair_idx = 100 -crosshair_date = dates[crosshair_idx] - -annotation_df = pd.DataFrame( - { - "date": [crosshair_date] * 3, - "value": [price[crosshair_idx], volume[crosshair_idx], rsi[crosshair_idx]], - "metric": ["Price ($)", "Volume (M)", "RSI"], - } -) -annotation_df["metric"] = annotation_df["metric"].astype(metric_order) - -label_df = pd.DataFrame( - { - "date": [crosshair_date] * 3, - "value": [price[crosshair_idx] * 1.05, volume[crosshair_idx] + 0.35, rsi[crosshair_idx] + 5], - "metric": ["Price ($)", "Volume (M)", "RSI"], - "label_text": [ - f"${price[crosshair_idx]:.2f}", - f"{volume[crosshair_idx]:.2f}M", - f"RSI: {rsi[crosshair_idx]:.1f}", - ], - } -) -label_df["metric"] = label_df["metric"].astype(metric_order) - -# Plot -plot = ( - ggplot(df, aes(x="date", y="value")) - + geom_line(color=BRAND, size=1.0, alpha=0.9) - + geom_vline(xintercept=crosshair_date, color=ACCENT, size=0.8, linetype="dashed") - + geom_point(data=annotation_df, mapping=aes(x="date", y="value"), color=ACCENT, size=3.0) - + geom_label( - data=label_df, mapping=aes(x="date", y="value", label="label_text"), color=ACCENT, fill=ELEVATED_BG, size=8 - ) - + facet_wrap("~metric", ncol=1, scales="free_y") - + scale_x_datetime(date_breaks="1 month", date_labels="%b %Y") - + scale_y_continuous(breaks=lambda lims: np.linspace(lims[0], lims[1], 4).tolist()) - + labs(title="dashboard-synchronized-crosshair · python · plotnine · anyplot.ai", x="Date", y="") - + theme_minimal() - + theme( - figure_size=(6, 6), - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - panel_background=element_rect(fill=PAGE_BG), - panel_grid_major=element_line(color=INK, size=0.3, alpha=0.10), - panel_grid_minor=element_blank(), - plot_title=element_text(size=10, weight="bold", ha="center", color=INK), - axis_title_x=element_text(size=9, color=INK), - axis_title_y=element_blank(), - axis_text=element_text(size=7, color=INK_SOFT), - axis_text_x=element_text(angle=45, ha="right"), - strip_text=element_text(size=9, weight="bold", color=INK), - strip_background=element_rect(fill=PAGE_BG, color=None), - panel_spacing=0.2, - ) -) - -plot.save(f"plot-{THEME}.png", dpi=400, width=6, height=6, units="in", verbose=False) diff --git a/plots/dashboard-synchronized-crosshair/implementations/python/pygal.py b/plots/dashboard-synchronized-crosshair/implementations/python/pygal.py deleted file mode 100644 index 22b45e828e..0000000000 --- a/plots/dashboard-synchronized-crosshair/implementations/python/pygal.py +++ /dev/null @@ -1,284 +0,0 @@ -""" anyplot.ai -dashboard-synchronized-crosshair: Synchronized Multi-Chart Dashboard -Library: pygal 3.1.0 | Python 3.13.13 -Quality: 87/100 | Updated: 2026-05-23 -""" - -import io -import json -import os -import sys -from datetime import date, timedelta - - -# Prevent this file (pygal.py) from shadowing the installed pygal package -_self_dir = os.path.dirname(os.path.abspath(__file__)) -sys.path = [p for p in sys.path if p != _self_dir] - -import numpy as np -import pygal -from PIL import Image -from pygal.style import Style - - -# 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_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -IMPRINT = ("#009E73", "#C475FD", "#AE3030", "#4467A3", "#99B314", "#954477", "#BD8233") - -# Data: 200 trading days with price, volume, and RSI -np.random.seed(42) -n_days = 200 - -# Realistic trading weekdays starting 2024-01-02 -_start = date(2024, 1, 2) -dates = [] -_d = _start -while len(dates) < n_days: - if _d.weekday() < 5: - dates.append(_d.strftime("%b %d")) - _d += timedelta(days=1) - -returns = np.random.randn(n_days) * 0.02 -price = 100 * np.cumprod(1 + returns) -volume = (np.abs(np.random.randn(n_days)) * 1e6 + 5e5) * (1 + np.abs(returns) * 10) -rsi = np.clip(50 + np.cumsum(np.random.randn(n_days) * 2), 0, 100) - -# Panel dimensions — width=3200, heights sum to 1800 -W, H_PRICE, H_VOLUME, H_RSI = 3200, 680, 540, 580 - -# Price chart — full anyplot title, pos-1 green (#009E73) -price_style = Style( - background=PAGE_BG, - plot_background=PAGE_BG, - foreground=INK, - foreground_strong=INK, - foreground_subtle=INK_MUTED, - colors=IMPRINT, - title_font_size=66, - label_font_size=56, - major_label_font_size=44, - legend_font_size=44, - value_font_size=36, - stroke_width=2.5, -) -price_chart = pygal.Line( - width=W, - height=H_PRICE, - style=price_style, - title="dashboard-synchronized-crosshair · python · pygal · anyplot.ai", - y_title="Price ($)", - show_x_labels=False, - show_legend=False, - show_y_guides=True, - show_x_guides=False, - dots_size=0, - fill=False, - truncate_label=-1, - margin_top=60, - margin_bottom=10, - margin_left=180, - margin_right=60, -) -price_chart.add("Price", price.tolist()) -price_chart.x_labels = dates - -# Volume chart — pos-2 purple (#C475FD) -volume_style = Style( - background=PAGE_BG, - plot_background=PAGE_BG, - foreground=INK, - foreground_strong=INK, - foreground_subtle=INK_MUTED, - colors=("#C475FD", "#009E73", "#AE3030", "#4467A3", "#99B314", "#954477", "#BD8233"), - title_font_size=60, - label_font_size=56, - major_label_font_size=44, - legend_font_size=44, - value_font_size=36, - stroke_width=2.5, -) -volume_chart = pygal.Line( - width=W, - height=H_VOLUME, - style=volume_style, - title="Trading Volume", - y_title="Volume (M)", - show_x_labels=False, - show_legend=False, - show_y_guides=True, - show_x_guides=False, - dots_size=0, - fill=True, - truncate_label=-1, - margin_top=30, - margin_bottom=10, - margin_left=180, - margin_right=60, -) -volume_chart.add("Volume", (volume / 1e6).tolist()) -volume_chart.x_labels = dates - -# RSI chart — pos-3 red (#AE3030), x-axis visible on bottom panel -# Reference lines at 30 (oversold) and 70 (overbought) use INK_MUTED -rsi_style = Style( - background=PAGE_BG, - plot_background=PAGE_BG, - foreground=INK, - foreground_strong=INK, - foreground_subtle=INK_MUTED, - colors=("#AE3030", INK_MUTED, INK_MUTED, "#4467A3", "#99B314", "#954477", "#BD8233"), - title_font_size=60, - label_font_size=56, - major_label_font_size=44, - legend_font_size=44, - value_font_size=36, - stroke_width=2.5, -) -rsi_chart = pygal.Line( - width=W, - height=H_RSI, - style=rsi_style, - title="RSI Indicator", - x_title="Trading Day", - y_title="RSI (0–100)", - show_x_labels=True, - show_legend=False, - show_y_guides=True, - show_x_guides=False, - dots_size=0, - fill=False, - truncate_label=-1, - x_labels_major_count=10, - show_minor_x_labels=False, - x_label_rotation=20, - range=(0, 100), - margin_top=30, - margin_bottom=40, - margin_left=180, - margin_right=60, -) -rsi_chart.add("RSI", rsi.tolist()) -# Overbought / oversold reference lines — muted horizontal guides at 70 and 30 -rsi_chart.add("Overbought (70)", [70] * n_days) -rsi_chart.add("Oversold (30)", [30] * n_days) -rsi_chart.x_labels = dates -# Explicit y-ticks at semantically meaningful RSI thresholds (avoids crowded auto ticks) -rsi_chart.y_labels = [0, 30, 50, 70, 100] - -# Render SVGs for the interactive HTML -price_svg = price_chart.render(is_unicode=True) -volume_svg = volume_chart.render(is_unicode=True) -rsi_svg = rsi_chart.render(is_unicode=True) - -price_js = price.tolist() -volume_js = (volume / 1e6).tolist() -rsi_js = rsi.tolist() -dates_js = json.dumps(dates) - -html = f""" - - - -dashboard-synchronized-crosshair · python · pygal · anyplot.ai - - - -
-
{price_svg}
-
-
{volume_svg}
-
-
{rsi_svg}
-
-
-
- - -""" - -with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f: - f.write(html) - -# PNG: composite three panels into exactly 3200×1800 -bg_rgb = tuple(int(PAGE_BG[i : i + 2], 16) for i in (1, 3, 5)) -muted_rgb = tuple(int(INK_MUTED[i : i + 2], 16) for i in (1, 3, 5)) - -price_img = Image.open(io.BytesIO(price_chart.render_to_png())).resize((W, H_PRICE), Image.LANCZOS) -volume_img = Image.open(io.BytesIO(volume_chart.render_to_png())).resize((W, H_VOLUME), Image.LANCZOS) -rsi_img = Image.open(io.BytesIO(rsi_chart.render_to_png())).resize((W, H_RSI), Image.LANCZOS) - -canvas = Image.new("RGB", (W, H_PRICE + H_VOLUME + H_RSI), bg_rgb) -canvas.paste(price_img, (0, 0)) -canvas.paste(volume_img, (0, H_PRICE)) -canvas.paste(rsi_img, (0, H_PRICE + H_VOLUME)) - -# Subtle 1px separator lines between panels -from PIL import ImageDraw - - -draw = ImageDraw.Draw(canvas) -draw.line([(0, H_PRICE), (W, H_PRICE)], fill=muted_rgb, width=1) -draw.line([(0, H_PRICE + H_VOLUME), (W, H_PRICE + H_VOLUME)], fill=muted_rgb, width=1) - -canvas.save(f"plot-{THEME}.png", dpi=(300, 300)) diff --git a/plots/dashboard-synchronized-crosshair/implementations/python/seaborn.py b/plots/dashboard-synchronized-crosshair/implementations/python/seaborn.py deleted file mode 100644 index 90bb2acdf5..0000000000 --- a/plots/dashboard-synchronized-crosshair/implementations/python/seaborn.py +++ /dev/null @@ -1,179 +0,0 @@ -""" anyplot.ai -dashboard-synchronized-crosshair: Synchronized Multi-Chart Dashboard -Library: seaborn 0.13.2 | Python 3.13.13 -Quality: 88/100 | Updated: 2026-05-23 -""" - -import os - -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd -import seaborn as sns - - -# Theme -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" - -sns.set_theme( - style="ticks", - rc={ - "figure.facecolor": PAGE_BG, - "axes.facecolor": PAGE_BG, - "axes.edgecolor": INK_SOFT, - "axes.labelcolor": INK, - "text.color": INK, - "xtick.color": INK_SOFT, - "ytick.color": INK_SOFT, - "grid.color": INK, - "grid.alpha": 0.10, - "legend.facecolor": ELEVATED_BG, - "legend.edgecolor": INK_SOFT, - }, -) -sns.set_context("notebook", font_scale=1.0) - -# Imprint palette — canonical order: green, purple, red, sky-blue, lime, … -IMPRINT = ["#009E73", "#C475FD", "#AE3030", "#4467A3", "#99B314", "#954477", "#BD8233"] -sns.set_palette(IMPRINT) - -COLOR_PRICE = "#009E73" # position 1 -COLOR_VOLUME = "#C475FD" # position 2 -COLOR_RSI = "#AE3030" # position 3 -COLOR_OVERBOUGHT = "#AE3030" -COLOR_OVERSOLD = "#99B314" -COLOR_CROSSHAIR = "#4467A3" - -# Data — stock-like metrics over 200 trading days -np.random.seed(42) -n_points = 200 -dates = pd.date_range("2024-01-01", periods=n_points, freq="B") - -price_returns = np.random.randn(n_points) * 0.02 -price = 100 * np.cumprod(1 + price_returns) -volume = np.abs(price_returns) * 1e8 + np.random.exponential(5e6, n_points) -rsi = np.clip(50 + np.cumsum(np.random.randn(n_points) * 3), 20, 80) -ma20 = pd.Series(price).rolling(20, min_periods=1).mean().values - -df = pd.DataFrame({"date": dates, "price": price, "volume": volume, "rsi": rsi, "ma20": ma20}) - -# Crosshair position for static demonstration -crosshair_idx = 120 -crosshair_date = df["date"].iloc[crosshair_idx] -crosshair_price = df["price"].iloc[crosshair_idx] -crosshair_volume = df["volume"].iloc[crosshair_idx] -crosshair_rsi = df["rsi"].iloc[crosshair_idx] - -# Figure: 3 stacked panels sharing x-axis (landscape 3200×1800) -fig, axes = plt.subplots( - 3, 1, figsize=(8, 4.5), dpi=400, sharex=True, facecolor=PAGE_BG, gridspec_kw={"height_ratios": [2, 1, 1]} -) -ax1, ax2, ax3 = axes - -# Chart 1: Price — line with 20-day moving average and area fill -ax1.set_facecolor(PAGE_BG) -sns.lineplot(data=df, x="date", y="price", ax=ax1, color=COLOR_PRICE, linewidth=2.0, errorbar=None, label="Price") -sns.lineplot( - data=df, - x="date", - y="ma20", - ax=ax1, - color=INK_SOFT, - linewidth=1.0, - linestyle="--", - alpha=0.7, - errorbar=None, - label="20-day MA", -) -ax1.fill_between(df["date"], df["price"], alpha=0.15, color=COLOR_PRICE) -ax1.axvline(x=crosshair_date, color=COLOR_CROSSHAIR, linewidth=1.2, linestyle="--", alpha=0.85) -ax1.scatter( - [crosshair_date], [crosshair_price], s=60, color=COLOR_CROSSHAIR, zorder=5, edgecolors=PAGE_BG, linewidth=0.8 -) -ax1.annotate( - f"${crosshair_price:.2f}", - xy=(crosshair_date, crosshair_price), - xytext=(6, 6), - textcoords="offset points", - fontsize=9, - fontweight="bold", - color="white", - bbox={"boxstyle": "round,pad=0.25", "facecolor": COLOR_CROSSHAIR, "edgecolor": "none", "alpha": 0.92}, -) -ax1.set_ylabel("Price ($)", fontsize=10, color=INK) -ax1.tick_params(axis="y", labelsize=8) -ax1.tick_params(bottom=False) -ax1.legend(loc="upper left", fontsize=8, framealpha=0.9) -sns.despine(ax=ax1, bottom=True) -ax1.spines["left"].set_color(INK_SOFT) -ax1.yaxis.grid(True, alpha=0.10, linewidth=0.6) - -# Chart 2: Volume — seaborn line plot with area fill -ax2.set_facecolor(PAGE_BG) -sns.lineplot(data=df, x="date", y="volume", ax=ax2, color=COLOR_VOLUME, linewidth=1.5, errorbar=None) -ax2.fill_between(df["date"], df["volume"], alpha=0.22, color=COLOR_VOLUME) -ax2.axvline(x=crosshair_date, color=COLOR_CROSSHAIR, linewidth=1.2, linestyle="--", alpha=0.85) -ax2.scatter( - [crosshair_date], [crosshair_volume], s=60, color=COLOR_CROSSHAIR, zorder=5, edgecolors=PAGE_BG, linewidth=0.8 -) -ax2.annotate( - f"{crosshair_volume / 1e6:.1f}M", - xy=(crosshair_date, crosshair_volume), - xytext=(6, 6), - textcoords="offset points", - fontsize=9, - fontweight="bold", - color="white", - bbox={"boxstyle": "round,pad=0.25", "facecolor": COLOR_CROSSHAIR, "edgecolor": "none", "alpha": 0.92}, -) -ax2.set_ylabel("Volume", fontsize=10, color=INK) -ax2.tick_params(axis="y", labelsize=8) -ax2.tick_params(bottom=False) -ax2.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f"{x / 1e6:.0f}M")) -sns.despine(ax=ax2, bottom=True) -ax2.spines["left"].set_color(INK_SOFT) -ax2.yaxis.grid(True, alpha=0.10, linewidth=0.6) - -# Chart 3: RSI indicator with overbought/oversold reference bands -ax3.set_facecolor(PAGE_BG) -sns.lineplot(data=df, x="date", y="rsi", ax=ax3, color=COLOR_RSI, linewidth=2.0, errorbar=None) -ax3.axhline(y=70, color=COLOR_OVERBOUGHT, linestyle=":", linewidth=1.2, alpha=0.85, label="Overbought (70)") -ax3.axhline(y=30, color=COLOR_OVERSOLD, linestyle=":", linewidth=1.2, alpha=0.85, label="Oversold (30)") -ax3.fill_between(df["date"], 30, 70, alpha=0.08, color=INK_SOFT) -ax3.set_ylim(15, 85) -ax3.axvline(x=crosshair_date, color=COLOR_CROSSHAIR, linewidth=1.2, linestyle="--", alpha=0.85) -ax3.scatter([crosshair_date], [crosshair_rsi], s=60, color=COLOR_CROSSHAIR, zorder=5, edgecolors=PAGE_BG, linewidth=0.8) -ax3.annotate( - f"{crosshair_rsi:.1f}", - xy=(crosshair_date, crosshair_rsi), - xytext=(6, 6), - textcoords="offset points", - fontsize=9, - fontweight="bold", - color="white", - bbox={"boxstyle": "round,pad=0.25", "facecolor": COLOR_CROSSHAIR, "edgecolor": "none", "alpha": 0.92}, -) -ax3.set_ylabel("RSI", fontsize=10, color=INK) -ax3.set_xlabel("Date", fontsize=10, color=INK) -ax3.tick_params(axis="both", labelsize=8) -ax3.legend(loc="upper right", fontsize=8, framealpha=0.9) -sns.despine(ax=ax3) -ax3.spines["left"].set_color(INK_SOFT) -ax3.spines["bottom"].set_color(INK_SOFT) -ax3.yaxis.grid(True, alpha=0.10, linewidth=0.6) - -# X-axis date formatting on bottom panel only -ax3.xaxis.set_major_locator(plt.MaxNLocator(6)) -plt.setp(ax3.xaxis.get_majorticklabels(), rotation=30, ha="right") - -# Title -fig.suptitle( - "dashboard-synchronized-crosshair · python · seaborn · anyplot.ai", fontsize=12, fontweight="medium", color=INK -) - -fig.subplots_adjust(top=0.93, bottom=0.12, left=0.12, right=0.97, hspace=0.10) -plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG) diff --git a/plots/dashboard-synchronized-crosshair/implementations/r/ggplot2.R b/plots/dashboard-synchronized-crosshair/implementations/r/ggplot2.R deleted file mode 100644 index 4b05245405..0000000000 --- a/plots/dashboard-synchronized-crosshair/implementations/r/ggplot2.R +++ /dev/null @@ -1,141 +0,0 @@ -#' anyplot.ai -#' dashboard-synchronized-crosshair: Synchronized Multi-Chart Dashboard -#' Library: ggplot2 3.5.1 | R 4.4.1 -#' Quality: 89/100 | Created: 2026-05-23 - -library(ggplot2) -library(dplyr) -library(scales) -library(ragg) - -set.seed(42) - -THEME <- Sys.getenv("ANYPLOT_THEME", "light") -PAGE_BG <- if (THEME == "light") "#FAF8F1" else "#1A1A17" -ELEVATED_BG <- if (THEME == "light") "#FFFDF6" else "#242420" -INK <- if (THEME == "light") "#1A1A17" else "#F0EFE8" -INK_SOFT <- if (THEME == "light") "#4A4A44" else "#B8B7B0" -INK_MUTED <- if (THEME == "light") "#6B6A63" else "#A8A79F" - -IMPRINT <- c( - "#009E73", "#C475FD", "#AE3030", "#4467A3", - "#99B314", "#954477", "#BD8233" -) - -# 200 trading days of synthetic stock data -n <- 200 -all_days <- seq.Date(as.Date("2024-01-02"), by = "day", length.out = 300) -dates <- all_days[!weekdays(all_days) %in% c("Saturday", "Sunday")][seq_len(n)] - -log_ret <- rnorm(n, mean = 0.0005, sd = 0.015) -price <- 100 * cumprod(1 + log_ret) - -vol_base <- exp(rnorm(n, log(2.5), 0.45)) -volume <- vol_base * (1 + 4 * abs(log_ret) / max(abs(log_ret))) - -# RSI 14-period (simple moving average approximation) -delta <- c(0, diff(price)) -gains <- pmax(delta, 0) -losses <- pmax(-delta, 0) -avg_g <- as.numeric(stats::filter(gains, rep(1 / 14, 14), sides = 1)) -avg_l <- as.numeric(stats::filter(losses, rep(1 / 14, 14), sides = 1)) -rs <- ifelse(avg_l == 0, Inf, avg_g / avg_l) -rsi <- 100 - 100 / (1 + rs) - -df <- tibble::tibble( - date = rep(dates, 3), - value = c(price, volume, rsi), - metric = factor( - rep(c("Price (USD)", "Volume (M)", "RSI (14)"), each = n), - levels = c("Price (USD)", "Volume (M)", "RSI (14)") - ) -) - -metric_colors <- c( - "Price (USD)" = IMPRINT[1], - "Volume (M)" = IMPRINT[2], - "RSI (14)" = IMPRINT[3] -) - -event_date <- dates[120] - -p <- ggplot(dplyr::filter(df, !is.na(value)), aes(x = date, y = value)) + - geom_vline( - xintercept = as.numeric(event_date), - color = INK_MUTED, - linewidth = 0.5, - linetype = "dashed" - ) + - geom_line(aes(color = metric), linewidth = 1.1) + - geom_text( - data = tibble::tibble( - date = event_date, - y_pos = Inf, - metric = factor("Price (USD)", levels = c("Price (USD)", "Volume (M)", "RSI (14)")) - ), - aes(x = date, y = y_pos, label = "Event"), - color = INK_MUTED, - size = 2.5, - hjust = -0.15, - vjust = 1.5, - inherit.aes = FALSE - ) + - facet_wrap(~metric, ncol = 1, scales = "free_y", strip.position = "right") + - scale_color_manual(values = metric_colors, guide = "none") + - scale_x_date( - date_labels = "%b '%y", - date_breaks = "2 months", - expand = expansion(mult = 0.01) - ) + - scale_y_continuous(labels = label_number(accuracy = 0.1)) + - labs( - title = "dashboard-synchronized-crosshair · r · ggplot2 · anyplot.ai", - subtitle = "Tech stock 2024 — price, volume & RSI across 200 trading days", - x = NULL, - y = NULL - ) + - theme_minimal(base_size = 8) + - theme( - plot.background = element_rect(fill = PAGE_BG, color = PAGE_BG), - panel.background = element_rect(fill = PAGE_BG, color = NA), - panel.grid.major = element_line( - color = adjustcolor(INK_SOFT, alpha.f = 0.3), - linewidth = 0.2 - ), - panel.grid.minor = element_blank(), - panel.border = element_blank(), - axis.line.x.bottom = element_line(color = INK_SOFT, linewidth = 0.3), - axis.line.y.left = element_line(color = INK_SOFT, linewidth = 0.3), - panel.spacing = unit(0.4, "cm"), - axis.text = element_text(color = INK_SOFT, size = 8), - axis.title = element_text(color = INK, size = 10), - plot.title = element_text( - color = INK, - size = 12, - face = "bold", - margin = margin(b = 4) - ), - plot.subtitle = element_text( - color = INK_SOFT, - size = 9, - margin = margin(b = 8) - ), - plot.margin = margin(t = 12, r = 8, b = 8, l = 8), - strip.text = element_text(color = INK, size = 9, face = "bold"), - strip.background = element_rect( - fill = ELEVATED_BG, - color = INK_SOFT, - linewidth = 0.3 - ), - strip.placement = "outside" - ) - -ggsave( - filename = sprintf("plot-%s.png", THEME), - plot = p, - device = ragg::agg_png, - width = 8, - height = 4.5, - units = "in", - dpi = 400 -) diff --git a/plots/dashboard-synchronized-crosshair/metadata/julia/makie.yaml b/plots/dashboard-synchronized-crosshair/metadata/julia/makie.yaml deleted file mode 100644 index 9d3f872c56..0000000000 --- a/plots/dashboard-synchronized-crosshair/metadata/julia/makie.yaml +++ /dev/null @@ -1,263 +0,0 @@ -library: makie -language: julia -specification_id: dashboard-synchronized-crosshair -created: '2026-05-23T12:57:24Z' -updated: '2026-05-23T20:28:13Z' -generated_by: claude-sonnet -workflow_run: 26333070350 -issue: 3784 -language_version: 1.11.9 -library_version: 0.22.10 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/julia/makie/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/julia/makie/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 86 -review: - strengths: - - Excellent use of linkxaxes! and stacked Axis layout to correctly implement the - synchronized dashboard pattern for a static library - - RSI overbought/oversold zone fills via band! with very low alpha (0.10) add meaningful - context without obscuring data - - 'Comprehensive theme adaptation: all chrome elements (title, labels, ticks, spines, - grid, zone fills) properly respond to ANYPLOT_THEME' - - Custom ytickformat callback for K-notation volume labels is a clean Makie-idiomatic - detail - - Row proportions (42/30/28%) and markers with white strokes show genuine attention - to visual polish - weaknesses: - - Tick label sizes (xticklabelsize, yticklabelsize) not explicitly set on any of - the three Axis objects — add xticklabelsize = 12, yticklabelsize = 12 to each - Axis to match style-guide defaults - - 'Palette ordering concern: volume bars render as bright sky blue and RSI line - as purple in both renders, but IMPRINT_PALETTE[2]=#9418DB (purple) and IMPRINT_PALETTE[3]=#B71D27 - (red) are assigned respectively — rendered colors do not match code assignments; - consider using IMPRINT_PALETTE[4]=#16B8F3 for volume and IMPRINT_PALETTE[2]=#9418DB - for RSI to match what is actually rendered, and avoid red RSI line clashing with - red overbought zone fill' - - Crosshair RSI value is 51.7 (neutral territory), contradicting the intended RSI - overbought peak narrative — if the rolling-mean RSI with seed 42 never produces - RSI > 70, increase mean return from 0.0008 to ~0.003 for a more volatile series, - or relabel the crosshair as 'RSI maximum' rather than implying overbought context - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct, not pure white - Chrome: Title "dashboard-synchronized-crosshair · julia · makie · anyplot.ai" in dark INK text, clearly readable. Axis labels (Price (USD), Volume, RSI (14), Trading Day) in dark INK. Tick labels in INK_SOFT dark color. Panel labels (PRICE, VOLUME, RSI) in top-left of each panel. All text clearly readable against light background. - Data: Price line in #009E73 green (palette position 1, correct). Volume bars in bright sky blue/teal color. RSI line in purple/violet. Zone fills at 10% alpha (light red tint in overbought zone 70-100, light green tint in oversold zone 0-30). Dashed crosshair at day ~120 spanning all panels. Value markers (circle dots) on each series at crosshair. Annotations: "100.1" (price), "276K" (volume), "51.7 <- peak" (RSI). - Legibility verdict: PASS — all text readable at full resolution in light theme - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct, not pure black - Chrome: Title, axis labels, tick labels, panel labels all render in light-colored text (INK flips to #F0EFE8, INK_SOFT to #B8B7B0). No dark-on-dark failures detected. Grid lines remain subtle. Spines (left, bottom) in INK_SOFT light color. - Data: Price line in #009E73 green — identical to light render (correct, data colors unchanged). Volume bars in same bright sky blue/teal as light render. RSI line in same purple/violet. Zone fills, crosshair, markers, and annotations all consistent with light render. Data palette is theme-independent as required. - Legibility verdict: PASS — all text readable at full resolution in dark theme, no dark-on-dark failures - criteria_checklist: - visual_quality: - score: 27 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 6 - max: 8 - passed: true - comment: Title, axis labels, chrome explicitly sized. Missing xticklabelsize/yticklabelsize - on all three Axis objects; relies on Figure fontsize=14 default for ticks. - Readable in both themes. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: Panel labels, zone labels, and value annotations well-separated. - No collisions. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Lines at linewidth=2.0 (4 effective px), bars filled, scatter markers - at markersize=10 (20 effective px). All elements clearly visible at 3200x1800. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Three series use distinct palette colors, CVD-safe. Zone fills at - 10% alpha do not obscure data. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Canvas gate passed. Three panels at 42/30/28% row proportions fill - the canvas well. Aligned grids and shared x-axis create clear visual connection. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Price (USD), Volume (K-formatted), RSI (14), Trading Day all descriptive - with units or context. Title correct. - - id: VQ-07 - name: Palette Compliance - score: 1 - max: 2 - passed: false - comment: 'First series correctly uses #009E73. Volume bars appear as bright - sky blue and RSI as purple/violet in both renders, not matching IMPRINT_PALETTE[2]=#9418DB - (purple) for volume and IMPRINT_PALETTE[3]=#B71D27 (red) for RSI per code. - Background and chrome are theme-correct.' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Clearly above defaults: theme-adaptive chrome, zone fills, crosshair - with value markers, panel labels, K-formatted volume ticks, markers with - white strokes. Not quite publication-ready.' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Top/right spines removed, y-only grid at 10% opacity, row gap of - 5, zone fills at 10% alpha, dashed reference lines at 50% opacity. Good - refinement. - - id: DE-03 - name: Data Storytelling - score: 3 - max: 6 - passed: false - comment: Zone fills and reference lines are good. However, crosshair at RSI=51.7 - (neutral territory) fails the overbought peak narrative. The <- peak annotation - is misleading. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct stacked multi-panel dashboard with three linked panels. Static - crosshair via vlines! is appropriate for static library. - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Synchronized crosshair, shared x-axis, independent y-axes, value - annotations, three data series. All spec features implemented. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Trading Day on shared X; Price (USD), Volume, RSI (14) on independent - Y axes. Correct. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title format exact match. No standalone legend needed; panel labels - serve orientation role. - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'Demonstrates all dashboard aspects: line chart, bar chart, oscillator - with zones, synchronized crosshair, value read-out, overbought/oversold - zones.' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Stock trading dashboard with price, volume, RSI is canonical and - neutral. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Price ~100 +/-15%, volume in hundreds of thousands, RSI 0-100. All - scales realistic. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Clean linear flow, no functions or classes. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Random.seed!(42) - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: CairoMakie, Colors, Random, Statistics all used. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: No fake UI elements. RSI for-loop is deliberate inline computation. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-$(THEME).png with px_per_unit=2. Uses current Makie - size API. - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Uses linkxaxes!, band!, rowsize!/rowgap!, hidexdecorations!, ytickformat - callback, grid layout indexing — all idiomatic Makie patterns. - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: linkxaxes! for panel synchronization, band! for zone fills, rowsize!(fig.layout,...) - for panel proportions are genuinely Makie-distinctive features. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - subplots - - annotations - - manual-ticks - patterns: - - data-generation - dataprep: - - rolling-window - styling: - - alpha-blending - - edge-highlighting diff --git a/plots/dashboard-synchronized-crosshair/metadata/python/altair.yaml b/plots/dashboard-synchronized-crosshair/metadata/python/altair.yaml deleted file mode 100644 index fd371c4fb3..0000000000 --- a/plots/dashboard-synchronized-crosshair/metadata/python/altair.yaml +++ /dev/null @@ -1,270 +0,0 @@ -library: altair -language: python -specification_id: dashboard-synchronized-crosshair -created: '2026-01-20T19:37:34Z' -updated: '2026-05-23T13:11:00Z' -generated_by: claude-sonnet -workflow_run: 26332804985 -issue: 3784 -language_version: 3.13.13 -library_version: 6.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/altair/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/altair/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/altair/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/altair/plot-dark.html -quality_score: 87 -review: - strengths: - - Proper Altair selection API for synchronized crosshair — idiomatic use of nearest - selection_point and mark_rule with conditional opacity across all three chart - panels - - Linked zoom/pan via selection_interval(bind="scales") shared across all charts - — correctly uses the same parameter name to link views in Vega-Lite - - Overbought/oversold reference zones with semi-transparent fills add meaningful - analytical context to the momentum chart - - 'Correct Imprint palette usage in canonical order: green price (#009E73), purple - volume (#9418DB), red momentum (#B71D27)' - - Theme adaptation is fully correct in both renders — INK/INK_SOFT tokens applied - to title, axis labels, tick labels, legend; backgrounds match FAF8F1 / 1A1A17 - - 'Canvas sizing handled correctly: inner view widths sized to avoid vl-convert - overshoot, then padded to exact 3200×1800 with PIL' - - Clean shared-axis design — X-axis labels and ticks hidden on top two charts, shown - only on bottom chart - weaknesses: - - Sub-chart panel titles ("Price", "Volume", "Momentum Indicator") at fontSize=12 - are the same size as axis titles — they would read more clearly as panel headers - if slightly larger (fontSize=13-14) or made bold - - 'Design Excellence is serviceable but not exceptional: no spine removal (configure_view - strokeWidth=0 hides the box border but axis domain lines remain), and the overall - visual style is clean-functional rather than polished-editorial' - - Volume bars at opacity=0.7 produce a slightly washed-out purple; reducing to opacity=0.85 - or removing the opacity entirely would give crisper bars without over-darkening - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct anyplot light surface, no pure white - Chrome: Main title "dashboard-synchronized-crosshair · python · altair · anyplot.ai" in dark near-black — clearly readable. Panel sub-titles "Price", "Volume", "Momentum Indicator" in dark text, visible. Y-axis titles ("Price ($)", "Volume", "Momentum") in dark text, readable. Tick labels in dark-gray (INK_SOFT), readable. X-axis date labels ("Jan 2024" ... "Oct 2024") visible on bottom chart only. - Data: Price line in #009E73 (brand green), Volume bars in #9418DB (purple) at opacity=0.7, Momentum line in #B71D27 (red). Overbought/oversold zone fills in light red tint (opacity=0.08), dashed reference lines at y=30 and y=70 visible in INK_SOFT gray. - Legibility verdict: PASS — all text is readable against the warm off-white background. - - Dark render (plot-dark.png): - Background: Near-black (#1A1A17) — correct anyplot dark surface, not pure black - Chrome: Main title and all panel sub-titles appear in light cream (#F0EFE8 via configure_title(color=INK)), readable against dark background. Axis titles in same light cream. Tick labels in #B8B7B0 (INK_SOFT for dark theme), visible. No dark-on-dark failures observed. - Data: Price line (#009E73), Volume bars (#9418DB), Momentum line (#B71D27) — colors identical to light render as required. Reference zones and dashed lines at overbought/oversold thresholds visible. The semi-transparent zone fills adapt well to dark background. - Legibility verdict: PASS — all text is readable against the near-black background; no dark-on-dark failures detected. - criteria_checklist: - visual_quality: - score: 29 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: 'All text readable in both themes. Font sizes explicitly set: 10px - tick, 12px axis title, 16px main title. Minor: panel sub-titles at fontSize=12 - are same size as axis titles — slightly small as panel headers.' - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No text or element overlaps in either render. Charts stack cleanly - with spacing=10. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: 200 data points at medium density. Lines at strokeWidth=2 clearly - visible. Volume bars distinguishable. Reference lines with dashed style - visible in both themes. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Three Imprint palette colors used. Good contrast in both themes. - CVD-safe palette. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Canvas gate passed (no gate file). 3200x1800 landscape. Three-panel - stacked layout with good proportions. No clipping visible. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: 'Y-axes labeled with units: Price ($), Volume, Momentum. X-axis: - Trading Date with date format. Title format correct.' - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73 (price). Second #9418DB (volume). Third #B71D27 - (momentum). Backgrounds correct: #FAF8F1 light, #1A1A17 dark. Both renders - theme-correct.' - design_excellence: - score: 11 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Above default (4). Coherent financial dashboard narrative. Overbought/oversold - zones add analytical context. Clean multi-chart composition. Not exceptional - — functional rather than editorial polish. - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: true - comment: Above default (2). Clean shared-axis design with X-axis hidden on - top charts. X-grid disabled. Y-grid at opacity=0.10 is subtle. configure_view - strokeWidth=0 removes box border. - - id: DE-03 - name: Data Storytelling - score: 3 - max: 6 - passed: true - comment: Above default (2). Financial story (price/volume/momentum) is coherent. - Overbought/oversold zones guide viewer interpretation. No strong focal point - or emphasis technique. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: 'Correct: multi-chart dashboard with synchronized crosshair. Line - for price and momentum, bar for volume.' - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Synchronized vertical crosshair via shared nearest selection. Tooltips - per chart. Stacked vertical layout. Linked zoom/pan via bind='scales'. Independent - y-axis scales. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Date on shared x-axis. Price/volume/indicator correctly mapped to - respective y-axes. Full 200-day range shown. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title exactly: ''dashboard-synchronized-crosshair · python · altair - · anyplot.ai''. No legend needed — each panel self-identifies via y-axis - title.' - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Three chart types demonstrating multi-series dashboard. Crosshair - sync, zoom/pan linkage, tooltips per chart. Momentum zones add analytical - depth. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Stock market data (price, volume, momentum indicator) over 200 trading - days. Realistic and politically neutral. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Price ~85-115 (random walk from 100). Volume 1M-4M (realistic for - stocks). Momentum 0-100 with overbought/oversold zones at 70/30 — industry - standard. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: No functions or classes. Linear top-to-bottom flow. Clean composition. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: os, altair, numpy, pandas, PIL.Image — all used. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Appropriate complexity for synchronized multi-chart dashboard. No - fake UI elements. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html. No bare plot.png. Current - Altair 6.x API. - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: 'Idiomatic Altair: selection_point with nearest=True, add_params - on multiple layers, vconcat composition, alt.condition for conditional opacity, - proper T/Q encoding types.' - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: 'Uses distinctly Altair/Vega-Lite features: shared selection_point - across vconcat panels, selection_interval(bind=''scales'') for linked zoom, - conditional opacity via alt.condition — not available in imperative libraries.' - verdict: APPROVED -impl_tags: - dependencies: - - pillow - techniques: - - hover-tooltips - - html-export - - layer-composition - patterns: - - data-generation - dataprep: - - time-series - - cumulative-sum - styling: - - alpha-blending diff --git a/plots/dashboard-synchronized-crosshair/metadata/python/bokeh.yaml b/plots/dashboard-synchronized-crosshair/metadata/python/bokeh.yaml deleted file mode 100644 index 8e6790b03e..0000000000 --- a/plots/dashboard-synchronized-crosshair/metadata/python/bokeh.yaml +++ /dev/null @@ -1,267 +0,0 @@ -library: bokeh -language: python -specification_id: dashboard-synchronized-crosshair -created: '2026-01-20T19:38:27Z' -updated: '2026-05-23T20:03:27Z' -generated_by: claude-sonnet -workflow_run: 26332758954 -issue: 3784 -language_version: 3.13.13 -library_version: 3.9.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/bokeh/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/bokeh/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/bokeh/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/bokeh/plot-dark.html -quality_score: 92 -review: - strengths: - - Full theme adaptation with all chrome elements (background, border, title, axis - labels, tick labels, grid, legends) properly using INK/INK_SOFT/PAGE_BG/ELEVATED_BG - tokens in both light and dark renders - - Exact 3200x1800 canvas via CDP Page.captureScreenshot with captureBeyondViewport=True - — avoids the headless Chrome toolbar-height bug - - Shared x_range across all three panels ensures synchronized zoom and pan - - Coherent financial dashboard narrative with semantically appropriate color choices - (green=price/buy, purple=volume, sky-blue=RSI, red=danger/overbought) - - Clean styling loop and flat KISS script structure with no functions or classes - weaknesses: - - 'CrosshairTool not shared across panels: the spec core feature (synchronized vertical - crosshair spanning ALL charts at the hover position) requires a single shared - CrosshairTool instance added to all figures. Currently each figure gets its own - independent instance so hovering over chart 1 shows a crosshair only on chart - 1. Fix: ch = CrosshairTool(dimensions=height, line_color=INK_SOFT, line_alpha=0.85, - line_width=2); p1.add_tools(ch); p2.add_tools(ch); p3.add_tools(ch).' - - Oversold (30) reference line uses C_PRICE (#009E73) — same color as the Price - line in chart 1. While green=buy-signal is semantically defensible, it creates - a visual identity confusion between the oversold threshold and the price line. - Consider palette position 5 (#99B314, lime) for the oversold reference. - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 across all three panels — correct, not pure white. - Chrome: Title "dashboard-synchronized-crosshair · python · bokeh · anyplot.ai" in dark ink (#1A1A17), spanning ~65% of width. Y-axis labels "Price ($)", "Volume (M)", "RSI" in dark ink, clearly readable. X-axis label "Date" on bottom panel in dark ink. Tick labels (Jan 2024, Mar 2024, etc.) in #4A4A44 (INK_SOFT), readable. Legend boxes on elevated #FFFDF6 fill with INK_SOFT borders — legible label text. - Data: Top panel — price line in #009E73 (brand green, correct first series). Middle panel — volume bars in #9418DB (purple, palette position 2) with alpha=0.75. Bottom panel — RSI line in #16B8F3 (sky blue), Overbought(70) dashed in #B71D27 (red, semantic), Oversold(30) dashed in #009E73 (green). - Legibility verdict: PASS — all text readable against warm off-white surface, no light-on-light issues. - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 across all three panels — correct, not pure black. - Chrome: Title and axis labels render in #F0EFE8 (near-white INK token), clearly visible against the dark background. Tick labels in #B8B7B0 (INK_SOFT dark token), readable. Legend boxes use #242420 (ELEVATED_BG dark) fill — text legible. No dark-on-dark failures detected. - Data: Colors are identical to the light render — price #009E73, volume #9418DB, RSI #16B8F3, overbought red, oversold green. Only chrome (background, text, legend fill) flipped between themes. - Legibility verdict: PASS — all text readable against warm near-black surface. Brand green #009E73 clearly visible on dark background. - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: Title, axis labels, tick labels, and legend text all clearly readable - in both themes. Font sizes match spec (50pt title, 42pt axis labels, 34pt - tick labels, 28pt legend). Title spans ~65% of width — expected for the - mandated title string. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No text or legend overlaps with data or other elements across any - of the three panels. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Line width=3 and bars with alpha=0.75 well-matched to 200 data points. - Reference lines (dashed) clearly distinguishable from RSI line. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Imprint palette is colorblind-safe. Dashing and legend labels differentiate - reference lines beyond color alone. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Canvas exactly 3200x1800 (gate passed). Three panels with heights - 600+540+660=1800 stack precisely. No overflow or clipping detected. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title correct. Y-axis labels (Price ($), Volume (M), RSI) and x-axis - label (Date) descriptive with units. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73 correct. Volume uses position 2 (#9418DB). - Red (#B71D27) for Overbought is valid semantic exception (danger/sell zone). - Backgrounds #FAF8F1/#1A1A17 correct. Both themes correct.' - design_excellence: - score: 14 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 6 - max: 8 - passed: true - comment: Intentional color hierarchy across panels. Consistent legend styling - with elevated backgrounds. Alpha=0.75 on bars prevents visual heaviness. - Professionally structured for a financial dashboard. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Grid at 10% alpha is appropriately subtle. Legend boxes styled with - ELEVATED_BG fill and INK_SOFT borders. All-four-side box outlines remain - — appropriate for panel separation but top/right spine removal would add - polish. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: 'Coherent financial narrative: price trend, volume context, RSI momentum - with overbought/oversold reference zones. Reference lines at 70/30 guide - viewer to signal thresholds.' - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Multi-chart synchronized dashboard with stacked vertical layout and - shared time axis. - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: false - comment: Synchronized zoom/pan via shared x_range OK. HoverTool with mode=vline - OK. But three separate CrosshairTool instances created (one per figure) - instead of a single shared instance. True cross-panel crosshair synchronization - requires sharing the same CrosshairTool object. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: date on x-axis, price/volume/rsi on respective y-axes, all 200 data - points shown. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title is exactly dashboard-synchronized-crosshair · python · bokeh - · anyplot.ai. Per-panel legends with descriptive labels. - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Three distinct chart types (line, bar, line+reference lines) covering - all aspects of the spec multi-indicator dashboard pattern. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Stock price random walk from 100, volume correlated with price change - magnitude, RSI clipped to realistic 20-80 range. Neutral financial data. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Price 80-110, volume 1-7M shares, RSI 20-80, 200 trading days over - ~10 months. All realistic for a mid-cap stock. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Flat script, no functions or classes. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports used. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Styling loop across all panels is clean. CDP Page.captureScreenshot - with captureBeyondViewport is the correct pattern for exact-dimension screenshots. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html correctly. - library_mastery: - score: 9 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: ColumnDataSource, figure(), column layout, x_range sharing, HoverTool - with mode=vline, CrosshairTool all used correctly and idiomatically. - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: CrosshairTool, linked x_range pan/zoom, ColumnDataSource with datetime - axis, interactive HTML output are distinctive Bokeh strengths used well. - Minor deduction because CrosshairTool is not shared, missing the full Bokeh - synchronized crosshair capability. - verdict: APPROVED -impl_tags: - dependencies: - - selenium - techniques: - - hover-tooltips - - html-export - - subplots - patterns: - - data-generation - - columndatasource - - iteration-over-groups - dataprep: - - cumulative-sum - - time-series - styling: - - alpha-blending - - grid-styling diff --git a/plots/dashboard-synchronized-crosshair/metadata/python/letsplot.yaml b/plots/dashboard-synchronized-crosshair/metadata/python/letsplot.yaml deleted file mode 100644 index c02cd43a2b..0000000000 --- a/plots/dashboard-synchronized-crosshair/metadata/python/letsplot.yaml +++ /dev/null @@ -1,254 +0,0 @@ -library: letsplot -language: python -specification_id: dashboard-synchronized-crosshair -created: '2026-01-20T19:39:41Z' -updated: '2026-05-23T20:44:08Z' -generated_by: claude-sonnet -workflow_run: 26342314644 -issue: 3784 -language_version: 3.13.13 -library_version: 4.10.1 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/letsplot/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/letsplot/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/letsplot/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/letsplot/plot-dark.html -quality_score: 84 -review: - strengths: - - 'Correct Imprint palette throughout: price=#009E73, volume=#9418DB, rsi=#B71D27, - crosshair=#D359A7 (palette position 6, distinct from all data series)' - - 'Full theme adaptation: all chrome tokens correctly wired to both themes; no dark-on-dark - in dark render' - - 'Excellent spec compliance: synchronized crosshair with value annotations in static - PNG, plus real interactive HTML with layer_tooltips()' - - Shared common_theme pattern eliminates code duplication across 3 sub-charts - - RSI overbought/oversold reference lines (30/70) add meaningful domain context; - height ratio 2:1:1 gives appropriate emphasis to price chart - - geom_segment for volume bars (rather than geom_bar) shows library knowledge; comment - explains the rationale - weaknesses: - - axis_title size=18 and axis_text size=14 render at 72px and 56px at scale=4, exceeding - recommended 48px and 40px — particularly noticeable in shorter Volume and RSI - sub-panels; reduce to axis_title=12, axis_text=10 per style guide - - Design sophistication limited to functional choices (crosshair color, height ratio); - no removal of panel border chrome, no refinement of whitespace between sub-panels - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct, not pure white - Chrome: Title "dashboard-synchronized-crosshair · python · letsplot · anyplot.ai" in dark INK text at top; sub-chart titles "Price", "Volume", "RSI Indicator" in dark text inside panels; y-axis labels "Price ($)", "Volume (M)", "RSI" and x-axis "Date" in dark INK text; tick labels (month names, numeric values) in INK_SOFT dark gray — all readable - Data: Price line #009E73 (green, first series correct); volume segments #9418DB (purple); RSI line #B71D27 (red); crosshair elements #D359A7 (pink palette position 6, distinct from all series); pink vertical crosshair line at day 100, pink dot markers, annotation labels "$85.91" and "47.9" - Legibility verdict: PASS - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct, not pure black - Chrome: Title in light INK (#F0EFE8) text; sub-chart titles in light text; axis labels in light text; tick labels in INK_SOFT (#B8B7B0) light gray — all readable against dark background; no dark-on-dark failures observed; RSI dashed reference lines switch to INK_SOFT light gray, remaining visible - Data: Price line #009E73 (identical to light render); volume segments #9418DB (identical); RSI line #B71D27 (identical); crosshair #D359A7 (identical) — all data colors unchanged between themes, only chrome flipped - Legibility verdict: PASS - criteria_checklist: - visual_quality: - score: 27 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 6 - max: 8 - passed: true - comment: All font sizes explicitly set; readable in both themes. axis_title=18 - (72px at scale=4) slightly above recommended 12 (48px); disproportionate - in shorter Volume/RSI panels - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text or data elements in either render - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: All data series clearly visible; volume geom_segment at 200 points - slightly dense but readable - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: CVD-safe Imprint palette with good luminance separation across all - series - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Correct 3200x1800 canvas; 3-panel stacked layout fills canvas well; - no clipping - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: 'Descriptive labels with units: Price ($), Volume (M), RSI, Date' - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73; positions 2-3 canonical order; backgrounds - #FAF8F1/#1A1A17; all chrome theme-adaptive' - design_excellence: - score: 11 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Thoughtful crosshair color (palette position 6, distinct from all - series); height ratio 2:1:1; shared common_theme. Above default, not quite - strong design - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: true - comment: theme_minimal base; minor grid removed; major grid subtle with custom - GRID_COLOR; explicit axis line styling. Above defaults, not fully polished - - id: DE-03 - name: Data Storytelling - score: 3 - max: 6 - passed: true - comment: RSI reference lines (30/70) add interpretive context; synchronized - crosshair creates focal point; coherent financial narrative - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Multi-chart dashboard with synchronized crosshair; line/segment/line - for price/volume/RSI; stacked vertical layout - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Synchronized vertical crosshair, value annotations, stacked layout, - independent y-scales, interactive HTML with layer_tooltips() - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: date on x-axis; price/volume/rsi on respective y-axes; all 200 points - shown - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title matches required format; no legend needed (self-labeled via - sub-chart titles) - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: Shows all 3 metrics demonstrating crosshair across different chart - types and y-scales; RSI reference lines show overbought/oversold zones - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: 'Real-world stock market: 200 trading days, cumulative price, volume - correlated with volatility, RSI technical indicator' - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Price ~80-110, volume in millions, RSI 0-100 clipped; all realistic - for equity data - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Imports → tokens → data → per-chart → gggrid → save; no functions - or classes - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: 'All imports used: geom_hline for RSI lines, geom_segment for volume, - geom_path for crosshair, gggrid/ggtitle for composition' - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Shared common_theme reduces repetition; geom_segment comment explains - quirk; clean readable structure - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png (scale=4) and plot-{THEME}.html - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Correct ggplot grammar; gggrid for multi-panel; layer_tooltips() - for interactive hover; ggsize+scale=4 for canvas control - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: 'letsplot-specific: gggrid (multi-panel composition), layer_tooltips() - API, HTML export; geom_segment workaround demonstrates library knowledge' - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - annotations - - subplots - - hover-tooltips - - html-export - - layer-composition - patterns: - - data-generation - dataprep: - - time-series - - cumulative-sum - styling: - - alpha-blending - - grid-styling diff --git a/plots/dashboard-synchronized-crosshair/metadata/python/matplotlib.yaml b/plots/dashboard-synchronized-crosshair/metadata/python/matplotlib.yaml deleted file mode 100644 index 2c1b5ebc9b..0000000000 --- a/plots/dashboard-synchronized-crosshair/metadata/python/matplotlib.yaml +++ /dev/null @@ -1,239 +0,0 @@ -library: matplotlib -language: python -specification_id: dashboard-synchronized-crosshair -created: '2026-01-20T19:38:43Z' -updated: '2026-05-23T12:54:35Z' -generated_by: claude-sonnet -workflow_run: 26332619833 -issue: 3784 -language_version: 3.13.13 -library_version: 3.10.9 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/matplotlib/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/matplotlib/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 88 -review: - strengths: - - 'Perfect spec compliance: stacked panels with sharex=True, synchronized crosshair - axvlines, and per-panel annotation boxes nail the spec requirements for a static - library' - - Semantic volume coloring (green up / red down) adds real narrative value beyond - plain data display - - 'Theme adaptation is complete and correct: all chrome tokens applied to every - text element, legend frame, and annotation box in both renders' - - 'Data well-constructed: price trend reversal, RSI moving through overbought/neutral/oversold - zones, crosshair at a meaningful inflection point' - weaknesses: - - 'VQ-04: Volume bars use green/red as the only directional signal — add alpha or - border-width difference to create a secondary visual cue for CVD users' - - 'VQ-01: Overbought/Oversold threshold labels at fontsize=6 are borderline small - at mobile widths — increase to fontsize=7-8' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct - Chrome: Title "dashboard-synchronized-crosshair · python · matplotlib · anyplot.ai" in dark ink, clearly readable. Axis labels Price ($), Volume (M), RSI, Date all in dark INK. Tick labels in INK_SOFT — all readable. Legend frames in ELEVATED_BG (#FFFDF6) with styled borders. - Data: Price line #009E73 (position 1) with subtle fill-between. Volume bars #009E73 (up) / #B71D27 (down) semantic. RSI line #9418DB (position 2). Synchronized crosshair vertical line in INK_MUTED across all 3 panels. Annotation boxes with rounded corners showing $88.6, 3.2M, RSI 55.5. - Legibility verdict: PASS — all main text readable; Overbought/Oversold at 6pt are small but visible - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct - Chrome: Title and axis labels in light cream #F0EFE8. Tick labels in #B8B7B0. Legend frames in #242420 (ELEVATED_BG dark). Annotation box backgrounds in #242420 with light text — no dark-on-dark issues anywhere. - Data: Colors identical to light render — #009E73 price, green/red volume, #9418DB RSI. Crosshair annotation boxes clearly visible with proper contrast. - Legibility verdict: PASS — all chrome flips correctly, no dark-on-dark failures - criteria_checklist: - visual_quality: - score: 28 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: All font sizes explicitly set; Overbought/Oversold at fontsize=6 - borderline small at mobile widths - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No text collisions across panels or annotation boxes - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Lines, bars clearly visible; fill-between intentionally subtle - - id: VQ-04 - name: Color Accessibility - score: 1 - max: 2 - passed: true - comment: Volume bars use green/red as sole directional signal — CVD concern - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 2400x2400 square canvas; 3 panels fill it well with tight synchronized - spacing - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Price ($), Volume (M), RSI, Date — descriptive with units - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Price #009E73 position 1, RSI #9418DB position 2, volume semantic - #B71D27; backgrounds correct; chrome flips cleanly' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Strong design with semantic coloring, fill-between depth, styled - annotation boxes, cross-panel focal point - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Spines removed, y-only subtle grid, styled legend frames, tight panel - spacing - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Synchronized crosshair creates clear focal point; semantic volume - coloring adds narrative - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct multi-chart stacked dashboard with synchronized crosshair - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: axvline crosshairs, annotation boxes, sharex=True, independent y-axes - — all present - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Date on shared x-axis; price, volume, RSI on independent y-axes - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title format correct; panel legends Price, Volume, RSI - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: All three panel types with realistic variation; RSI traverses multiple - zones - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Stock market data over 200 trading days; neutral financial scenario - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Price ~80-110, volume in millions, RSI 0-90 — all realistic - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Flat Imports -> Data -> Plot -> Save; no functions or classes - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports actively used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: ann_kw dict avoids repetition; annotations legitimate per spec - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png; no bbox_inches=tight; no deprecated APIs - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: sharex=True, DateFormatter, MonthLocator, fill_between, axhline/axvline - — all idiomatic - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: sharex=True synchronized multi-panel and date axis formatting are - distinctively matplotlib - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - subplots - - annotations - patterns: - - data-generation - - explicit-figure - - iteration-over-groups - dataprep: - - cumulative-sum - - time-series - - normalization - styling: - - alpha-blending - - grid-styling diff --git a/plots/dashboard-synchronized-crosshair/metadata/python/plotly.yaml b/plots/dashboard-synchronized-crosshair/metadata/python/plotly.yaml deleted file mode 100644 index b431a6330a..0000000000 --- a/plots/dashboard-synchronized-crosshair/metadata/python/plotly.yaml +++ /dev/null @@ -1,260 +0,0 @@ -library: plotly -language: python -specification_id: dashboard-synchronized-crosshair -created: '2026-01-20T19:38:13Z' -updated: '2026-05-23T12:44:31Z' -generated_by: claude-sonnet -workflow_run: 26332710883 -issue: 3784 -language_version: 3.13.13 -library_version: 6.7.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/plotly/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/plotly/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/plotly/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/plotly/plot-dark.html -quality_score: 94 -review: - strengths: - - 'Perfect spec compliance: synchronized crosshair via spike lines (spikemode=''across''), - unified hover tooltip (hovermode=''x unified''), shared x-axis zoom/pan, and full - interactive HTML export' - - Accurate RSI implementation using Wilder's exponential smoothing with overbought/oversold - reference bands distinguished by dash pattern for colorblind accessibility - - Proportional panel layout (50/25/25 row heights) creates clear visual hierarchy - that reflects analytical importance of each financial metric - - 'Full theme-adaptive chrome: INK/INK_SOFT/GRID tokens applied consistently to - all text, axis titles, tick labels, grid lines, and hover labels in both light - and dark themes' - - Both renders are fully readable with no dark-on-dark or light-on-light issues; - data colors identical between themes (only chrome flips) - weaknesses: - - Design could be elevated with a subtle area fill (fill='tozeroy', fillcolor with - low alpha) under the price line to add visual depth to the primary panel - - RSI panel uses palette position 4 (#16B8F3 sky blue) instead of canonical position - 3 (#B71D27 red) — the skip is defensible to avoid confusion with the red overbought - reference line, but deviates from canonical order - - 'No explicit spine removal: top/right axis lines are handled by the plotly_white - template rather than being explicitly removed; minor concern for template portability' - - Subplot annotation titles use INK_SOFT coloring rather than INK, making the panel - headers slightly less visually dominant as navigation anchors in a multi-panel - layout - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct anyplot light surface. Canvas 3200×1800. - Chrome: Main title "dashboard-synchronized-crosshair · python · plotly · anyplot.ai" centered at top in #1A1A17 at size 16. Subplot titles "Price (USD)", "Volume", "RSI (14-period)" in INK_SOFT above each panel. Y-axis labels "Price (USD)", "Volume", "RSI" in dark INK on left. X-axis label "Date" at bottom. Tick labels in INK_SOFT, clearly readable. Grid lines subtle with rgba(26,26,23,0.10) opacity. All text is readable against the light background. - Data: Price line in #009E73 (position 1, correct first series). Volume bars in #9418DB (position 2). RSI line in #16B8F3 (position 4 — skips position 3 to avoid confusion with the red overbought reference line). Red dashed line at RSI=70 using #B71D27. Green dotted line at RSI=30 using #009E73. - Legibility verdict: PASS - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct anyplot dark surface. Canvas 3200×1800. - Chrome: Title and all text elements flip to light — title in #F0EFE8, subplot titles and secondary text in #B8B7B0 (INK_SOFT dark). Grid lines use rgba(240,239,232,0.10) — subtle on dark. Hover label uses ELEVATED_BG #242420. All axis labels, tick labels, and panel titles are clearly visible against the dark background. No dark-on-dark failures detected. - Data: Data colors are identical to the light render — #009E73 price, #9418DB volume, #16B8F3 RSI. Reference lines unchanged. Only chrome (background, text, grid) flipped. - Legibility verdict: PASS - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All font sizes explicitly set (title=16, axes=12, ticks=10). All - text readable in both light and dark themes. No legibility failures. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text or data elements in either render. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: All lines, bars, and reference lines clearly visible. line width=2.5 - appropriate for 200-point time series. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Imprint palette used (colorblind-safe). RSI reference lines distinguished - by dash pattern (dashed vs dotted) in addition to color. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Canvas 3200x1800 confirmed. Proportional panels (50/25/25), good - margins (l=100, r=40, t=80, b=60). Nothing cut off. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title correct format. Y-axes labeled with units where relevant. X-axis - labeled 'Date'. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series = #009E73. Multi-series uses Imprint palette. Plot - backgrounds #FAF8F1/#1A1A17. Both themes correct. RSI position 4 skip justified - to avoid confusion with red reference line.' - design_excellence: - score: 14 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 6 - max: 8 - passed: true - comment: Intentional 50/25/25 panel proportions, RSI reference bands with - accessibility-conscious dash patterns, custom spike lines, custom hover - templates. Professional financial dashboard aesthetic above defaults. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Subtle GRID opacity (rgba with 0.10), INK/INK_SOFT text hierarchy, - volume bar opacity=0.8, custom hoverlabel styling with ELEVATED_BG. Could - be elevated with explicit spine removal. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Three panels tell a complete financial analysis story. RSI bands - provide analytical context. Panel sizing reflects metric importance. Could - be elevated with area fill under price or callouts for notable patterns. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Multi-chart dashboard with synchronized crosshair, exactly as specified. - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Synchronized crosshair (spikemode='across'), unified hover tooltip, - shared x-axis zoom/pan, independent y-scales, HTML export for interactivity. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Price/volume/RSI correctly mapped to panels 1/2/3. Shared date axis. - 200 business day data points within 100-500 range. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title 'dashboard-synchronized-crosshair · python · plotly · anyplot.ai' - correct. showlegend=False appropriate as each panel has subplot title. - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Three chart types across panels (line, bar, line with bands). Spike - lines demonstrate crosshair synchronization. HTML export enables full interactivity. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: 'Financial data (price/volume/RSI) is real-world plausible and neutral. - Realistic values: price ~85-110 USD, volume ~1M, RSI bounded 0-100.' - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: 200 business days appropriate for financial analysis. Proper random - walk price model. Volume correlated with price volatility. Wilder's RSI - correctly bounded 0-100. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Flat imperative code, no functions or classes. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) ensures deterministic output. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: 'All imports used: os, numpy, pandas, plotly.graph_objects, plotly.subplots.' - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean organization. RSI calculation using Wilder's method is correct. - No fake UI elements. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html. autosize=False with - explicit width=800, height=450, scale=4. - library_mastery: - score: 10 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: make_subplots with shared_xaxes=True, row_heights, hovermode='x unified', - fig.update_yaxes(row=N, col=1) pattern — all idiomatic Plotly. - - id: LM-02 - name: Distinctive Features - score: 5 - max: 5 - passed: true - comment: Spike lines (showspikes, spikemode='across', spikesnap='cursor') - for synchronized crosshair. Custom hovertemplate per trace. HTML export - for full interactivity. These are distinctively Plotly features used correctly. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - subplots - - hover-tooltips - - html-export - patterns: - - data-generation - dataprep: - - time-series - - rolling-window - styling: - - alpha-blending diff --git a/plots/dashboard-synchronized-crosshair/metadata/python/plotnine.yaml b/plots/dashboard-synchronized-crosshair/metadata/python/plotnine.yaml deleted file mode 100644 index 7e53742b63..0000000000 --- a/plots/dashboard-synchronized-crosshair/metadata/python/plotnine.yaml +++ /dev/null @@ -1,257 +0,0 @@ -library: plotnine -language: python -specification_id: dashboard-synchronized-crosshair -created: '2026-01-20T19:39:43Z' -updated: '2026-05-23T13:01:46Z' -generated_by: claude-sonnet -workflow_run: 26332848592 -issue: 3784 -language_version: 3.13.13 -library_version: 0.15.4 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/plotnine/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/plotnine/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 87 -review: - strengths: - - 'Perfect spec compliance: all required dashboard features present and well-executed - for a static library' - - Full theme-adaptive chrome in both renders — both light and dark pass readability - checks with no dark-on-dark or light-on-light failures - - Realistic domain-appropriate data (stock price/volume/RSI) with meaningful variation - across all three metrics - - Effective visual hierarchy via green data lines and red crosshair accent creating - a clear focal point - - Clean KISS code with ordered categorical facets (CategoricalDtype) keeping panel - order deterministic - weaknesses: - - Tick label font size (7pt) is slightly below the recommended 8pt for 2400x2400 - canvas; bump axis_text to size=8 for better mobile readability - - Strip panel headers retain a visible rectangular border despite strip_background - color=None; try adding panel_border=element_blank() to fully suppress residual - frames - - panel_spacing=0.2 introduces visible vertical gaps between panels; reducing to - 0.1 or 0.15 would create a tighter more cohesive dashboard feel - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct light theme surface - Chrome: Title "dashboard-synchronized-crosshair · python · plotnine · anyplot.ai" bold and visible; strip headers "Price ($)", "Volume (M)", "RSI" in bold dark text; x-axis "Date" label and monthly tick labels (Jan-Oct 2024) rotated 45° and readable; y-axis blank by design (units in strip headers); all text is dark-colored against light background - Data: Three panels with #009E73 brand green data lines; dashed red #B71D27 vertical crosshair spanning all panels; red markers at intersection points; geom_label boxes showing "$113.26", "1.12M", "RSI: 65.0" with elevated background fill - Legibility verdict: PASS — all text clearly readable, no light-on-light issues - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct dark theme surface - Chrome: Same title and strip headers in light #F0EFE8 text; axis labels and tick labels in lighter #B8B7B0 tone; annotation label boxes use #242420 elevated fill — all clearly readable against dark surface; no dark-on-dark failures detected - Data: Data line colors identical to light render — #009E73 green lines and #B71D27 red crosshair elements unchanged; only chrome flipped - Legibility verdict: PASS — all text clearly readable on dark background, no dark-on-dark failures - criteria_checklist: - visual_quality: - score: 28 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: All font sizes explicitly set (title=10pt, x-axis=9pt, ticks=7pt, - strip=9pt); readable in both themes; tick labels slightly below 8pt guide - recommendation for this canvas size - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: 'No overlap: 45-degree rotated monthly x-axis labels well-spaced; - annotation labels positioned above data points without collision' - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: geom_line(size=1.0) well-adapted to 200-point time series; crosshair - markers (size=3.0) clearly distinct; all elements visible in both themes - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: 'Green #009E73 vs red #B71D27 crosshair satisfies CVD safety via - lightness contrast under deuteranopia' - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: Three-panel layout fills 2400x2400 canvas well; minor visual gaps - between panels from panel_spacing=0.2 - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Strip labels carry units (Price ($), Volume (M), RSI); x-axis labelled - Date; y-axis blank by design - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Data lines #009E73 (position 1); crosshair uses #B71D27 (palette - position 3); backgrounds #FAF8F1/#1A1A17; all chrome tokens theme-adaptive - in both renders' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: true - comment: Well-configured library default with anyplot theme tokens; intentional - green/red color scheme; professional but not at FiveThirtyEight level - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: theme_minimal() removes spines; grid at alpha=0.10 is subtle; strip - panel header boxes retain slight rectangular prominence - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Red dashed crosshair with value annotations creates strong focal - point demonstrating synchronized-inspection concept; green/red hierarchy - guides the viewer - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Multi-panel line chart stacked vertically with synchronized crosshair - — correct chart type for dashboard - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Vertical crosshair (geom_vline), value annotations (geom_label), - stacked layout (facet_wrap ncol=1), independent y-axes (scales=free_y), - shared x-axis — all present - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Date on x-axis, metric values on y-axis, correct panel assignment - for each series - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title matches dashboard-synchronized-crosshair · python · plotnine - · anyplot.ai exactly; no legend needed for single-series panels - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'All three dashboard metrics present: price with trend+seasonality, - volume with earnings-season effects, RSI with momentum — all show realistic - variation' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Stock market domain with price/volume/RSI is a classic neutral use - case for synchronized crosshair dashboards - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Price ~$90-120, volume ~1-2M shares, RSI clipped to 10-90 (standard - range) — all factually coherent - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear: imports -> theme tokens -> data generation -> DataFrame - construction -> plot -> save' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) present - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports used; no stray imports - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean, Pythonic; CategoricalDtype for ordered facets; no over-engineering; - no fake interactivity code - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png using current plotnine API - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: 'Strong ggplot2 grammar: facet_wrap with scales=free_y, scale_x_datetime - with date_breaks/date_labels, callable breaks in scale_y_continuous, geom_vline - with datetime xintercept' - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Uses CategoricalDtype(ordered=True) for facet panel ordering, scales=free_y - for independent axes, and callable breaks — all distinctively ggplot-style - patterns - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - faceting - - annotations - - layer-composition - patterns: - - data-generation - dataprep: - - time-series - styling: - - alpha-blending diff --git a/plots/dashboard-synchronized-crosshair/metadata/python/pygal.yaml b/plots/dashboard-synchronized-crosshair/metadata/python/pygal.yaml deleted file mode 100644 index 8cc9819c89..0000000000 --- a/plots/dashboard-synchronized-crosshair/metadata/python/pygal.yaml +++ /dev/null @@ -1,265 +0,0 @@ -library: pygal -language: python -specification_id: dashboard-synchronized-crosshair -created: '2026-01-20T19:37:49Z' -updated: '2026-05-23T13:08:57Z' -generated_by: claude-sonnet -workflow_run: 26332894670 -issue: 3784 -language_version: 3.13.13 -library_version: 3.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/pygal/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/pygal/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/pygal/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/pygal/plot-dark.html -quality_score: 87 -review: - strengths: - - Correct canvas size (3200×1800) achieved via PIL composite of three separate pygal - renders - - Proper dual-theme support — light (#FAF8F1) and dark (#1A1A17) backgrounds both - look correct - - Interactive HTML implements real synchronized crosshair + unified tooltip via - JavaScript, satisfying the spec's interactive requirement for pygal - - 'Clean three-panel dashboard: x-axis labels suppressed on upper panels, only shown - on the bottom RSI panel — textbook dashboard design' - - RSI panel includes muted overbought (70) and oversold (30) reference lines with - semantic y-ticks [0, 30, 50, 70, 100] - - All font sizes explicitly set per style-guide values (title=66, label=56, major_label=44) - - Reproducible with np.random.seed(42); KISS top-level structure; all imports used - weaknesses: - - 'VQ-07 partial: Each sub-chart''s first (and only) series should use #009E73 regardless - of dashboard panel position. The volume chart opens with #9418DB and the RSI chart - with #B71D27 — the palette order is applied at the dashboard level rather than - the chart level, which violates the ''first series always #009E73'' rule' - - 'LM-02 moderate: The synchronized crosshair is implemented via hand-written JavaScript - rather than leveraging pygal''s native tooltip/hover callbacks, making it a custom - HTML layer rather than a pygal-native interactive feature' - - 'DE-01 moderate: Design is thoughtful but constrained by pygal''s limited styling - options — no spine removal, no whitespace control beyond margins, no label weight - customization — capping aesthetic sophistication below publication level' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white consistent with #FAF8F1 — correct light theme surface. - Chrome: Title "dashboard-synchronized-crosshair · python · pygal · anyplot.ai" clearly readable in dark ink near the top. Panel sub-titles "Trading Volume" and "RSI Indicator" readable. Axis labels Price ($), Volume (M), RSI (0-100), Trading Day visible with units. Tick labels (price: 76–108; volume: 1–4; RSI: 0/30/50/70/100; dates: Jan 02 – Oct 07) all legible. Subtle 1px separator lines between panels visible. - Data: Price line in #009E73 (brand green, correct first series). Volume fill in #9418DB (purple, palette pos 2). RSI line in #B71D27 (red, palette pos 3) with muted reference lines at 70 and 30. Colors distinct and identifiable. - Legibility verdict: PASS — all text readable against warm off-white background, no light-on-light failures. - - Dark render (plot-dark.png): - Background: Warm near-black consistent with #1A1A17 — correct dark theme surface. - Chrome: Title and panel sub-titles visible in light text. Axis labels and tick labels rendered in light/cream colors clearly visible against the dark background. Separator lines between panels subtle but visible. Grid lines faint and non-dominant. - Data: Price #009E73, Volume #9418DB, RSI #B71D27 — identical to light render, only chrome flips. Reference lines in muted light grey appropriate for dark surface. - Legibility verdict: PASS — no dark-on-dark failures observed. All text elements clearly readable on the near-black background. Brand green (#009E73) remains visible on the dark surface. - criteria_checklist: - visual_quality: - score: 28 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: 'All font sizes explicitly set at style-guide values; both renders - legible; minor: pygal''s title rendering in dark mode appears slightly thinner - than light but fully readable' - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping elements in either render; rotated x-axis labels (20 - deg) prevent collision on dense date axis - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: dots_size=0 appropriate for 200-point time series; filled area for - volume provides excellent visibility; line weight 2.5 adequate - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Green/purple/red palette combo distinguishable under major CVD types; - no red-green sole signal - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 3200x1800 confirmed; three panels fill canvas proportionally (680+540+580=1800); - margins balanced - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Price ($), Volume (M), RSI (0-100), Trading Day — all descriptive - with units - - id: VQ-07 - name: Palette Compliance - score: 1 - max: 2 - passed: false - comment: 'Palette positions 1-3 used in canonical order at dashboard level; - backgrounds correct (#FAF8F1/#1A1A17); chrome theme-correct in both renders. - Deduction: volume and RSI sub-charts each have one series but it''s not - #009E73 — rule is ''first series always green'' per chart, not per dashboard' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Thoughtful dashboard design with separator lines, panel hierarchy, - reference lines; constrained by pygal's limited spine/whitespace control - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: x-axis suppressed on upper panels, legends suppressed, y-guides only; - some refinement evident; pygal defaults limit further polish - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Dashboard structure guides price->volume->RSI narrative; overbought/oversold - lines contextualize RSI interpretation; color coding creates panel identity - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Synchronized multi-chart dashboard with real interactive crosshair - in HTML; stacked vertical layout correct - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Synchronized crosshair (HTML), shared tooltip with all values, stacked - layout, independent y-axes, clear visual connection; zoom/pan not supported - by pygal natively (spec says 'when supported') - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Shared date x-axis, price/volume/RSI correctly mapped to panels 1/2/3 - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title 'dashboard-synchronized-crosshair · python · pygal · anyplot.ai' - correct; no legends (appropriate for single-series panels with sub-titles) - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: Price with trend, volume with spikes, RSI with overbought/oversold - zones shown; reference lines add context beyond spec - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: 200 trading weekdays starting 2024-01-02; stock price (random walk - from 100), volume in millions, RSI 0-100 — fully realistic financial scenario - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Price 76-108 (realistic range), volume 0.5-4.5M, RSI 0-100 with meaningful - thresholds — all domain-correct - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Flat top-level: imports -> data -> three chart configs -> render - -> composite PNG; no classes/functions' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: 'All imports used: io, json, os, sys, datetime.date/timedelta, numpy, - pygal, PIL.Image/ImageDraw, pygal.style.Style' - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: HTML/JS crosshair is real interactivity, not fake UI; clean f-string - HTML template; appropriate complexity - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html as required for interactive - library - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Uses pygal.Line with Style object, render(is_unicode=True) for SVG - embedding, render_to_png() for PNG; proper use of x_labels_major_count and - show_minor_x_labels for tick control - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Leverages pygal's SVG-string output (render(is_unicode=True)) to - embed charts in HTML dashboard; uses pygal's built-in interactivity infrastructure; - custom JS crosshair layer coordinates across panels - verdict: APPROVED -impl_tags: - dependencies: - - pillow - techniques: - - subplots - - html-export - - hover-tooltips - - annotations - patterns: - - data-generation - dataprep: - - time-series - - cumulative-sum - styling: - - alpha-blending diff --git a/plots/dashboard-synchronized-crosshair/metadata/python/seaborn.yaml b/plots/dashboard-synchronized-crosshair/metadata/python/seaborn.yaml deleted file mode 100644 index 13dbb20d0a..0000000000 --- a/plots/dashboard-synchronized-crosshair/metadata/python/seaborn.yaml +++ /dev/null @@ -1,259 +0,0 @@ -library: seaborn -language: python -specification_id: dashboard-synchronized-crosshair -created: '2026-01-20T19:37:56Z' -updated: '2026-05-23T18:47:40Z' -generated_by: claude-sonnet -workflow_run: 26332666120 -issue: 3784 -language_version: 3.13.13 -library_version: 0.13.2 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/seaborn/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/python/seaborn/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 88 -review: - strengths: - - 'Full spec compliance: all required dashboard features (stacked panels, shared - axis, crosshair demonstration, independent y-scales, value annotations) correctly - implemented with explicit acknowledgment of static-library constraints' - - 'Clean theme adaptation: sns.set_theme(rc={...}) propagates all chrome tokens - correctly; both renders pass the readability check with no dark-on-dark failures' - - 'Excellent data quality: realistic stock-market domain with proper business-day - frequency, meaningful RSI overbought/oversold bands, and a moving average secondary - series' - - Canvas-compliant figsize=(8, 4.5), dpi=400 without bbox_inches='tight' - - Correct Imprint palette order with semantic color assignments - weaknesses: - - 'LM-02 is limited: seaborn''s contribution is mostly theming and line plots; the - multi-panel crosshair mechanics are pure matplotlib (axvline, annotate, fill_between, - scatter, subplots) — the implementation would look nearly identical in plain matplotlib' - - Annotation bubble color in rendered images appears dark red rather than the code's - intended sky-blue (#16B8F3); images may be stale from a prior iteration — regeneration - with current code should confirm crosshair colors render as coded - - Volume and RSI panel area fills are subtle at alpha 0.22 and 0.15 respectively; - slightly higher alpha (0.25-0.30) would improve visual weight in both themes - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct anyplot light surface, not pure white - Chrome: Title "dashboard-synchronized-crosshair · python · seaborn · anyplot.ai" in dark ink, readable. Axis labels "Price ($)", "Volume", "RSI", "Date" in dark ink at 10pt. Tick labels at 8pt in INK_SOFT tone, rotated 30° on x-axis bottom panel. Legends in panel 1 ("Price", "20-day MA") and panel 3 ("Overbought (70)", "Oversold (30)") with semi-transparent frames. - Data: Three panels. Panel 1: green (#009E73) price line with green area fill (alpha=0.15) and gray dashed MA line. Panel 2: volume line and fill appear sky-blue/teal in rendered output (code sets #9418DB purple — possible stale images). Panel 3: RSI line appears purple/violet in rendered output (code sets #B71D27 red — same stale image concern). Vertical dashed crosshair spans all panels; annotation bubbles show $82.29, 8.2M, 57.9 (bubbles appear dark red in images, code sets #16B8F3 sky blue). - Legibility verdict: PASS — all text readable against light background, no light-on-light failures - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct anyplot dark surface, not pure black - Chrome: Title and all axis labels rendered in light text (#F0EFE8 / #B8B7B0), clearly readable against dark background. Legend frames use elevated-dark token (#242420). Tick labels in INK_SOFT light tone. No dark-on-dark text failures observed. - Data: Same three-panel layout. Data series colors appear identical to light render (green price, blue-teal volume, purple RSI). Crosshair and annotation bubbles appear same dark red color. Area fills are more subdued on dark background but lines remain clearly distinguishable. - Legibility verdict: PASS — all text readable against dark background, no dark-on-dark failures - criteria_checklist: - visual_quality: - score: 29 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: All font sizes explicitly set (title 12pt, labels 10pt, ticks 8pt, - legend 8pt). Readable in both themes. 8pt tick labels across three stacked - panels slightly small at mobile scale. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No text collisions. Annotation bubbles positioned with offset points - clear of data lines. Legends do not overlap data. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Price and RSI lines at 2.0pt well-sized for 200 points. Volume at - 1.5pt visible. Area fills at alpha 0.15-0.22 appropriately subtle. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Imprint palette CVD-safe. Data series distinguished by color and - panel position. No red-green sole signal. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Canvas gate passed (3200x1800). Height ratios 2:1:1. subplots_adjust - with generous margins and tight hspace=0.10. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Price ($), Volume (M formatter), RSI, Date — all descriptive with - units. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73. Palette order canonical. Backgrounds #FAF8F1/#1A1A17. - Chrome adapts correctly.' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Above default (4). Intentional color choices, area fills, pill annotation - bubbles, height ratios show design deliberation. Professional but not FiveThirtyEight - level. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: sns.despine on all panels, spine colors INK_SOFT, y-axis-only grid - alpha=0.10, tight hspace. Clearly above defaults (2). - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Static crosshair creates focal point across all three panels. RSI - bands add domain context. MA line provides trend reference. Visual hierarchy - guides reader. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Three-panel stacked dashboard with shared x-axis. - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Synchronized crosshair across all panels, value annotations, sharex=True, - independent y-scales, RSI bands. Spec permits annotations for static libraries. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Shared date x-axis; price/volume/RSI mapped to correct panels with - appropriate scales. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title correctly formatted. Panel 1 legend: Price, 20-day MA. Panel - 3 legend: Overbought (70), Oversold (30).' - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Three distinct metric types with MA secondary series. Full multi-panel - crosshair concept demonstrated with realistic variation. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: 'Stock market: 200 business days Jan-Sep 2024, random-walk price - $80-110, volume in millions, RSI 20-80. Neutral financial domain.' - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Price $80-110, volume 0-25M, RSI 15-85 — all match real-world financial - data. Business-day frequency correct. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear flow: theme tokens -> palette -> data -> plots -> save. No - functions or classes.' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: os, matplotlib.pyplot, numpy, pandas, seaborn — all used. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Well-organized with section comments. No fake UI. Honest comment - '# Crosshair position for static demonstration'. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png without bbox_inches='tight'. Current seaborn - 0.13.2 API. - library_mastery: - score: 6 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Good use of sns.lineplot with ax=, sns.set_theme with rc dict, sns.despine, - sns.set_context. Multi-panel via plt.subplots(sharex=True) is correct approach. - - id: LM-02 - name: Distinctive Features - score: 2 - max: 5 - passed: false - comment: Seaborn contribution limited to theming and line plots. Core mechanics - (axvline, annotate, fill_between, scatter, subplots) are pure matplotlib. - Partially inherent to this spec type for a static library. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - subplots - - annotations - patterns: - - data-generation - - explicit-figure - dataprep: - - time-series - - rolling-window - - cumulative-sum - styling: - - alpha-blending - - grid-styling - - edge-highlighting diff --git a/plots/dashboard-synchronized-crosshair/metadata/r/ggplot2.yaml b/plots/dashboard-synchronized-crosshair/metadata/r/ggplot2.yaml deleted file mode 100644 index 822ac554f5..0000000000 --- a/plots/dashboard-synchronized-crosshair/metadata/r/ggplot2.yaml +++ /dev/null @@ -1,256 +0,0 @@ -library: ggplot2 -language: r -specification_id: dashboard-synchronized-crosshair -created: '2026-05-23T12:51:23Z' -updated: '2026-05-23T13:07:21Z' -generated_by: claude-sonnet -workflow_run: 26333027645 -issue: 3784 -language_version: 4.4.1 -library_version: 3.5.1 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/r/ggplot2/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/dashboard-synchronized-crosshair/r/ggplot2/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 89 -review: - strengths: - - 'Perfect palette and theme compliance: Imprint palette positions 1-3 for the three - series, correct light/dark backgrounds (#FAF8F1/#1A1A17), all chrome adapts properly - between themes with no dark-on-dark or light-on-light failures' - - 'Full spec compliance for a static library: facet_wrap(ncol=1, scales=''free_y'') - correctly implements the stacked layout with independent y-axes and a shared x-axis, - with geom_vline spanning all panels as a static crosshair' - - RSI calculation from first principles using stats::filter demonstrates realistic - financial data with 14-period smoothing approximation - - Clean, reproducible code with set.seed(42), flat KISS structure, and idiomatic - ggplot2 patterns including strip.position='right' and strip.placement='outside' - - Both renders fully legible — title, subtitle, axis ticks, and strip labels all - readable in light and dark themes - weaknesses: - - RSI panel lacks standard overbought (70) and oversold (30) reference lines — adding - geom_hline(yintercept=c(30,70), linetype='dashed', color=INK_MUTED, linewidth=0.4) - would add meaningful domain context and improve DE-03 storytelling - - The 'Event' annotation on the crosshair is vague — labeling it with a descriptive - name (e.g., 'Earnings Release') would improve data storytelling without adding - complexity - - DE-01 could be elevated further with more intentional visual emphasis on key moments - — e.g., highlighting price extremes or marking where RSI crosses the overbought/oversold - thresholds - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct anyplot light surface, not pure white - Chrome: Bold title "dashboard-synchronized-crosshair · r · ggplot2 · anyplot.ai" in dark ink at top; subtitle "Tech stock 2024 — price, volume & RSI across 200 trading days" below in softer ink; x-axis date ticks (Feb '24 through Oct '24) clearly visible; strip labels "Price (USD)", "Volume (M)", "RSI (14)" on the right side in small elevated boxes - Data: Price panel uses #009E73 (brand green) for a smooth line ranging ~95-115 USD; Volume panel uses #9418DB (purple) for a noisy spike chart ranging ~5-25M; RSI panel uses #B71D27 (red) oscillating between ~20-80; a dashed vertical line at ~Jun '24 with "Event" label spans all three panels - Legibility verdict: PASS — all text readable against the warm off-white background; no light-on-light failures - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct anyplot dark surface, not pure black - Chrome: Title and subtitle visible in light off-white text; date axis tick labels readable in lighter gray; strip labels in elevated dark boxes (#242420) with light text — all legible against the dark background; no dark-on-dark failures detected - Data: Data colors are identical to light render — Price line #009E73, Volume line #9418DB, RSI line #B71D27 — palette positions 1-3 unchanged between themes as required; dashed event marker and "Event" label also visible - Legibility verdict: PASS — all text readable against the warm near-black background; no dark-on-dark failures observed - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All font sizes explicitly set (title=12, subtitle=9, axis.text=8, - axis.title=10, strip.text=9); both themes fully legible; no overflow - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No text or data element collisions; faceted panels provide ample - separation - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: linewidth=1.1 appropriate for 200-point time series; all three lines - clearly distinguishable - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: CVD-safe Imprint palette; series separated by facet panels for shape-independent - identification - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 3200x1800 landscape canvas (8x4.5in @ dpi=400); three panels fill - canvas well; balanced margins - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: 'Strip labels serve as y-axis labels with units: ''Price (USD)'', - ''Volume (M)'', ''RSI (14)''; informative subtitle' - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73 (brand green); positions 1-3 in canonical order; - backgrounds #FAF8F1/#1A1A17; all chrome theme-adaptive' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Above well-configured defaults: ELEVATED_BG strip backgrounds, clean - event marker, intentional hierarchy; not quite publication-ready' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: L-shaped spine frame (panel.border=blank + axis.line.x.bottom/y.left), - subtle grid with adjustcolor alpha=0.3, no minor grid, adequate panel spacing - - id: DE-03 - name: Data Storytelling - score: 3 - max: 6 - passed: true - comment: Event marker and subtitle provide context above default; but 'Event' - label is vague and RSI lacks conventional 30/70 reference lines - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Multi-panel stacked dashboard implemented correctly with facet_wrap; - correct static adaptation per spec guidance - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Synchronized crosshair via geom_vline across panels; stacked layout - via facet_wrap(ncol=1); independent y-axes via scales='free_y'; shared x-axis - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Date on shared x-axis; three metrics on independent y-axes; 200 trading - days as specified - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title 'dashboard-synchronized-crosshair · r · ggplot2 · anyplot.ai'; - strip labels serve as legend; subtitle adds context - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Three distinct financial metrics (price, volume, RSI) demonstrating - the multi-panel dashboard pattern fully - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Tech stock 2024 scenario; realistic price/volume/RSI values; neutral - financial data - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Price ~95-115 USD plausible for mid-cap tech; volume ~5-25M; RSI - 20-80 realistic oscillation - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Flat script: theme tokens → data generation → plot → save' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: set.seed(42) at top - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: 'ggplot2, dplyr, scales, ragg — all used; tibble:: and stats:: via - namespace' - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Appropriate complexity; RSI calculation from first principles; no - fake UI - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: sprintf('plot-%s.png', THEME) → plot-light.png/plot-dark.png; ragg::agg_png - device - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: facet_wrap with free_y scales, scale_x_date with date_labels/date_breaks, - expansion(), label_number() — all idiomatic - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: strip.position='right' + strip.placement='outside'; geom_text with - y=Inf for panel-relative coordinates; adjustcolor() for grid alpha - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - faceting - - annotations - - layer-composition - patterns: - - data-generation - dataprep: - - time-series - - rolling-window - styling: - - grid-styling diff --git a/plots/dashboard-synchronized-crosshair/specification.md b/plots/dashboard-synchronized-crosshair/specification.md deleted file mode 100644 index 17f95e1961..0000000000 --- a/plots/dashboard-synchronized-crosshair/specification.md +++ /dev/null @@ -1,31 +0,0 @@ -# dashboard-synchronized-crosshair: Synchronized Multi-Chart Dashboard - -## Description - -A multi-chart dashboard layout where hovering over one chart displays synchronized crosshairs and tooltips across all other charts at the same x-axis position. This interaction pattern is essential for comparing multiple related metrics over a shared time axis, allowing users to instantly see how different variables relate at any given point. The vertical crosshair line spans all charts simultaneously, while tooltips display values from each chart at the hover position. - -## Applications - -- Multi-indicator financial analysis comparing price, volume, RSI, and MACD with synchronized cursors for correlated signal detection -- Correlated time series comparison in scientific research where multiple measurements share a common timeline -- Operations dashboards linking CPU, memory, network, and disk metrics with unified hover inspection -- Exploratory data analysis with linked views for discovering temporal relationships across variables - -## Data - -- `date` (datetime) - Shared time axis for all charts -- `series_1` (numeric) - Values for the first chart (e.g., price) -- `series_2` (numeric) - Values for the second chart (e.g., volume) -- `series_3` (numeric) - Values for the third chart (e.g., indicator) -- Size: 100-500 time points for responsive interactivity -- Example: Stock data with price, volume, and technical indicators over 200 trading days - -## Notes - -- Synchronized vertical crosshair line that spans all charts at the hover position -- Shared tooltip showing values from all series at the current x-position -- Stacked vertical layout with charts sharing a common x-axis -- Synchronized zoom and pan across all charts when supported by the library -- Independent y-axis scales for each chart to accommodate different value ranges -- Clear visual connection between charts (shared axis, aligned grids) -- For static libraries, demonstrate the layout structure with annotations; for interactive libraries, implement full crosshair synchronization diff --git a/plots/dashboard-synchronized-crosshair/specification.yaml b/plots/dashboard-synchronized-crosshair/specification.yaml deleted file mode 100644 index 7fee7921d9..0000000000 --- a/plots/dashboard-synchronized-crosshair/specification.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# Specification-level metadata for dashboard-synchronized-crosshair -# Auto-synced to PostgreSQL on push to main - -spec_id: dashboard-synchronized-crosshair -title: Synchronized Multi-Chart Dashboard - -# Specification tracking -created: 2026-01-11T22:53:07Z -updated: 2026-05-23T12:23:32Z -issue: 3784 -suggested: MarkusNeusinger - -# Classification tags (applies to all library implementations) -# See docs/reference/tagging-system.md for detailed guidelines -tags: - plot_type: - - line - data_type: - - timeseries - - numeric - domain: - - finance - - general - features: - - interactive - - multi - - temporal - - comparison diff --git a/plots/hierarchy-toggle-view/implementations/python/altair.py b/plots/hierarchy-toggle-view/implementations/python/altair.py deleted file mode 100644 index 8c73331b44..0000000000 --- a/plots/hierarchy-toggle-view/implementations/python/altair.py +++ /dev/null @@ -1,259 +0,0 @@ -""" anyplot.ai -hierarchy-toggle-view: Interactive Treemap-Sunburst Toggle View -Library: altair 6.1.0 | Python 3.13.13 -Quality: 90/100 | Updated: 2026-05-19 -""" - -import os - -import altair as alt -import numpy as np -import pandas as pd - - -# 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" - -DEPT_DOMAIN = ["Engineering", "Sales", "Marketing", "Operations"] -DEPT_COLORS = ["#009E73", "#C475FD", "#4467A3", "#BD8233"] - -# Data -np.random.seed(42) - -hierarchy_data = [ - {"id": "Company", "parent": "", "label": "TechCorp", "value": 0}, - {"id": "Engineering", "parent": "Company", "label": "Engineering", "value": 0}, - {"id": "Sales", "parent": "Company", "label": "Sales", "value": 0}, - {"id": "Marketing", "parent": "Company", "label": "Marketing", "value": 0}, - {"id": "Operations", "parent": "Company", "label": "Operations", "value": 0}, - {"id": "Frontend", "parent": "Engineering", "label": "Frontend", "value": 55}, - {"id": "Backend", "parent": "Engineering", "label": "Backend", "value": 62}, - {"id": "DevOps", "parent": "Engineering", "label": "DevOps", "value": 38}, - {"id": "QA", "parent": "Engineering", "label": "QA", "value": 32}, - {"id": "DataSci", "parent": "Engineering", "label": "Data Sci", "value": 45}, - {"id": "Enterprise", "parent": "Sales", "label": "Enterprise", "value": 48}, - {"id": "SMB", "parent": "Sales", "label": "SMB", "value": 35}, - {"id": "Partners", "parent": "Sales", "label": "Partners", "value": 28}, - {"id": "Digital", "parent": "Marketing", "label": "Digital", "value": 30}, - {"id": "Content", "parent": "Marketing", "label": "Content", "value": 25}, - {"id": "Events", "parent": "Marketing", "label": "Events", "value": 22}, - {"id": "HR", "parent": "Operations", "label": "HR", "value": 28}, - {"id": "Finance", "parent": "Operations", "label": "Finance", "value": 32}, - {"id": "Legal", "parent": "Operations", "label": "Legal", "value": 20}, -] - -df = pd.DataFrame(hierarchy_data) - -# Calculate parent values (sum of leaf children) -parent_ids = df[df["parent"] != ""]["parent"].unique() -for _ in range(5): - for parent_id in parent_ids: - children = df[df["parent"] == parent_id] - if len(children) > 0 and all(children["value"] > 0): - df.loc[df["id"] == parent_id, "value"] = children["value"].sum() - -# Assign department grouping -for dept in DEPT_DOMAIN: - df.loc[df["parent"] == dept, "department"] = dept -df.loc[df["id"].isin(DEPT_DOMAIN), "department"] = df.loc[df["id"].isin(DEPT_DOMAIN), "id"] -df["department"] = df["department"].fillna("Company") - -# Treemap layout (leaf nodes only) - fills [0,100] x [0,100] coordinate space -all_parent_ids = set(df["parent"].unique()) -leaf_mask = ~df["id"].isin(all_parent_ids - {""}) -leaf_df = df[leaf_mask & (df["value"] > 0)].copy() - -sorted_leaf = leaf_df.sort_values("value", ascending=False).reset_index(drop=True) -items = [(row["value"], row["id"], row["label"], row["department"]) for _, row in sorted_leaf.iterrows()] - -layout_stack = [(items, 0, 0, 100, 100)] -treemap_records = [] - -while layout_stack: - current_items, x, y, w, h = layout_stack.pop() - if not current_items: - continue - if len(current_items) == 1: - val, nid, lbl, dept = current_items[0] - treemap_records.append( - { - "id": nid, - "label": lbl, - "value": val, - "department": dept, - "x": x, - "y": y, - "x2": x + w, - "y2": y + h, - "cx": x + w / 2, - "cy": y + h / 2, - "view": "Treemap", - } - ) - continue - total = sum(v for v, _, _, _ in current_items) - half_val = total / 2 - cumsum = 0 - split_idx = 1 - for i, (val, _, _, _) in enumerate(current_items): - cumsum += val - if cumsum >= half_val: - split_idx = i + 1 - break - left_items = current_items[:split_idx] - right_items = current_items[split_idx:] - left_total = sum(v for v, _, _, _ in left_items) - ratio = left_total / total if total > 0 else 0.5 - if w >= h: - layout_stack.append((right_items, x + w * ratio, y, w * (1 - ratio), h)) - layout_stack.append((left_items, x, y, w * ratio, h)) - else: - layout_stack.append((right_items, x, y + h * ratio, w, h * (1 - ratio))) - layout_stack.append((left_items, x, y, w, h * ratio)) - -treemap_df = pd.DataFrame(treemap_records) - -# Sunburst data (polar arc segments, no x/y to avoid scale conflict with treemap) -sunburst_records = [] -dept_values = {d: df[df["id"] == d]["value"].values[0] for d in DEPT_DOMAIN} -total_company = sum(dept_values.values()) - -start_angle = 0 -for dept in DEPT_DOMAIN: - dept_angle = 360 * (dept_values[dept] / total_company) - end_angle = start_angle + dept_angle - sunburst_records.append( - { - "id": dept, - "label": dept, - "value": dept_values[dept], - "department": dept, - "startAngle": start_angle, - "endAngle": end_angle, - "innerRadius": 35, - "outerRadius": 65, - "depth": 1, - "view": "Sunburst", - } - ) - teams = df[df["parent"] == dept] - dept_start = start_angle - for _, team in teams.iterrows(): - team_angle = dept_angle * (team["value"] / dept_values[dept]) if dept_values[dept] > 0 else 0 - team_end = dept_start + team_angle - sunburst_records.append( - { - "id": team["id"], - "label": team["label"], - "value": team["value"], - "department": dept, - "startAngle": dept_start, - "endAngle": team_end, - "innerRadius": 65, - "outerRadius": 100, - "depth": 2, - "view": "Sunburst", - } - ) - dept_start = team_end - start_angle = end_angle - -sunburst_df = pd.DataFrame(sunburst_records) -sunburst_df["startAngle_rad"] = np.radians(sunburst_df["startAngle"] - 90) -sunburst_df["endAngle_rad"] = np.radians(sunburst_df["endAngle"] - 90) - -# Interactive toggle -view_dropdown = alt.binding_select(options=["Treemap", "Sunburst"], name="Select View: ") -view_selection = alt.selection_point(fields=["view"], bind=view_dropdown, value="Treemap") - -color_scale = alt.Scale(domain=DEPT_DOMAIN, range=DEPT_COLORS) -color_legend = alt.Legend(title="Department", titleFontSize=20, labelFontSize=18, orient="right") - -# Treemap - x/y domain [0,100] fills full canvas (1600x900) -treemap_rects = ( - alt.Chart(treemap_df) - .mark_rect(stroke=PAGE_BG, strokeWidth=3) - .encode( - x=alt.X("x:Q", scale=alt.Scale(domain=[0, 100]), axis=None), - y=alt.Y("y:Q", scale=alt.Scale(domain=[0, 100]), axis=None), - x2="x2:Q", - y2="y2:Q", - color=alt.Color("department:N", scale=color_scale, legend=color_legend), - opacity=alt.condition(view_selection, alt.value(1), alt.value(0)), - tooltip=[ - alt.Tooltip("label:N", title="Team"), - alt.Tooltip("value:Q", title="Headcount"), - alt.Tooltip("department:N", title="Department"), - ], - ) - .add_params(view_selection) -) - -treemap_labels = ( - alt.Chart(treemap_df) - .mark_text(fontWeight="bold", color="white", baseline="middle", align="center") - .encode( - x=alt.X("cx:Q", scale=alt.Scale(domain=[0, 100])), - y=alt.Y("cy:Q", scale=alt.Scale(domain=[0, 100])), - text="label:N", - opacity=alt.condition(view_selection, alt.value(1), alt.value(0)), - size=alt.Size("value:Q", scale=alt.Scale(domain=[20, 65], range=[14, 24]), legend=None), - ) -) - -treemap_chart = treemap_rects + treemap_labels - -# Sunburst - uses only polar channels (theta, radius), no x/y, so no scale conflict -sunburst_arcs = ( - alt.Chart(sunburst_df) - .mark_arc(stroke=PAGE_BG, strokeWidth=2) - .encode( - theta=alt.Theta("endAngle_rad:Q", scale=alt.Scale(domain=[-np.pi, np.pi])), - theta2="startAngle_rad:Q", - radius=alt.Radius("outerRadius:Q", scale=alt.Scale(domain=[0, 120], range=[0, 420])), - radius2="innerRadius:Q", - color=alt.Color("department:N", scale=color_scale, legend=color_legend), - opacity=alt.condition(view_selection, alt.value(1), alt.value(0)), - tooltip=[ - alt.Tooltip("label:N", title="Name"), - alt.Tooltip("value:Q", title="Headcount"), - alt.Tooltip("department:N", title="Department"), - ], - ) -) - -# Combine - treemap x/y domain [0,100] governs the shared axes; -# sunburst polar channels don't conflict, so treemap fills the full canvas -combined_chart = ( - alt.layer(treemap_chart, sunburst_arcs) - .properties( - width=1600, - height=900, - title=alt.Title( - "hierarchy-toggle-view · python · altair · anyplot.ai", - fontSize=28, - anchor="middle", - offset=20, - subtitle="Use dropdown to toggle between Treemap and Sunburst views", - subtitleFontSize=18, - ), - ) - .configure_view(strokeWidth=0, fill=PAGE_BG) - .configure_title(color=INK, subtitleColor=INK_SOFT) - .configure_legend( - fillColor=ELEVATED_BG, - strokeColor=INK_SOFT, - labelColor=INK_SOFT, - titleColor=INK, - titleFontSize=20, - labelFontSize=18, - symbolSize=200, - ) -) - -combined_chart.save(f"plot-{THEME}.png", scale_factor=3.0) -combined_chart.save(f"plot-{THEME}.html") diff --git a/plots/hierarchy-toggle-view/implementations/python/bokeh.py b/plots/hierarchy-toggle-view/implementations/python/bokeh.py deleted file mode 100644 index a5149ec5e0..0000000000 --- a/plots/hierarchy-toggle-view/implementations/python/bokeh.py +++ /dev/null @@ -1,524 +0,0 @@ -""" anyplot.ai -hierarchy-toggle-view: Interactive Treemap-Sunburst Toggle View -Library: bokeh 3.9.0 | Python 3.13.13 -Quality: 85/100 | Updated: 2026-05-19 -""" - -import os -import sys - - -# Prevent self-import: when run as `python bokeh.py`, the script directory is -# prepended to sys.path, which makes `import bokeh` find this file instead of -# the installed bokeh package. Remove the script directory before other imports. -_script_dir = os.path.dirname(os.path.realpath(__file__)) -sys.path = [p for p in sys.path if p and os.path.realpath(p) != _script_dir] - -import time # noqa: E402 -from pathlib import Path # noqa: E402 - -import numpy as np # noqa: E402 -from bokeh.io import output_file, save # noqa: E402 -from bokeh.layouts import column, row # noqa: E402 -from bokeh.models import Button, ColumnDataSource, CustomJS, Label, Legend, LegendItem # noqa: E402 -from bokeh.plotting import figure # noqa: E402 -from bokeh.resources import CDN # noqa: E402 -from selenium import webdriver # noqa: E402 -from selenium.webdriver.chrome.options import Options # noqa: E402 - - -# Theme -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" - -# Okabe-Ito colors for top-level departments; derived shades for sub-departments -DEPT_COLORS = {"engineering": "#009E73", "marketing": "#C475FD", "operations": "#4467A3", "hr": "#BD8233"} -SUB_COLORS = { - "frontend": "#00C48D", - "backend": "#007A58", - "devops": "#33B48A", - "qa": "#66C8A8", - "digital": "#FF7722", - "content": "#AA4800", - "analytics": "#E88240", - "logistics": "#0092E0", - "facilities": "#005688", - "procurement": "#3398CC", - "recruiting": "#E099C0", - "training": "#A85C85", - "benefits": "#D9A3C4", -} -NODE_COLORS = {**DEPT_COLORS, **SUB_COLORS} - -# Data — company budget hierarchy -np.random.seed(42) -hierarchy = [ - {"id": "company", "parent": "", "label": "Company", "value": 0}, - {"id": "engineering", "parent": "company", "label": "Engineering", "value": 0}, - {"id": "marketing", "parent": "company", "label": "Marketing", "value": 0}, - {"id": "operations", "parent": "company", "label": "Operations", "value": 0}, - {"id": "hr", "parent": "company", "label": "HR", "value": 0}, - {"id": "frontend", "parent": "engineering", "label": "Frontend", "value": 1200}, - {"id": "backend", "parent": "engineering", "label": "Backend", "value": 1500}, - {"id": "devops", "parent": "engineering", "label": "DevOps", "value": 800}, - {"id": "qa", "parent": "engineering", "label": "QA", "value": 600}, - {"id": "digital", "parent": "marketing", "label": "Digital", "value": 900}, - {"id": "content", "parent": "marketing", "label": "Content", "value": 700}, - {"id": "analytics", "parent": "marketing", "label": "Analytics", "value": 500}, - {"id": "logistics", "parent": "operations", "label": "Logistics", "value": 1100}, - {"id": "facilities", "parent": "operations", "label": "Facilities", "value": 600}, - {"id": "procurement", "parent": "operations", "label": "Procurement", "value": 450}, - {"id": "recruiting", "parent": "hr", "label": "Recruiting", "value": 400}, - {"id": "training", "parent": "hr", "label": "Training", "value": 350}, - {"id": "benefits", "parent": "hr", "label": "Benefits", "value": 300}, -] - -id_to_node = {n["id"]: n for n in hierarchy} -for node in reversed(hierarchy): - if node["value"] == 0: - node["value"] = sum(n["value"] for n in hierarchy if n["parent"] == node["id"]) - -leaf_nodes = [n for n in hierarchy if not any(m["parent"] == n["id"] for m in hierarchy)] -leaf_values = [n["value"] for n in leaf_nodes] - -# Squarify treemap layout — left panel bounding box -sq_x, sq_y, sq_w, sq_h = 150, 380, 2100, 2150 -sq_total = sum(leaf_values) -sq_remaining = list(enumerate(leaf_values)) -treemap_rects = [] - -while sq_remaining: - if sq_w >= sq_h: - strip, strip_sum, best_ratio, strip_idx = [], 0, float("inf"), 0 - for i, (idx, v) in enumerate(sq_remaining): - test = strip + [(idx, v)] - s = strip_sum + v - sw = (s / sq_total) * sq_w if sq_total > 0 else sq_w - ratios = [max(sw / max((sv / s) * sq_h, 1e-9), max((sv / s) * sq_h, 1e-9) / sw) for _, sv in test if s > 0] - worst = max(ratios) if ratios else float("inf") - if worst <= best_ratio: - strip, strip_sum, best_ratio, strip_idx = test, s, worst, i + 1 - else: - break - sw = (strip_sum / sq_total) * sq_w if sq_total > 0 else sq_w - cy = sq_y - for idx, sv in strip: - sh = (sv / strip_sum) * sq_h if strip_sum > 0 else sq_h / len(strip) - treemap_rects.append((idx, sq_x, cy, sw, sh)) - cy += sh - sq_x += sw - sq_w -= sw - sq_total -= strip_sum - sq_remaining = sq_remaining[strip_idx:] - else: - strip, strip_sum, best_ratio, strip_idx = [], 0, float("inf"), 0 - for i, (idx, v) in enumerate(sq_remaining): - test = strip + [(idx, v)] - s = strip_sum + v - sh = (s / sq_total) * sq_h if sq_total > 0 else sq_h - ratios = [max(sh / max((sv / s) * sq_w, 1e-9), max((sv / s) * sq_w, 1e-9) / sh) for _, sv in test if s > 0] - worst = max(ratios) if ratios else float("inf") - if worst <= best_ratio: - strip, strip_sum, best_ratio, strip_idx = test, s, worst, i + 1 - else: - break - sh = (strip_sum / sq_total) * sq_h if sq_total > 0 else sq_h - cx = sq_x - for idx, sv in strip: - sw = (sv / strip_sum) * sq_w if strip_sum > 0 else sq_w / len(strip) - treemap_rects.append((idx, cx, sq_y, sw, sh)) - cx += sw - sq_y += sh - sq_h -= sh - sq_total -= strip_sum - sq_remaining = sq_remaining[strip_idx:] - -# Build treemap data arrays (side-by-side positions for PNG static figure) -tm_cx_arr, tm_cy_arr = [], [] -tm_w_arr, tm_h_arr = [], [] -tm_colors, tm_tooltip, tm_text, tm_vals = [], [], [], [] -tm_lx, tm_ly, tm_vx, tm_vy = [], [], [], [] - -for idx, rx, ry, rw, rh in treemap_rects: - node = leaf_nodes[idx] - cx_val = rx + rw / 2 - cy_val = ry + rh / 2 - tm_cx_arr.append(cx_val) - tm_cy_arr.append(cy_val) - tm_w_arr.append(max(rw - 6, 1)) - tm_h_arr.append(max(rh - 6, 1)) - tm_colors.append(NODE_COLORS.get(node["id"], "#999999")) - tm_tooltip.append(f"{node['label']}: ${node['value']}K") - tm_text.append(node["label"]) - tm_vals.append(f"${node['value']}K") - tm_lx.append(cx_val) - tm_ly.append(cy_val + 38) - tm_vx.append(cx_val) - tm_vy.append(cy_val - 38) - -# Sunburst layout — right panel, center (3480, 1455) -SCX, SCY = 3480, 1455 -DEPT_INNER, DEPT_OUTER = 170, 490 -SUB_INNER, SUB_OUTER = 510, 840 -total_val = id_to_node["company"]["value"] - -sb_inner, sb_outer, sb_start, sb_end = [], [], [], [] -sb_colors, sb_tooltip = [], [] - -angle_cursor = 0.0 -departments = [n for n in hierarchy if n["parent"] == "company"] -for dept in departments: - dept_span = (dept["value"] / total_val) * 2 * np.pi - sb_inner.append(DEPT_INNER) - sb_outer.append(DEPT_OUTER) - sb_start.append(angle_cursor) - sb_end.append(angle_cursor + dept_span) - sb_colors.append(DEPT_COLORS.get(dept["id"], "#999999")) - sb_tooltip.append(f"{dept['label']}: ${dept['value']}K") - - sub_cursor = angle_cursor - for sub in [n for n in hierarchy if n["parent"] == dept["id"]]: - sub_span = (sub["value"] / total_val) * 2 * np.pi - sb_inner.append(SUB_INNER) - sb_outer.append(SUB_OUTER) - sb_start.append(sub_cursor) - sb_end.append(sub_cursor + sub_span) - sb_colors.append(SUB_COLORS.get(sub["id"], "#999999")) - sb_tooltip.append(f"{sub['label']}: ${sub['value']}K") - sub_cursor += sub_span - - angle_cursor += dept_span - -# Convert sunburst wedges to patch polygons -sb_xs_list, sb_ys_list = [], [] -for i in range(len(sb_inner)): - angles = np.linspace(sb_start[i], sb_end[i], 32) - ix = SCX + sb_inner[i] * np.cos(angles) - iy = SCY + sb_inner[i] * np.sin(angles) - ox = SCX + sb_outer[i] * np.cos(angles[::-1]) - oy = SCY + sb_outer[i] * np.sin(angles[::-1]) - sb_xs_list.append(list(ix) + list(ox)) - sb_ys_list.append(list(iy) + list(oy)) - -# --- Static side-by-side figure (for PNG screenshot) --- -p_static = figure( - width=4800, - height=2700, - x_range=(-50, 4750), - y_range=(0, 2700), - tools="hover", - tooltips="@tooltip", - toolbar_location=None, -) - -p_static.title.text = "hierarchy-toggle-view · python · bokeh · anyplot.ai" -p_static.title.text_font_size = "28pt" -p_static.title.text_color = INK -p_static.title.text_font_style = "bold" - -p_static.background_fill_color = PAGE_BG -p_static.border_fill_color = PAGE_BG -p_static.outline_line_color = None -p_static.xaxis.visible = False -p_static.yaxis.visible = False -p_static.grid.visible = False - -# Panel header labels -p_static.add_layout( - Label( - x=1200, - y=2560, - text="Treemap View", - text_font_size="26pt", - text_color=INK, - text_font_style="bold", - text_align="center", - ) -) -p_static.add_layout( - Label( - x=3480, - y=2560, - text="Sunburst View", - text_font_size="26pt", - text_color=INK, - text_font_style="bold", - text_align="center", - ) -) - -# Vertical divider -p_static.segment(x0=[2350], y0=[100], x1=[2350], y1=[2500], line_color=INK_SOFT, line_width=2, line_alpha=0.4) - -# Treemap rectangles -tm_source = ColumnDataSource( - data={ - "x": tm_cx_arr, - "y": tm_cy_arr, - "width": tm_w_arr, - "height": tm_h_arr, - "color": tm_colors, - "tooltip": tm_tooltip, - } -) -p_static.rect( - x="x", - y="y", - width="width", - height="height", - color="color", - source=tm_source, - line_color=PAGE_BG, - line_width=3, - alpha=0.92, -) - -# Treemap labels (only for large enough rectangles) -large_mask = [w > 180 and h > 100 for w, h in zip(tm_w_arr, tm_h_arr, strict=True)] -p_static.text( - x=[tm_lx[i] for i in range(len(tm_lx)) if large_mask[i]], - y=[tm_ly[i] for i in range(len(tm_ly)) if large_mask[i]], - text=[tm_text[i] for i in range(len(tm_text)) if large_mask[i]], - text_font_size="18pt", - text_color=INK, - text_align="center", - text_baseline="middle", - text_font_style="bold", -) -p_static.text( - x=[tm_vx[i] for i in range(len(tm_vx)) if large_mask[i]], - y=[tm_vy[i] for i in range(len(tm_vy)) if large_mask[i]], - text=[tm_vals[i] for i in range(len(tm_vals)) if large_mask[i]], - text_font_size="15pt", - text_color=INK_SOFT, - text_align="center", - text_baseline="middle", -) - -# Sunburst patches -sb_source = ColumnDataSource(data={"xs": sb_xs_list, "ys": sb_ys_list, "color": sb_colors, "tooltip": sb_tooltip}) -p_static.patches(xs="xs", ys="ys", color="color", source=sb_source, line_color=PAGE_BG, line_width=3, alpha=0.92) - -# Legend by department -dept_order = ["engineering", "marketing", "operations", "hr"] -dept_labels_map = {"engineering": "Engineering", "marketing": "Marketing", "operations": "Operations", "hr": "HR"} -subdept_order = { - "engineering": ["backend", "frontend", "devops", "qa"], - "marketing": ["digital", "content", "analytics"], - "operations": ["logistics", "facilities", "procurement"], - "hr": ["recruiting", "training", "benefits"], -} -legend_items = [] -for d_id in dept_order: - r = p_static.rect(x=[-5000], y=[-5000], width=1, height=1, color=DEPT_COLORS[d_id], alpha=0.92) - legend_items.append(LegendItem(label=dept_labels_map[d_id], renderers=[r])) - for s_id in subdept_order[d_id]: - r = p_static.rect(x=[-5000], y=[-5000], width=1, height=1, color=SUB_COLORS[s_id], alpha=0.92) - legend_items.append(LegendItem(label=f" {id_to_node[s_id]['label']}", renderers=[r])) - -legend = Legend( - items=legend_items, - location="top_right", - title="Budget Hierarchy", - title_text_font_size="18pt", - label_text_font_size="14pt", - background_fill_color=ELEVATED_BG, - border_line_color=INK_SOFT, - label_text_color=INK_SOFT, - title_text_color=INK, - glyph_height=22, - glyph_width=22, - spacing=4, - padding=14, -) -p_static.add_layout(legend, "right") - -# --- HTML toggle figure --- -# Centered treemap: offset x by +1100 so center is at ~2300 -H_OFF = 1100 -html_tm_cx = [x + H_OFF for x in tm_cx_arr] -html_tm_lx = [x + H_OFF for x in tm_lx] -html_tm_vx = [x + H_OFF for x in tm_vx] - -# Centered sunburst: center at (2300, 1455) -H_SCX = 2300 -sb_xs_html, sb_ys_html = [], [] -for i in range(len(sb_inner)): - angles = np.linspace(sb_start[i], sb_end[i], 32) - ix = H_SCX + sb_inner[i] * np.cos(angles) - iy = SCY + sb_inner[i] * np.sin(angles) - ox = H_SCX + sb_outer[i] * np.cos(angles[::-1]) - oy = SCY + sb_outer[i] * np.sin(angles[::-1]) - sb_xs_html.append(list(ix) + list(ox)) - sb_ys_html.append(list(iy) + list(oy)) - -p_html = figure( - width=4800, - height=2700, - x_range=(-50, 4750), - y_range=(0, 2700), - tools="hover", - tooltips="@tooltip", - toolbar_location=None, -) - -p_html.title.text = "hierarchy-toggle-view · python · bokeh · anyplot.ai" -p_html.title.text_font_size = "28pt" -p_html.title.text_color = INK -p_html.title.text_font_style = "bold" -p_html.background_fill_color = PAGE_BG -p_html.border_fill_color = PAGE_BG -p_html.outline_line_color = None -p_html.xaxis.visible = False -p_html.yaxis.visible = False -p_html.grid.visible = False - -html_tm_source = ColumnDataSource( - data={ - "x": html_tm_cx, - "y": tm_cy_arr, - "width": tm_w_arr, - "height": tm_h_arr, - "color": tm_colors, - "tooltip": tm_tooltip, - } -) -html_tm_rect = p_html.rect( - x="x", - y="y", - width="width", - height="height", - color="color", - source=html_tm_source, - line_color=PAGE_BG, - line_width=3, - alpha=0.92, -) -html_tm_name = p_html.text( - x=[html_tm_lx[i] for i in range(len(html_tm_lx)) if large_mask[i]], - y=[tm_ly[i] for i in range(len(tm_ly)) if large_mask[i]], - text=[tm_text[i] for i in range(len(tm_text)) if large_mask[i]], - text_font_size="18pt", - text_color=INK, - text_align="center", - text_baseline="middle", - text_font_style="bold", -) -html_tm_val = p_html.text( - x=[html_tm_vx[i] for i in range(len(html_tm_vx)) if large_mask[i]], - y=[tm_vy[i] for i in range(len(tm_vy)) if large_mask[i]], - text=[tm_vals[i] for i in range(len(tm_vals)) if large_mask[i]], - text_font_size="15pt", - text_color=INK_SOFT, - text_align="center", - text_baseline="middle", -) - -html_sb_source = ColumnDataSource(data={"xs": sb_xs_html, "ys": sb_ys_html, "color": sb_colors, "tooltip": sb_tooltip}) -html_sb_patches = p_html.patches( - xs="xs", ys="ys", color="color", source=html_sb_source, line_color=PAGE_BG, line_width=3, alpha=0.92 -) -html_sb_patches.visible = False - -# Legend for HTML figure -html_legend_items = [] -for d_id in dept_order: - r = p_html.rect(x=[-5000], y=[-5000], width=1, height=1, color=DEPT_COLORS[d_id], alpha=0.92) - html_legend_items.append(LegendItem(label=dept_labels_map[d_id], renderers=[r])) - for s_id in subdept_order[d_id]: - r = p_html.rect(x=[-5000], y=[-5000], width=1, height=1, color=SUB_COLORS[s_id], alpha=0.92) - html_legend_items.append(LegendItem(label=f" {id_to_node[s_id]['label']}", renderers=[r])) - -html_legend = Legend( - items=html_legend_items, - location="top_right", - title="Budget Hierarchy", - title_text_font_size="18pt", - label_text_font_size="14pt", - background_fill_color=ELEVATED_BG, - border_line_color=INK_SOFT, - label_text_color=INK_SOFT, - title_text_color=INK, - glyph_height=22, - glyph_width=22, - spacing=4, - padding=14, -) -p_html.add_layout(html_legend, "right") - -# Toggle buttons -btn_treemap = Button(label="Treemap View", button_type="primary", width=220, height=55) -btn_sunburst = Button(label="Sunburst View", button_type="default", width=220, height=55) - -toggle_treemap_js = CustomJS( - args={ - "tm_rect": html_tm_rect, - "tm_name": html_tm_name, - "tm_val": html_tm_val, - "sb": html_sb_patches, - "btn_tm": btn_treemap, - "btn_sb": btn_sunburst, - }, - code=""" - tm_rect.visible = true; - tm_name.visible = true; - tm_val.visible = true; - sb.visible = false; - btn_tm.button_type = 'primary'; - btn_sb.button_type = 'default'; - """, -) -toggle_sunburst_js = CustomJS( - args={ - "tm_rect": html_tm_rect, - "tm_name": html_tm_name, - "tm_val": html_tm_val, - "sb": html_sb_patches, - "btn_tm": btn_treemap, - "btn_sb": btn_sunburst, - }, - code=""" - tm_rect.visible = false; - tm_name.visible = false; - tm_val.visible = false; - sb.visible = true; - btn_tm.button_type = 'default'; - btn_sb.button_type = 'primary'; - """, -) - -btn_treemap.js_on_click(toggle_treemap_js) -btn_sunburst.js_on_click(toggle_sunburst_js) - -html_layout = column(row(btn_treemap, btn_sunburst), p_html) - -# Save HTML (toggle interactive version) -output_file(f"plot-{THEME}.html") -save(html_layout, resources=CDN, title="Hierarchy Toggle View") - -# Screenshot static side-by-side figure with Selenium -_static_html = f"_plot_static_{THEME}.html" -output_file(_static_html) -save(p_static, resources=CDN, title="Hierarchy Toggle View Static") - -W, H = 4800, 2700 -opts = Options() -for arg in ( - "--headless=new", - "--no-sandbox", - "--disable-dev-shm-usage", - "--disable-gpu", - f"--window-size={W},{H}", - "--hide-scrollbars", -): - opts.add_argument(arg) - -driver = webdriver.Chrome(options=opts) -driver.set_window_size(W, H) -driver.get(f"file://{Path(_static_html).resolve()}") -time.sleep(3) -driver.save_screenshot(f"plot-{THEME}.png") -driver.quit() diff --git a/plots/hierarchy-toggle-view/implementations/python/letsplot.py b/plots/hierarchy-toggle-view/implementations/python/letsplot.py deleted file mode 100644 index de90747cb7..0000000000 --- a/plots/hierarchy-toggle-view/implementations/python/letsplot.py +++ /dev/null @@ -1,505 +0,0 @@ -""" anyplot.ai -hierarchy-toggle-view: Interactive Treemap-Sunburst Toggle View -Library: letsplot 4.9.0 | Python 3.13.13 -Quality: 80/100 | Updated: 2026-05-19 -""" - -import json -import math -import os - -import numpy as np -import pandas as pd -from lets_plot import ( - LetsPlot, - aes, - coord_fixed, - element_blank, - element_rect, - element_text, - geom_polygon, - geom_rect, - geom_text, - gggrid, - ggplot, - ggsize, - labs, - scale_fill_manual, - scale_x_continuous, - scale_y_continuous, - theme, -) -from lets_plot.export import ggsave - - -LetsPlot.setup_html() - -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" - -np.random.seed(42) - -# Data: company budget allocation (3-level hierarchy) -hierarchy_data = { - "root": {"id": "root", "parent": None, "label": "Company", "value": 0, "level": 0}, - "engineering": {"id": "engineering", "parent": "root", "label": "Engineering", "value": 0, "level": 1}, - "sales": {"id": "sales", "parent": "root", "label": "Sales", "value": 0, "level": 1}, - "marketing": {"id": "marketing", "parent": "root", "label": "Marketing", "value": 0, "level": 1}, - "operations": {"id": "operations", "parent": "root", "label": "Operations", "value": 0, "level": 1}, - "eng_backend": {"id": "eng_backend", "parent": "engineering", "label": "Backend", "value": 180, "level": 2}, - "eng_frontend": {"id": "eng_frontend", "parent": "engineering", "label": "Frontend", "value": 150, "level": 2}, - "eng_devops": {"id": "eng_devops", "parent": "engineering", "label": "DevOps", "value": 120, "level": 2}, - "sales_north": {"id": "sales_north", "parent": "sales", "label": "North", "value": 160, "level": 2}, - "sales_south": {"id": "sales_south", "parent": "sales", "label": "South", "value": 120, "level": 2}, - "sales_east": {"id": "sales_east", "parent": "sales", "label": "East", "value": 100, "level": 2}, - "mkt_digital": {"id": "mkt_digital", "parent": "marketing", "label": "Digital", "value": 130, "level": 2}, - "mkt_brand": {"id": "mkt_brand", "parent": "marketing", "label": "Brand", "value": 90, "level": 2}, - "ops_facilities": {"id": "ops_facilities", "parent": "operations", "label": "Facilities", "value": 85, "level": 2}, - "ops_logistics": {"id": "ops_logistics", "parent": "operations", "label": "Logistics", "value": 65, "level": 2}, -} - -for node in hierarchy_data.values(): - if node["level"] == 1: - node["value"] = sum(n["value"] for n in hierarchy_data.values() if n["parent"] == node["id"]) - -total_value = sum(n["value"] for n in hierarchy_data.values() if n["level"] == 1) - -# Okabe-Ito positions 1-4 for departments; lighter variants for children -DEPT_COLORS = {"Engineering": "#009E73", "Sales": "#C475FD", "Marketing": "#4467A3", "Operations": "#BD8233"} -CHILD_COLORS = { - "Backend": "#4DC4A0", - "Frontend": "#80D5B8", - "DevOps": "#B3E6D0", - "North": "#E08040", - "South": "#E89A66", - "East": "#F0B48C", - "Digital": "#3399CC", - "Brand": "#66B2D9", - "Facilities": "#DDA0BE", - "Logistics": "#E8C0D3", -} - -departments = sorted([n for n in hierarchy_data.values() if n["level"] == 1], key=lambda x: x["value"], reverse=True) -leaves = [n for n in hierarchy_data.values() if n["level"] == 2] - -# ===== TREEMAP: proportional-width columns per dept, children stacked vertically ===== -treemap_rects = [] -treemap_labels = [] -pad = 1.5 -cumx = 0.0 - -for dept in departments: - dw = dept["value"] / total_value * 100 - treemap_rects.append({"xmin": cumx, "ymin": 0, "xmax": cumx + dw, "ymax": 100, "label": dept["label"], "level": 1}) - dept_children = sorted([n for n in leaves if n["parent"] == dept["id"]], key=lambda x: x["value"], reverse=True) - child_total = sum(c["value"] for c in dept_children) - cumy = 0.0 - for child in dept_children: - ch = child["value"] / child_total * 90 - treemap_rects.append( - { - "xmin": cumx + pad, - "ymin": cumy + pad, - "xmax": cumx + dw - pad, - "ymax": cumy + ch - pad, - "label": child["label"], - "level": 2, - } - ) - pct = child["value"] / total_value * 100 - if dw - 2 * pad > 5 and ch > 5: - treemap_labels.append( - {"x": cumx + dw / 2, "y": cumy + ch / 2, "label": f"{child['label']}\n{pct:.0f}%", "level": 2} - ) - cumy += ch - treemap_labels.append({"x": cumx + dw / 2, "y": 95, "label": dept["label"], "level": 1}) - cumx += dw - -treemap_df = pd.DataFrame(treemap_rects) -treemap_lbl_df = pd.DataFrame(treemap_labels) -tm_dept = treemap_df[treemap_df["level"] == 1].reset_index(drop=True) -tm_child = treemap_df[treemap_df["level"] == 2].reset_index(drop=True) -lbl_dept = treemap_lbl_df[treemap_lbl_df["level"] == 1].reset_index(drop=True) -lbl_child = treemap_lbl_df[treemap_lbl_df["level"] == 2].reset_index(drop=True) - -dept_fill = [DEPT_COLORS.get(lbl, "#888") for lbl in tm_dept["label"]] -child_fill = [CHILD_COLORS.get(lbl, "#888") for lbl in tm_child["label"]] - -treemap_plot = ( - ggplot() - + geom_rect( - aes(xmin="xmin", ymin="ymin", xmax="xmax", ymax="ymax", fill="label"), - data=tm_dept, - color="white", - size=3, - alpha=0.35, - ) - + geom_rect( - aes(xmin="xmin", ymin="ymin", xmax="xmax", ymax="ymax", fill="label"), - data=tm_child, - color="white", - size=1.5, - alpha=0.92, - ) - + geom_text(aes(x="x", y="y", label="label"), data=lbl_dept, size=16, color=INK, fontface="bold") - + geom_text(aes(x="x", y="y", label="label"), data=lbl_child, size=11, color="white", fontface="bold") - + scale_fill_manual(values=dept_fill + child_fill) - + labs(title="Treemap View") - + theme( - plot_title=element_text(size=22, hjust=0.5, face="bold", color=INK), - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - panel_background=element_rect(fill=PAGE_BG), - legend_position="none", - axis_title=element_blank(), - axis_text=element_blank(), - axis_ticks=element_blank(), - axis_line=element_blank(), - panel_grid=element_blank(), - ) - + ggsize(750, 750) -) - -# ===== SUNBURST: concentric rings ===== -sunburst_polys = [] -sunburst_lbls = [] -seg_id = 0 -n_pts = 40 -r0, r1, r2 = 15, 45, 80 # hole, inner ring, outer ring - -# Inner ring: departments -start_a = math.pi / 2 -dept_angles = {} - -for dept in departments: - end_a = start_a - dept["value"] / total_value * 2 * math.pi - dept_angles[dept["id"]] = (start_a, end_a) - angles = [end_a + (start_a - end_a) * i / n_pts for i in range(n_pts + 1)] - rev_a = list(reversed(angles)) - xs = [r1 * math.cos(a) for a in angles] + [r0 * math.cos(a) for a in rev_a] - ys = [r1 * math.sin(a) for a in angles] + [r0 * math.sin(a) for a in rev_a] - for x, y in zip(xs, ys, strict=False): - sunburst_polys.append({"x": x, "y": y, "seg": seg_id, "label": dept["label"], "ring": 1}) - mid_a = (start_a + end_a) / 2 - lr = (r0 + r1) / 2 - sunburst_lbls.append({"x": lr * math.cos(mid_a), "y": lr * math.sin(mid_a), "label": dept["label"], "ring": 1}) - seg_id += 1 - start_a = end_a - -# Outer ring: children -for dept in departments: - d_start, d_end = dept_angles[dept["id"]] - d_span = d_start - d_end - dept_children = sorted([n for n in leaves if n["parent"] == dept["id"]], key=lambda x: x["value"], reverse=True) - child_total = sum(c["value"] for c in dept_children) - c_start = d_start - for child in dept_children: - c_span = (child["value"] / child_total if child_total > 0 else 0) * d_span - c_end = c_start - c_span - angles = [c_end + (c_start - c_end) * i / n_pts for i in range(n_pts + 1)] - rev_a = list(reversed(angles)) - xs = [r2 * math.cos(a) for a in angles] + [r1 * math.cos(a) for a in rev_a] - ys = [r2 * math.sin(a) for a in angles] + [r1 * math.sin(a) for a in rev_a] - for x, y in zip(xs, ys, strict=False): - sunburst_polys.append({"x": x, "y": y, "seg": seg_id, "label": child["label"], "ring": 2}) - if child["value"] / total_value > 0.05: - mid_a = (c_start + c_end) / 2 - lr = (r1 + r2) / 2 - sunburst_lbls.append( - {"x": lr * math.cos(mid_a), "y": lr * math.sin(mid_a), "label": child["label"], "ring": 2} - ) - seg_id += 1 - c_start = c_end - -sb_df = pd.DataFrame(sunburst_polys) -sb_lbl_df = pd.DataFrame(sunburst_lbls) -sb_r1 = sb_df[sb_df["ring"] == 1].reset_index(drop=True) -sb_r2 = sb_df[sb_df["ring"] == 2].reset_index(drop=True) -sb_l1 = sb_lbl_df[sb_lbl_df["ring"] == 1].reset_index(drop=True) -sb_l2 = sb_lbl_df[sb_lbl_df["ring"] == 2].reset_index(drop=True) - -r1_fill = [DEPT_COLORS.get(lbl, "#888") for lbl in sb_r1["label"].unique()] -r2_fill = [CHILD_COLORS.get(lbl, "#888") for lbl in sb_r2["label"].unique()] - -sunburst_plot = ( - ggplot() - + geom_polygon(aes(x="x", y="y", fill="label", group="seg"), data=sb_r1, color="white", size=2, alpha=0.95) - + geom_polygon(aes(x="x", y="y", fill="label", group="seg"), data=sb_r2, color="white", size=1.2, alpha=0.9) - + geom_text(aes(x="x", y="y", label="label"), data=sb_l1, size=11, color="white", fontface="bold") - + geom_text(aes(x="x", y="y", label="label"), data=sb_l2, size=10, color=INK, fontface="bold") - + scale_fill_manual(values=r1_fill + r2_fill) - + coord_fixed(ratio=1) - + scale_x_continuous(limits=(-100, 100)) - + scale_y_continuous(limits=(-100, 100)) - + labs(title="Sunburst View") - + ggsize(750, 750) - + theme( - plot_title=element_text(size=22, hjust=0.5, face="bold", color=INK), - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - panel_background=element_rect(fill=PAGE_BG), - legend_position="none", - axis_title=element_blank(), - axis_text=element_blank(), - axis_ticks=element_blank(), - axis_line=element_blank(), - panel_grid=element_blank(), - ) -) - -# Combine side-by-side -combined = gggrid([treemap_plot, sunburst_plot], ncol=2) -final_plot = ( - combined - + ggsize(1600, 900) - + labs( - title="hierarchy-toggle-view · python · letsplot · anyplot.ai", - subtitle="Company Budget Allocation · Toggle between views in the HTML version", - ) - + theme( - plot_title=element_text(size=24, hjust=0.5, face="bold", color=INK), - plot_subtitle=element_text(size=16, hjust=0.5, color=INK_SOFT), - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - ) -) - -ggsave(final_plot, f"plot-{THEME}.png", path=".", scale=3) - -# Interactive HTML with theme-aware toggle -html_content = f""" - - - - hierarchy-toggle-view · python · letsplot · anyplot.ai - - - -
-

hierarchy-toggle-view · python · letsplot · anyplot.ai

-

Company Budget Allocation

-
- - -
-
- -
-
-
- - -""" - -with open(f"plot-{THEME}.html", "w") as f: - f.write(html_content) diff --git a/plots/hierarchy-toggle-view/implementations/python/matplotlib.py b/plots/hierarchy-toggle-view/implementations/python/matplotlib.py deleted file mode 100644 index ff764c86db..0000000000 --- a/plots/hierarchy-toggle-view/implementations/python/matplotlib.py +++ /dev/null @@ -1,162 +0,0 @@ -""" anyplot.ai -hierarchy-toggle-view: Interactive Treemap-Sunburst Toggle View -Library: matplotlib 3.10.9 | Python 3.13.13 -Quality: 81/100 | Created: 2026-05-19 -""" - -import os - -import matplotlib.patches as mpatches -import matplotlib.pyplot as plt -import numpy as np - - -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" - -COLORS = ["#009E73", "#C475FD", "#4467A3", "#BD8233"] - -# Data: Corporate Annual Budget ($M) -budget = { - "Engineering": {"Software Dev": 250, "Hardware": 100, "R&D": 50}, - "Marketing": {"Digital Ads": 150, "Events": 70, "PR": 30}, - "Operations": {"HR": 80, "Finance": 70, "Admin": 50}, - "Sales": {"Direct": 100, "Channel": 50}, -} -cat_names = list(budget.keys()) -cat_totals = {cat: sum(vals.values()) for cat, vals in budget.items()} -total = sum(cat_totals.values()) # $1000M - -fig, (ax_tree, ax_sun) = plt.subplots(1, 2, figsize=(16, 9), facecolor=PAGE_BG) -fig.subplots_adjust(left=0.01, right=0.99, top=0.88, bottom=0.11, wspace=0.06) -ax_tree.set_facecolor(PAGE_BG) -ax_sun.set_facecolor(PAGE_BG) - -# ── Treemap ─────────────────────────────────────────────────────────────────── -ax_tree.set_xlim(0, 1) -ax_tree.set_ylim(0, 1) -ax_tree.axis("off") -ax_tree.set_title("Treemap", fontsize=22, color=INK, fontweight="medium", pad=12) - -y_pos = 0.0 -for i, (cat, items) in enumerate(budget.items()): - cat_h = cat_totals[cat] / total - n_items = len(items) - x_pos = 0.0 - for j, (item, val) in enumerate(items.items()): - item_w = val / cat_totals[cat] - cell_alpha = 0.55 + 0.45 * ((n_items - 1 - j) / max(n_items - 1, 1)) - rect = mpatches.Rectangle( - (x_pos + 0.004, y_pos + 0.004), - item_w - 0.008, - cat_h - 0.008, - facecolor=COLORS[i], - edgecolor=PAGE_BG, - linewidth=3, - alpha=cell_alpha, - zorder=2, - ) - ax_tree.add_patch(rect) - if item_w > 0.10 and cat_h > 0.07: - ax_tree.text( - x_pos + item_w / 2, - y_pos + cat_h / 2, - f"{item}\n${val}M", - ha="center", - va="center", - fontsize=11, - color="white", - fontweight="bold", - zorder=4, - linespacing=1.4, - ) - x_pos += item_w - # Category label at top-left of band - ax_tree.text( - 0.010, y_pos + cat_h - 0.012, cat, ha="left", va="top", fontsize=13, color="white", fontweight="bold", zorder=5 - ) - y_pos += cat_h - -# ── Sunburst ────────────────────────────────────────────────────────────────── -ax_sun.set_xlim(-1.15, 1.15) -ax_sun.set_ylim(-1.15, 1.15) -ax_sun.set_aspect("equal") -ax_sun.axis("off") -ax_sun.set_title("Sunburst", fontsize=22, color=INK, fontweight="medium", pad=12) - -r_inner = 0.28 -r_outer = 0.70 -ring_gap = 0.05 - -start_deg = 90.0 -for i, (cat, items) in enumerate(budget.items()): - cat_sweep = (cat_totals[cat] / total) * 360 - end_deg = start_deg - cat_sweep - - # Inner ring: category - wedge_in = mpatches.Wedge( - (0, 0), r_inner, end_deg, start_deg, facecolor=COLORS[i], edgecolor=PAGE_BG, linewidth=2, zorder=2 - ) - ax_sun.add_patch(wedge_in) - - # Outer ring: items - item_start = start_deg - n_items = len(items) - for j, (item, val) in enumerate(items.items()): - item_sweep = (val / cat_totals[cat]) * cat_sweep - item_end = item_start - item_sweep - cell_alpha = 0.50 + 0.50 * ((n_items - 1 - j) / max(n_items - 1, 1)) - wedge_out = mpatches.Wedge( - (0, 0), - r_outer, - item_end, - item_start, - width=r_outer - r_inner - ring_gap, - facecolor=COLORS[i], - edgecolor=PAGE_BG, - linewidth=2, - alpha=cell_alpha, - zorder=2, - ) - ax_sun.add_patch(wedge_out) - - if item_sweep >= 20: - mid_deg = (item_start + item_end) / 2 - mid_rad = np.radians(mid_deg) - r_mid = r_inner + ring_gap + (r_outer - r_inner - ring_gap) / 2 - ax_sun.text( - np.cos(mid_rad) * r_mid, - np.sin(mid_rad) * r_mid, - f"{item}\n${val}M", - ha="center", - va="center", - fontsize=9, - color="white", - fontweight="bold", - zorder=5, - linespacing=1.3, - ) - item_start = item_end - start_deg = end_deg - -# Center label -ax_sun.text(0, 0.05, f"${total}M", ha="center", va="center", fontsize=18, color=INK, fontweight="bold", zorder=6) -ax_sun.text(0, -0.08, "Total", ha="center", va="center", fontsize=13, color=INK_SOFT, zorder=6) - -# Legend -legend_patches = [mpatches.Patch(facecolor=COLORS[i], label=cat_names[i]) for i in range(len(cat_names))] -leg = fig.legend( - handles=legend_patches, loc="lower center", ncol=4, fontsize=15, frameon=True, bbox_to_anchor=(0.5, 0.01) -) -leg.get_frame().set_facecolor(ELEVATED_BG) -leg.get_frame().set_edgecolor(INK_SOFT) -plt.setp(leg.get_texts(), color=INK_SOFT) - -fig.suptitle( - "hierarchy-toggle-view · python · matplotlib · anyplot.ai", fontsize=20, color=INK, fontweight="medium", y=0.96 -) - -plt.savefig(f"plot-{THEME}.png", dpi=300, bbox_inches="tight", facecolor=PAGE_BG) diff --git a/plots/hierarchy-toggle-view/implementations/python/plotly.py b/plots/hierarchy-toggle-view/implementations/python/plotly.py deleted file mode 100644 index 5d63225c3c..0000000000 --- a/plots/hierarchy-toggle-view/implementations/python/plotly.py +++ /dev/null @@ -1,208 +0,0 @@ -""" anyplot.ai -hierarchy-toggle-view: Interactive Treemap-Sunburst Toggle View -Library: plotly 6.7.0 | Python 3.13.13 -Quality: 87/100 | Updated: 2026-05-19 -""" - -import os - -import plotly.graph_objects as go -from plotly.subplots import make_subplots - - -# 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" - -# Hierarchical data: Company budget allocation (in thousands) -# Deterministic data — no random seed needed -labels = ["Company"] -parents = [""] -values = [9600] - -departments = [("Engineering", 4500), ("Marketing", 2100), ("Sales", 1800), ("Operations", 1200)] - -for dept_name, dept_budget in departments: - labels.append(dept_name) - parents.append("Company") - values.append(dept_budget) - -teams_data = [ - ("Backend", "Engineering", 1800), - ("Frontend", "Engineering", 1200), - ("Data Science", "Engineering", 900), - ("DevOps", "Engineering", 600), - ("Digital", "Marketing", 900), - ("Brand", "Marketing", 600), - ("Content", "Marketing", 600), - ("Enterprise", "Sales", 1000), - ("SMB", "Sales", 500), - ("Partners", "Sales", 300), - ("IT", "Operations", 500), - ("HR", "Operations", 400), - ("Finance", "Operations", 300), -] - -for team_name, parent_dept, team_budget in teams_data: - labels.append(team_name) - parents.append(parent_dept) - values.append(team_budget) - -# Okabe-Ito palette for departments (positions 1–4) -dept_colors = {"Engineering": "#009E73", "Marketing": "#C475FD", "Sales": "#4467A3", "Operations": "#BD8233"} - -colors = [] -for i, label in enumerate(labels): - if label == "Company": - colors.append(ELEVATED_BG) - elif label in dept_colors: - colors.append(dept_colors[label]) - else: - colors.append(dept_colors.get(parents[i], "#009E73")) - -# Side-by-side PNG: both views visible simultaneously -fig = make_subplots(rows=1, cols=2, specs=[[{"type": "treemap"}, {"type": "sunburst"}]], horizontal_spacing=0.03) - -fig.add_trace( - go.Treemap( - labels=labels, - parents=parents, - values=values, - branchvalues="total", - marker={"colors": colors, "line": {"width": 2, "color": PAGE_BG}}, - textfont={"size": 20}, - textinfo="label+value", - texttemplate="%{label}
$%{value}K", - hovertemplate="%{label}
Budget: $%{value}K
Percent: %{percentParent:.1%}", - ), - row=1, - col=1, -) - -fig.add_trace( - go.Sunburst( - labels=labels, - parents=parents, - values=values, - branchvalues="total", - marker={"colors": colors, "line": {"width": 2, "color": PAGE_BG}}, - textfont={"size": 16}, - textinfo="label", - hovertemplate="%{label}
Budget: $%{value}K
Percent: %{percentParent:.1%}", - insidetextorientation="radial", - ), - row=1, - col=2, -) - -fig.update_layout( - title={ - "text": "hierarchy-toggle-view · python · plotly · anyplot.ai", - "font": {"size": 28, "color": INK}, - "x": 0.5, - "xanchor": "center", - }, - paper_bgcolor=PAGE_BG, - plot_bgcolor=PAGE_BG, - font={"color": INK}, - margin={"t": 130, "l": 20, "r": 20, "b": 90}, - annotations=[ - { - "text": "Treemap View", - "x": 0.22, - "y": 1.05, - "xref": "paper", - "yref": "paper", - "showarrow": False, - "font": {"size": 22, "color": INK_SOFT}, - }, - { - "text": "Sunburst View", - "x": 0.78, - "y": 1.05, - "xref": "paper", - "yref": "paper", - "showarrow": False, - "font": {"size": 22, "color": INK_SOFT}, - }, - { - "text": "Two perspectives: rectangles for size comparison, radial for hierarchy depth", - "x": 0.5, - "y": -0.05, - "xref": "paper", - "yref": "paper", - "showarrow": False, - "font": {"size": 18, "color": INK_SOFT}, - }, - ], -) - -fig.write_image(f"plot-{THEME}.png", width=1600, height=900, scale=3) - -# Interactive HTML with toggle buttons -fig_html = go.Figure() - -fig_html.add_trace( - go.Treemap( - labels=labels, - parents=parents, - values=values, - branchvalues="total", - marker={"colors": colors, "line": {"width": 2, "color": PAGE_BG}}, - textfont={"size": 22}, - textinfo="label+value", - texttemplate="%{label}
$%{value}K", - hovertemplate="%{label}
Budget: $%{value}K
Percent: %{percentParent:.1%}", - visible=True, - ) -) - -fig_html.add_trace( - go.Sunburst( - labels=labels, - parents=parents, - values=values, - branchvalues="total", - marker={"colors": colors, "line": {"width": 2, "color": PAGE_BG}}, - textfont={"size": 18}, - textinfo="label", - hovertemplate="%{label}
Budget: $%{value}K
Percent: %{percentParent:.1%}", - visible=False, - insidetextorientation="radial", - ) -) - -fig_html.update_layout( - title={ - "text": "hierarchy-toggle-view · python · plotly · anyplot.ai", - "font": {"size": 28, "color": INK}, - "x": 0.5, - "xanchor": "center", - }, - paper_bgcolor=PAGE_BG, - plot_bgcolor=PAGE_BG, - font={"color": INK}, - margin={"t": 150, "l": 30, "r": 30, "b": 50}, - updatemenus=[ - { - "type": "buttons", - "direction": "right", - "x": 0.5, - "xanchor": "center", - "y": 1.12, - "buttons": [ - {"label": " Treemap ", "method": "update", "args": [{"visible": [True, False]}]}, - {"label": " Sunburst ", "method": "update", "args": [{"visible": [False, True]}]}, - ], - "font": {"size": 18, "color": INK}, - "bgcolor": ELEVATED_BG, - "bordercolor": "#009E73", - "borderwidth": 2, - } - ], -) - -fig_html.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn") diff --git a/plots/hierarchy-toggle-view/implementations/python/plotnine.py b/plots/hierarchy-toggle-view/implementations/python/plotnine.py deleted file mode 100644 index fcd1533fa0..0000000000 --- a/plots/hierarchy-toggle-view/implementations/python/plotnine.py +++ /dev/null @@ -1,279 +0,0 @@ -""" anyplot.ai -hierarchy-toggle-view: Interactive Treemap-Sunburst Toggle View -Library: plotnine 0.15.4 | Python 3.13.13 -Quality: 70/100 | Updated: 2026-05-19 -""" - -import numpy as np -import pandas as pd -from plotnine import ( - aes, - annotate, - geom_polygon, - geom_rect, - geom_text, - ggplot, - lims, - scale_fill_identity, - theme, - theme_void, -) - - -# Hierarchical data: Research department budget allocation -hierarchy_data = [ - {"id": "root", "parent": "", "label": "Research", "value": 0}, - # Level 1: Main divisions - {"id": "bio", "parent": "root", "label": "Biology", "value": 0}, - {"id": "chem", "parent": "root", "label": "Chemistry", "value": 0}, - {"id": "phys", "parent": "root", "label": "Physics", "value": 0}, - {"id": "comp", "parent": "root", "label": "Computing", "value": 0}, - # Level 2: Biology subdepartments - {"id": "genetics", "parent": "bio", "label": "Genetics", "value": 150}, - {"id": "ecology", "parent": "bio", "label": "Ecology", "value": 120}, - {"id": "micro", "parent": "bio", "label": "Microbio", "value": 90}, - # Level 2: Chemistry subdepartments - {"id": "organic", "parent": "chem", "label": "Organic", "value": 130}, - {"id": "inorganic", "parent": "chem", "label": "Inorganic", "value": 80}, - {"id": "analytic", "parent": "chem", "label": "Analytic", "value": 70}, - # Level 2: Physics subdepartments - {"id": "quantum", "parent": "phys", "label": "Quantum", "value": 180}, - {"id": "astro", "parent": "phys", "label": "Astro", "value": 140}, - {"id": "particle", "parent": "phys", "label": "Particle", "value": 100}, - # Level 2: Computing subdepartments - {"id": "ml", "parent": "comp", "label": "ML/AI", "value": 200}, - {"id": "hpc", "parent": "comp", "label": "HPC", "value": 110}, -] - -df = pd.DataFrame(hierarchy_data) - -# Build hierarchy and calculate parent values -children = {} -for _, row in df.iterrows(): - parent = row["parent"] - if parent: - if parent not in children: - children[parent] = [] - children[parent].append(row["id"]) - -# Calculate parent values as sum of children -level1_ids = ["bio", "chem", "phys", "comp"] -for nid in level1_ids: - child_sum = df[df["parent"] == nid]["value"].sum() - df.loc[df["id"] == nid, "value"] = child_sum - -total_value = df[df["id"].isin(level1_ids)]["value"].sum() -df.loc[df["id"] == "root", "value"] = total_value - -# Color mapping (colorblind-safe) -colors = { - "bio": "#306998", # Python Blue - "chem": "#FFD43B", # Python Yellow - "phys": "#2E8B57", # Sea Green - "comp": "#E07B39", # Burnt Orange -} - -# Assign colors to children -for nid in level1_ids: - if nid in children: - for cid in children[nid]: - colors[cid] = colors[nid] - - -# ============ TREEMAP DATA (flat structure) ============ -treemap_rects = [] -x_start = 0 -treemap_total = df[df["id"].isin(level1_ids)]["value"].sum() - -for nid in level1_ids: - parent_val = df[df["id"] == nid]["value"].iloc[0] - parent_width = (parent_val / treemap_total) * 100 - - child_ids = children.get(nid, []) - child_vals = [df[df["id"] == cid]["value"].iloc[0] for cid in child_ids] - child_total = sum(child_vals) - - y_start = 0 - for cid, cval in zip(child_ids, child_vals, strict=True): - child_height = (cval / child_total) * 100 - label = df[df["id"] == cid]["label"].iloc[0] - text_col = "#333333" if colors[cid] == "#FFD43B" else "white" - treemap_rects.append( - { - "xmin": x_start + 0.5, - "xmax": x_start + parent_width - 0.5, - "ymin": y_start + 0.5, - "ymax": y_start + child_height - 0.5, - "fill": colors[cid], - "label": f"{label}\n${cval}K", - "label_x": x_start + parent_width / 2, - "label_y": y_start + child_height / 2, - "text_color": text_col, - } - ) - y_start += child_height - x_start += parent_width - -treemap_df = pd.DataFrame(treemap_rects) - - -# ============ SUNBURST DATA (flat structure) ============ -sunburst_wedges = [] -sunburst_labels = [] -wedge_id = 0 -sunburst_total = df[df["id"].isin(level1_ids)]["value"].sum() -start_angle = np.pi / 2 # Start at top -cx, cy = 50, 50 # Center position -n_points = 50 - -for nid in level1_ids: - parent_val = df[df["id"] == nid]["value"].iloc[0] - parent_sweep = (parent_val / sunburst_total) * 2 * np.pi - - # Inner ring wedge (level 1) - inner_r, outer_r = 12, 28 - angles_outer = np.linspace(start_angle, start_angle + parent_sweep, n_points) - angles_inner = np.linspace(start_angle + parent_sweep, start_angle, n_points) - x_outer = cx + outer_r * np.cos(angles_outer) - y_outer = cy + outer_r * np.sin(angles_outer) - x_inner = cx + inner_r * np.cos(angles_inner) - y_inner = cy + inner_r * np.sin(angles_inner) - x = np.concatenate([x_outer, x_inner]) - y = np.concatenate([y_outer, y_inner]) - - for xi, yi in zip(x, y, strict=True): - sunburst_wedges.append({"x": xi, "y": yi, "wedge_id": wedge_id, "fill": colors[nid]}) - wedge_id += 1 - - # Inner ring label with budget - mid_angle = start_angle + parent_sweep / 2 - text_col = "#333333" if colors[nid] == "#FFD43B" else "white" - label_r = 20 - lx = cx + label_r * np.cos(mid_angle) - ly = cy + label_r * np.sin(mid_angle) - parent_label = df[df["id"] == nid]["label"].iloc[0] - sunburst_labels.append({"x": lx, "y": ly, "label": f"{parent_label}\n${int(parent_val)}K", "text_color": text_col}) - - # Outer ring wedges (level 2 - children) - child_ids = children.get(nid, []) - child_vals = [df[df["id"] == cid]["value"].iloc[0] for cid in child_ids] - child_total = sum(child_vals) - child_start = start_angle - - for cid, cval in zip(child_ids, child_vals, strict=True): - child_sweep = (cval / child_total) * parent_sweep - - inner_r, outer_r = 30, 45 - angles_outer = np.linspace(child_start, child_start + child_sweep, n_points) - angles_inner = np.linspace(child_start + child_sweep, child_start, n_points) - x_outer = cx + outer_r * np.cos(angles_outer) - y_outer = cy + outer_r * np.sin(angles_outer) - x_inner = cx + inner_r * np.cos(angles_inner) - y_inner = cy + inner_r * np.sin(angles_inner) - x = np.concatenate([x_outer, x_inner]) - y = np.concatenate([y_outer, y_inner]) - - for xi, yi in zip(x, y, strict=True): - sunburst_wedges.append({"x": xi, "y": yi, "wedge_id": wedge_id, "fill": colors[cid]}) - wedge_id += 1 - - # Outer ring label with budget (only for large enough segments) - if child_sweep > 0.2: - child_mid = child_start + child_sweep / 2 - text_col = "#333333" if colors[cid] == "#FFD43B" else "white" - label_r = 37.5 - lx = cx + label_r * np.cos(child_mid) - ly = cy + label_r * np.sin(child_mid) - child_label = df[df["id"] == cid]["label"].iloc[0] - sunburst_labels.append({"x": lx, "y": ly, "label": f"{child_label}\n${cval}K", "text_color": text_col}) - child_start += child_sweep - - start_angle += parent_sweep - -sunburst_df = pd.DataFrame(sunburst_wedges) -sunburst_labels_df = pd.DataFrame(sunburst_labels) - -# Split labels by color for proper text rendering -treemap_white_labels = treemap_df[treemap_df["text_color"] == "white"] -treemap_dark_labels = treemap_df[treemap_df["text_color"] == "#333333"] - -sunburst_white_labels = sunburst_labels_df[sunburst_labels_df["text_color"] == "white"] -sunburst_dark_labels = sunburst_labels_df[sunburst_labels_df["text_color"] == "#333333"] - -# Create the combined plot with both views -plot = ( - ggplot() - # Treemap rectangles - + geom_rect( - data=treemap_df, - mapping=aes(xmin="xmin", xmax="xmax", ymin="ymin", ymax="ymax", fill="fill"), - color="white", - size=1.2, - ) - # Treemap labels - white text - + geom_text( - data=treemap_white_labels, - mapping=aes(x="label_x", y="label_y", label="label"), - color="white", - size=11, - fontweight="bold", - ) - # Treemap labels - dark text (for yellow backgrounds) - + geom_text( - data=treemap_dark_labels, - mapping=aes(x="label_x", y="label_y", label="label"), - color="#333333", - size=11, - fontweight="bold", - ) - # Sunburst polygons (offset to right) - + geom_polygon( - data=sunburst_df.assign(x=lambda d: d["x"] + 110), - mapping=aes(x="x", y="y", fill="fill", group="wedge_id"), - color="white", - size=0.7, - ) - # Sunburst labels - white text (offset to right) - + geom_text( - data=sunburst_white_labels.assign(x=lambda d: d["x"] + 110), - mapping=aes(x="x", y="y", label="label"), - color="white", - size=10, - fontweight="bold", - ) - # Sunburst labels - dark text (offset to right) - + geom_text( - data=sunburst_dark_labels.assign(x=lambda d: d["x"] + 110), - mapping=aes(x="x", y="y", label="label"), - color="#333333", - size=10, - fontweight="bold", - ) - # Use identity scale to respect literal color values - + scale_fill_identity() - # Sunburst center circle - + annotate("point", x=160, y=50, size=40, color="white") - + annotate("text", x=160, y=53, label="Total", size=12, fontweight="bold", color="#306998") - + annotate("text", x=160, y=47, label=f"${int(total_value)}K", size=11, color="#555555") - # View titles - + annotate("text", x=50, y=108, label="Treemap View", size=16, fontweight="bold", color="#333333") - + annotate("text", x=160, y=108, label="Sunburst View", size=16, fontweight="bold", color="#333333") - # Main title - + annotate( - "text", - x=105, - y=120, - label="hierarchy-toggle-view · plotnine · pyplots.ai", - size=18, - fontweight="bold", - color="#306998", - ) - # Subtitle - + annotate("text", x=105, y=114, label="Research Department Budget Allocation", size=13, color="#666666") - + lims(x=(-5, 215), y=(-10, 130)) - + theme_void() - + theme(figure_size=(16, 9), legend_position="none") -) - -# Save the plot -plot.save("plot.png", dpi=300) diff --git a/plots/hierarchy-toggle-view/implementations/python/pygal.py b/plots/hierarchy-toggle-view/implementations/python/pygal.py deleted file mode 100644 index 93eb51626f..0000000000 --- a/plots/hierarchy-toggle-view/implementations/python/pygal.py +++ /dev/null @@ -1,306 +0,0 @@ -""" anyplot.ai -hierarchy-toggle-view: Interactive Treemap-Sunburst Toggle View -Library: pygal 3.1.0 | Python 3.13.13 -Quality: 81/100 | Updated: 2026-05-19 -""" - -import os -import sys -from pathlib import Path - -from PIL import Image, ImageDraw, ImageFont - - -# Ensure we import the pygal package, not this file (filename collision) -_this_dir = Path(__file__).parent -if str(_this_dir) in sys.path: - sys.path.remove(str(_this_dir)) - -import pygal -from pygal.style import Style - - -# Theme tokens -THEME = os.getenv("ANYPLOT_THEME", "light") -PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" -INK = "#1A1A17" if THEME == "light" else "#F0EFE8" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" -PIL_BG = (250, 248, 241) if THEME == "light" else (26, 26, 23) - -# Okabe-Ito palette — position 1 (#009E73) is always first series -IMPRINT = ("#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477") - -# Data: file-system storage allocation (GB) — 4 top-level folders, 3 sub-folders each -hierarchy = [ - {"id": "root", "parent": "", "label": "Storage (240 GB)", "value": 0}, - # Top-level categories - {"id": "media", "parent": "root", "label": "Media", "value": 0}, - {"id": "documents", "parent": "root", "label": "Documents", "value": 0}, - {"id": "apps", "parent": "root", "label": "Applications", "value": 0}, - {"id": "system", "parent": "root", "label": "System", "value": 0}, - # Media sub-folders - {"id": "videos", "parent": "media", "label": "Videos", "value": 52}, - {"id": "photos", "parent": "media", "label": "Photos", "value": 24}, - {"id": "music", "parent": "media", "label": "Music", "value": 12}, - # Documents sub-folders - {"id": "work", "parent": "documents", "label": "Work Docs", "value": 22}, - {"id": "personal", "parent": "documents", "label": "Personal", "value": 16}, - {"id": "archived", "parent": "documents", "label": "Archived", "value": 14}, - # Applications sub-folders - {"id": "productivity", "parent": "apps", "label": "Productivity", "value": 28}, - {"id": "games", "parent": "apps", "label": "Games", "value": 22}, - {"id": "devtools", "parent": "apps", "label": "Dev Tools", "value": 18}, - # System sub-folders - {"id": "os", "parent": "system", "label": "OS", "value": 16}, - {"id": "cache", "parent": "system", "label": "Cache", "value": 10}, - {"id": "logs", "parent": "system", "label": "Logs", "value": 6}, -] - -# Calculate parent sums bottom-up -for node in reversed(hierarchy): - if node["value"] == 0: - node["value"] = sum(n["value"] for n in hierarchy if n["parent"] == node["id"]) - -# Sub-folder colors: tints of each Okabe-Ito category (data colors are theme-invariant) -sub_colors = { - "videos": "#009E73", - "photos": "#4DBB9D", - "music": "#99D7C7", - "work": "#C475FD", - "personal": "#E28B4D", - "archived": "#EEB899", - "productivity": "#4467A3", - "games": "#4D9EC9", - "devtools": "#99C9E0", - "os": "#BD8233", - "cache": "#DAA0C0", - "logs": "#E8C7D8", -} - -categories = [n for n in hierarchy if n["parent"] == "root"] - -# Pygal style for HTML charts (interactive, 1600×900) -html_style = Style( - background=PAGE_BG, - plot_background=PAGE_BG, - foreground=INK, - foreground_strong=INK, - foreground_subtle=INK_MUTED, - colors=IMPRINT, - title_font_size=28, - label_font_size=18, - major_label_font_size=16, - legend_font_size=16, - value_font_size=14, - stroke_width=3, -) - -# Pygal style for PNG charts (large canvas, ~2300×2400 each half) -png_style = Style( - background=PAGE_BG, - plot_background=PAGE_BG, - foreground=INK, - foreground_strong=INK, - foreground_subtle=INK_MUTED, - colors=IMPRINT, - title_font_size=64, - label_font_size=36, - major_label_font_size=32, - legend_font_size=32, - value_font_size=28, - value_label_font_size=28, - stroke_width=4, -) - -# --- HTML: interactive toggle view --- - -html_treemap = pygal.Treemap( - width=1600, - height=900, - style=html_style, - title="Treemap — Disk Storage by Folder", - show_legend=True, - legend_at_bottom=True, - legend_at_bottom_columns=4, - print_values=True, - print_values_position="center", - value_formatter=lambda x: f"{x} GB", -) -for cat in categories: - items = [n for n in hierarchy if n["parent"] == cat["id"]] - html_treemap.add( - f"{cat['label']} ({cat['value']} GB)", - [{"value": t["value"], "label": t["label"], "color": sub_colors[t["id"]]} for t in items], - ) - -html_sunburst = pygal.Pie( - width=1600, - height=900, - style=html_style, - title="Sunburst — Disk Storage Hierarchy", - inner_radius=0.35, - show_legend=True, - legend_at_bottom=True, - legend_at_bottom_columns=4, - print_values=True, - value_formatter=lambda x: f"{x} GB", -) -for cat in categories: - items = [n for n in hierarchy if n["parent"] == cat["id"]] - for item in items: - html_sunburst.add( - f"{cat['label']}: {item['label']}", [{"value": item["value"], "color": sub_colors[item["id"]]}] - ) - -treemap_svg = html_treemap.render(is_unicode=True) -sunburst_svg = html_sunburst.render(is_unicode=True) - -html_content = f""" - - - - Disk Storage · hierarchy-toggle-view · python · pygal · anyplot.ai - - - -
-

Disk Storage · hierarchy-toggle-view · python · pygal · anyplot.ai

-

240 GB total — toggle between rectangular (Treemap) and radial (Sunburst) views

-
- - -
-
{treemap_svg}
-
{sunburst_svg}
-
- - -""" - -with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f: - f.write(html_content) - -# --- PNG: side-by-side static view (4800×2700 canvas) --- - -png_treemap = pygal.Treemap( - width=2300, - height=2400, - style=png_style, - title="Treemap View", - show_legend=True, - legend_at_bottom=True, - legend_at_bottom_columns=2, - print_values=True, - print_values_position="center", - value_formatter=lambda x: f"{x} GB", - margin_left=20, - margin_right=20, -) -for cat in categories: - items = [n for n in hierarchy if n["parent"] == cat["id"]] - png_treemap.add( - cat["label"], [{"value": t["value"], "label": t["label"], "color": sub_colors[t["id"]]} for t in items] - ) - -png_sunburst = pygal.Pie( - width=2300, - height=2400, - style=png_style, - title="Sunburst View", - inner_radius=0.35, - show_legend=True, - legend_at_bottom=True, - legend_at_bottom_columns=3, - print_values=True, - value_formatter=lambda x: f"{x} GB", - margin_left=20, - margin_right=20, -) -for cat in categories: - items = [n for n in hierarchy if n["parent"] == cat["id"]] - for item in items: - png_sunburst.add(item["label"], [{"value": item["value"], "color": sub_colors[item["id"]]}]) - -png_treemap.render_to_png("treemap_tmp.png") -png_sunburst.render_to_png("sunburst_tmp.png") - -# Assemble 4800×2700 canvas -combined = Image.new("RGB", (4800, 2700), PIL_BG) -draw = ImageDraw.Draw(combined) - -try: - font_title = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 72) - font_sub = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 40) -except OSError: - font_title = ImageFont.load_default() - font_sub = ImageFont.load_default() - -title_str = "Disk Storage · hierarchy-toggle-view · python · pygal · anyplot.ai" -tw = draw.textbbox((0, 0), title_str, font=font_title) -draw.text(((4800 - (tw[2] - tw[0])) // 2, 40), title_str, fill=IMPRINT[0], font=font_title) - -sub_str = "240 GB total — Treemap excels at size comparison; Sunburst reveals hierarchy depth" -sw = draw.textbbox((0, 0), sub_str, font=font_sub) -draw.text(((4800 - (sw[2] - sw[0])) // 2, 140), sub_str, fill=INK_MUTED, font=font_sub) - -treemap_img = Image.open("treemap_tmp.png").resize((2200, 2400), Image.Resampling.LANCZOS) -combined.paste(treemap_img, (100, 250)) - -sunburst_img = Image.open("sunburst_tmp.png").resize((2200, 2400), Image.Resampling.LANCZOS) -combined.paste(sunburst_img, (2500, 250)) - -combined.save(f"plot-{THEME}.png", "PNG") - -os.remove("treemap_tmp.png") -os.remove("sunburst_tmp.png") diff --git a/plots/hierarchy-toggle-view/implementations/python/seaborn.py b/plots/hierarchy-toggle-view/implementations/python/seaborn.py deleted file mode 100644 index 7d28416b45..0000000000 --- a/plots/hierarchy-toggle-view/implementations/python/seaborn.py +++ /dev/null @@ -1,303 +0,0 @@ -""" anyplot.ai -hierarchy-toggle-view: Interactive Treemap-Sunburst Toggle View -Library: seaborn 0.13.2 | Python 3.13.13 -Quality: 75/100 | Updated: 2026-05-19 -""" - -import os - -import matplotlib.patches as mpatches -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd -import seaborn as sns -from matplotlib.patches import FancyBboxPatch, Wedge - - -# 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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -sns.set_theme( - style="ticks", - rc={ - "figure.facecolor": PAGE_BG, - "axes.facecolor": PAGE_BG, - "axes.edgecolor": INK_SOFT, - "axes.labelcolor": INK, - "text.color": INK, - "xtick.color": INK_SOFT, - "ytick.color": INK_SOFT, - "grid.color": INK, - "grid.alpha": 0.10, - "legend.facecolor": ELEVATED_BG, - "legend.edgecolor": INK_SOFT, - }, -) - -# Okabe-Ito palette — first series always #009E73 -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233"] - -# Data: software project budget allocation (3 levels, 17 nodes) -data = [ - {"id": "root", "parent": "", "label": "Budget", "value": 1350}, - {"id": "dev", "parent": "root", "label": "Development", "value": 600}, - {"id": "infra", "parent": "root", "label": "Infrastructure", "value": 310}, - {"id": "ops", "parent": "root", "label": "Operations", "value": 240}, - {"id": "research", "parent": "root", "label": "Research", "value": 200}, - {"id": "frontend", "parent": "dev", "label": "Frontend", "value": 180}, - {"id": "backend", "parent": "dev", "label": "Backend", "value": 220}, - {"id": "mobile", "parent": "dev", "label": "Mobile", "value": 120}, - {"id": "qa", "parent": "dev", "label": "QA", "value": 80}, - {"id": "cloud", "parent": "infra", "label": "Cloud", "value": 150}, - {"id": "security", "parent": "infra", "label": "Security", "value": 90}, - {"id": "database", "parent": "infra", "label": "Database", "value": 70}, - {"id": "support", "parent": "ops", "label": "Support", "value": 100}, - {"id": "monitoring", "parent": "ops", "label": "Monitoring", "value": 60}, - {"id": "devops", "parent": "ops", "label": "DevOps", "value": 80}, - {"id": "ml", "parent": "research", "label": "ML/AI", "value": 130}, - {"id": "proto", "parent": "research", "label": "Prototyping", "value": 70}, -] - -df = pd.DataFrame(data) - -# Build hierarchy lookup -children = {} -for _, row in df.iterrows(): - if row["parent"]: - children.setdefault(row["parent"], []).append(row["id"]) - -# Color map: Okabe-Ito for level-1 nodes, same color inherited by children -level1_ids = ["dev", "infra", "ops", "research"] -color_map = {nid: IMPRINT[i] for i, nid in enumerate(level1_ids)} -for nid in level1_ids: - for cid in children.get(nid, []): - color_map[cid] = color_map[nid] - -# Plot -fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 9)) -fig.patch.set_facecolor(PAGE_BG) - -# === LEFT: TREEMAP VIEW === -ax1.set_xlim(0, 100) -ax1.set_ylim(0, 100) -ax1.set_aspect("equal") -ax1.axis("off") -ax1.set_facecolor(PAGE_BG) - -l1_values = [df[df["id"] == nid]["value"].iloc[0] for nid in level1_ids] -l1_total = sum(l1_values) -margin = 2 -l1_x = margin - -for i, nid in enumerate(level1_ids): - l1_width = (100 - 2 * margin) * (l1_values[i] / l1_total) - - ax1.add_patch( - FancyBboxPatch( - (l1_x, margin), - l1_width, - 100 - 2 * margin, - boxstyle="round,pad=0.02,rounding_size=1.5", - facecolor=color_map[nid], - edgecolor=PAGE_BG, - linewidth=3, - alpha=0.35, - ) - ) - - if nid in children: - c_ids = children[nid] - c_values = [df[df["id"] == cid]["value"].iloc[0] for cid in c_ids] - c_total = sum(c_values) - c_y = margin + 3 - - for j, cid in enumerate(c_ids): - c_height = (100 - 2 * margin - 6) * (c_values[j] / c_total) - ax1.add_patch( - FancyBboxPatch( - (l1_x + 2, c_y), - l1_width - 4, - c_height - 1, - boxstyle="round,pad=0.01,rounding_size=0.8", - facecolor=color_map[cid], - edgecolor=PAGE_BG, - linewidth=2, - alpha=0.9, - ) - ) - - c_label = df[df["id"] == cid]["label"].iloc[0] - if c_height > 10 and l1_width > 12: - ax1.text( - l1_x + l1_width / 2, - c_y + c_height / 2, - f"{c_label}\n${c_values[j]}K", - ha="center", - va="center", - fontsize=13, - fontweight="bold", - color="white", - ) - c_y += c_height - - cat_label = df[df["id"] == nid]["label"].iloc[0] - if l1_width > 20: - label_text = f"{cat_label}: ${l1_values[i]}K" - elif l1_width > 10: - label_text = f"${l1_values[i]}K" - else: - label_text = None - if label_text: - ax1.text( - l1_x + l1_width / 2, - margin + 2, - label_text, - ha="center", - va="bottom", - fontsize=11, - fontweight="bold", - color="white", - bbox={"boxstyle": "round,pad=0.2", "facecolor": color_map[nid], "alpha": 0.85}, - ) - - l1_x += l1_width - -ax1.set_title("Treemap View", fontsize=20, fontweight="bold", pad=15, color=INK) - -# === RIGHT: SUNBURST VIEW === -ax2.set_xlim(-1.3, 1.3) -ax2.set_ylim(-1.3, 1.3) -ax2.set_aspect("equal") -ax2.axis("off") -ax2.set_facecolor(PAGE_BG) - -total_value = df[df["id"] == "root"]["value"].iloc[0] -start_angle = 90 -inner_r1, outer_r1 = 0.32, 0.62 -inner_r2, outer_r2 = 0.67, 1.05 - -for i, nid in enumerate(level1_ids): - ratio = l1_values[i] / total_value - sweep = 360 * ratio - - ax2.add_patch( - Wedge( - (0, 0), - outer_r1, - start_angle, - start_angle + sweep, - width=outer_r1 - inner_r1, - facecolor=color_map[nid], - edgecolor=PAGE_BG, - linewidth=2.5, - alpha=0.75, - ) - ) - - if sweep > 30: - mid_angle = np.radians(start_angle + sweep / 2) - lx = (inner_r1 + outer_r1) / 2 * np.cos(mid_angle) - ly = (inner_r1 + outer_r1) / 2 * np.sin(mid_angle) - angle_deg = (start_angle + sweep / 2) % 360 - text_rot = angle_deg - 180 if 90 < angle_deg <= 270 else angle_deg - ax2.text( - lx, - ly, - df[df["id"] == nid]["label"].iloc[0], - ha="center", - va="center", - fontsize=14, - fontweight="bold", - color="white", - rotation=text_rot, - ) - - if nid in children: - c_ids = children[nid] - c_values = [df[df["id"] == cid]["value"].iloc[0] for cid in c_ids] - c_total = sum(c_values) - c_start = start_angle - - for j, cid in enumerate(c_ids): - c_sweep = sweep * (c_values[j] / c_total) - ax2.add_patch( - Wedge( - (0, 0), - outer_r2, - c_start, - c_start + c_sweep, - width=outer_r2 - inner_r2, - facecolor=color_map[cid], - edgecolor=PAGE_BG, - linewidth=1.5, - alpha=0.92, - ) - ) - - if c_sweep > 12: - c_mid = np.radians(c_start + c_sweep / 2) - clx = (inner_r2 + outer_r2) / 2 * np.cos(c_mid) - cly = (inner_r2 + outer_r2) / 2 * np.sin(c_mid) - angle_deg = (c_start + c_sweep / 2) % 360 - text_rot = angle_deg - 180 if 90 < angle_deg <= 270 else angle_deg - ax2.text( - clx, - cly, - df[df["id"] == cid]["label"].iloc[0], - ha="center", - va="center", - fontsize=13, - fontweight="bold", - color="white", - rotation=text_rot, - ) - - c_start += c_sweep - - start_angle += sweep - -# Center circle showing total -ax2.add_patch(plt.Circle((0, 0), 0.27, color=ELEVATED_BG, zorder=10, ec=INK_SOFT, lw=2)) -ax2.text(0, 0.06, "Total", ha="center", va="center", fontsize=14, fontweight="bold", color=INK) -ax2.text(0, -0.08, f"${total_value}K", ha="center", va="center", fontsize=13, color=INK_SOFT) - -ax2.set_title("Sunburst View", fontsize=20, fontweight="bold", pad=15, color=INK) - -# Style -fig.suptitle("hierarchy-toggle-view · python · seaborn · anyplot.ai", fontsize=24, fontweight="bold", y=0.98, color=INK) - -fig.text( - 0.5, - 0.92, - "Software Project Budget Allocation — Dual Hierarchy View", - ha="center", - fontsize=15, - style="italic", - color=INK_MUTED, -) - -legend_patches = [ - mpatches.Patch(color=color_map[nid], label=df[df["id"] == nid]["label"].iloc[0]) for nid in level1_ids -] -fig.legend( - handles=legend_patches, - loc="lower center", - ncol=4, - fontsize=14, - frameon=True, - fancybox=True, - facecolor=ELEVATED_BG, - edgecolor=INK_SOFT, - labelcolor=INK, - bbox_to_anchor=(0.5, 0.01), -) - -plt.tight_layout(rect=[0, 0.07, 1, 0.90]) - -# Save -plt.savefig(f"plot-{THEME}.png", dpi=300, bbox_inches="tight", facecolor=PAGE_BG) diff --git a/plots/hierarchy-toggle-view/metadata/python/altair.yaml b/plots/hierarchy-toggle-view/metadata/python/altair.yaml deleted file mode 100644 index 8e7eb65020..0000000000 --- a/plots/hierarchy-toggle-view/metadata/python/altair.yaml +++ /dev/null @@ -1,251 +0,0 @@ -library: altair -language: python -specification_id: hierarchy-toggle-view -created: '2026-01-11T09:30:49Z' -updated: '2026-05-19T08:58:16Z' -generated_by: claude-sonnet -workflow_run: 26085145976 -issue: 3697 -language_version: 3.13.13 -library_version: 6.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/altair/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/altair/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/altair/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/altair/plot-dark.html -quality_score: 90 -review: - strengths: - - Elegant interactive toggle using Altair binding_select + selection_point — idiomatic - and non-trivial - - 'Correct Okabe-Ito palette (Engineering = #009E73 first) with full theme-adaptive - chrome for both light and dark renders' - - Both treemap (mark_rect with x/y/x2/y2) and sunburst (mark_arc with theta/theta2/radius/radius2) - correctly implemented as Altair layers - - Realistic organizational hierarchy data with meaningful storytelling subtitle - highlighting key insight - - Clean flat code structure appropriate for the complexity - weaknesses: - - Binary-split treemap layout produces irregular rectangle proportions; squarified - algorithm would give better aesthetic results - - Cell label font sizes are value-mapped (14-24px range) making smallest team labels - borderline at the lower end — raise minimum to 16px - - Minor whitespace gap between title area and top edge of treemap reduces canvas - utilization slightly - image_description: |- - Light render (plot-light.png): - Background: Warm near-white (#FAF8F1) — configure_view fill set correctly - Chrome: Title "hierarchy-toggle-view · python · altair · anyplot.ai" in dark ink, large and bold — readable. Subtitle (Engineering leads with 232 staff / 46% headcount context) in smaller dark text — readable. Legend at top-right with warm-white elevated background (#FFFDF6), dark title/label text — readable. - Data: Four departments as Okabe-Ito colored rectangles: Engineering=#009E73 (green), Sales=#D55E00 (orange), Marketing=#0072B2 (blue), Operations=#CC79A7 (pink). Leaf team nodes labeled in bold white text, sizes proportional to headcount. First series correctly #009E73. - Legibility verdict: PASS — all text (title, subtitle, cell labels, legend) readable against light background. - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — configure_view fill set correctly for dark theme - Chrome: Title in warm off-white (#F0EFE8) — readable against dark surface. Subtitle in slightly muted tone — legible. Legend background switches to elevated dark (#242420) with light-colored labels (#B8B7B0) — readable. - Data: Colors identical to light render — Engineering=#009E73, Sales=#D55E00, Marketing=#0072B2, Operations=#CC79A7. White cell labels on same Okabe-Ito backgrounds — unchanged and visible. - Legibility verdict: PASS — no dark-on-dark failures. All text elements readable in dark theme. - criteria_checklist: - visual_quality: - score: 28 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: Title 28px, legend title 20px, legend labels 18px explicitly set. - Cell labels value-mapped 14-24px — borderline minimum but readable at rendered - resolution. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text; all cell labels centered within their rectangles. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Treemap rectangles fill canvas; all department tiles clearly visible - with white labels. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette is CVD-safe; white text on colored rectangles provides - high contrast. - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: Treemap fills ~75% of canvas. Legend sits outside data area (~8% - horizontal). Minor gap between title and treemap top edge. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title format correct; no axis labels needed for treemap — appropriate. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series Engineering=#009E73. Remaining: #D55E00, #0072B2, #CC79A7 - — correct Okabe-Ito order. Backgrounds theme-correct in both renders.' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: true - comment: Clean professional treemap with correct palette and theme-adaptive - chrome. Dynamic label sizing is thoughtful. Binary-split layout produces - irregular proportions. Well-configured library output. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: No unnecessary chrome. PAGE_BG-colored cell borders (strokeWidth=3) - create clean separation. Legend custom-styled with theme tokens. Solid refinement. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Subtitle explicitly calls out key insight (Engineering dominance). - Color+area encoding enables immediate department comparison. Good visual - hierarchy. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Both treemap (mark_rect) and sunburst (mark_arc with theta/radius) - implemented as layered chart with dropdown toggle. - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Dropdown toggle (binding_select) ✓; consistent colors across views - ✓; tooltips on both views ✓; view switching via conditional opacity ✓. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Hierarchical data correctly structured (Company → Dept → Team). Leaf - values map to area (treemap) and angle (sunburst). - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title: ''hierarchy-toggle-view · python · altair · anyplot.ai'' - ✓. Department legend labels match data ✓.' - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: All hierarchy levels shown (root, department, team). Both treemap - and sunburst views implemented. 19 nodes across 3 levels. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: TechCorp org structure with realistic department and team names (Backend, - Frontend, DevOps, QA, Data Sci, Enterprise, SMB, etc.). - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Team headcounts (20-62) and department totals realistic for mid-size - tech company. Engineering at 46% is plausible. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Flat script: imports → data → treemap layout → sunburst layout → - chart → save. No functions or classes.' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: os, altair, numpy, pandas — all used, no extraneous imports. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Custom treemap layout algorithm appropriately complex for a library - without native treemap. Sunburst angle computation clean. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png (scale_factor=3.0) and plot-{THEME}.html ✓. - library_mastery: - score: 10 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: 'Full declarative encoding: mark_rect, mark_arc, mark_text with correct - channels (x/y/x2/y2 for treemap; theta/theta2/radius/radius2 for sunburst). - alt.layer() composition and alt.condition() for selection-driven opacity.' - - id: LM-02 - name: Distinctive Features - score: 5 - max: 5 - passed: true - comment: alt.binding_select + alt.selection_point for interactive dropdown - toggle is distinctively Vega-Lite/Altair. Layered treemap+sunburst with - conditional opacity is elegant Altair-specific pattern. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - hover-tooltips - - html-export - - layer-composition - - polar-projection - patterns: - - iteration-over-groups - dataprep: [] - styling: - - edge-highlighting diff --git a/plots/hierarchy-toggle-view/metadata/python/bokeh.yaml b/plots/hierarchy-toggle-view/metadata/python/bokeh.yaml deleted file mode 100644 index ebfde37a84..0000000000 --- a/plots/hierarchy-toggle-view/metadata/python/bokeh.yaml +++ /dev/null @@ -1,264 +0,0 @@ -library: bokeh -language: python -specification_id: hierarchy-toggle-view -created: '2026-01-11T09:31:55Z' -updated: '2026-05-19T09:00:04Z' -generated_by: claude-sonnet -workflow_run: 26085052111 -issue: 3697 -language_version: 3.13.13 -library_version: 3.9.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/bokeh/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/bokeh/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/bokeh/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/bokeh/plot-dark.html -quality_score: 85 -review: - strengths: - - 'Excellent spec compliance: both treemap and sunburst correctly implemented with - interactive HTML toggle; static PNG shows side-by-side for immediate visual comparison' - - 'Outstanding data quality: realistic company budget hierarchy with authentic department - names and sensible budget proportions' - - Correct Okabe-Ito palette applied at all 4 hierarchy levels; consistent colors - across treemap and sunburst views aiding cross-view identification - - 'Proper theme adaptation: both light and dark renders pass readability checks; - INK/INK_SOFT tokens threaded through all text elements' - - 'Idiomatic Bokeh: CustomJS toggle with Button widgets, ColumnDataSource, patches - for sunburst — leverages Bokeh client-side interactivity model effectively' - weaknesses: - - 'DE-03 (3/6): No visual emphasis or annotation guides the viewer to a specific - insight (e.g., which department dominates); adding a callout or center label would - lift storytelling' - - 'VQ-05 (3/4): Sunburst is undersized relative to the right panel — increasing - radius to fill more of the right panel would improve canvas utilization' - - 'DE-02 (4/6): Sunburst segment borders use line_color=PAGE_BG which is near-invisible - in dark mode; use ELEVATED_BG for dark theme to maintain separation cues' - - 'VQ-01 (6/8): Legend label font size (14pt) is slightly below the 16px recommendation; - bumping to 16pt would improve readability' - - 'CQ-04 (1/2): Static and HTML figures share almost identical code — a small helper - or shared data dict would reduce duplication' - image_description: |- - Light render (plot-light.png): - Background: warm off-white #FAF8F1 — correct per style guide - Chrome: title "hierarchy-toggle-view · python · bokeh · anyplot.ai" at top-left in dark INK text (readable); panel headers "Treemap View" and "Sunburst View" in 26pt bold dark text (prominent); legend "Budget Hierarchy" on far right with dark INK title and INK_SOFT label text - Data: Treemap rectangles use Engineering=#009E73 (brand green) for Frontend/Backend/DevOps/QA with derived shades; Marketing=#D55E00 for Digital/Content/Analytics; Operations=#0072B2 for Logistics/Facilities/Procurement; HR=#CC79A7 for Recruiting/Training/Benefits; same colors in sunburst wedges; all segments clearly visible with alpha=0.92 - Legibility verdict: PASS — all text readable; large treemap cells show name+value labels; smaller cells unlabeled by design (size filter) - - Dark render (plot-dark.png): - Background: warm near-black #1A1A17 — correct per style guide - Chrome: title in light #F0EFE8 text (readable); panel headers "Treemap View" and "Sunburst View" in light INK text (readable); legend uses ELEVATED_BG (#242420) fill with INK_SOFT (#B8B7B0) label text and INK (#F0EFE8) title text — all readable; segment borders use line_color=PAGE_BG=#1A1A17 (low contrast in dark mode but segments distinguishable by color) - Data: all data colors identical to light render — Engineering=#009E73, Marketing=#D55E00, Operations=#0072B2, HR=#CC79A7 with same derived sub-department shades; sunburst center hole shows as near-black background (correct); no color changes between themes - Legibility verdict: PASS — no dark-on-dark text failures; treemap labels use adaptive INK=#F0EFE8 on dark background; value labels use INK_SOFT=#B8B7B0 (visible); legend text readable - criteria_checklist: - visual_quality: - score: 27 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 6 - max: 8 - passed: true - comment: All sizes explicitly set (title 28pt, panel headers 26pt, labels - 18pt/15pt, legend 14pt/18pt); legend text at 14pt slightly below 16px recommendation - for 4800px canvas - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: Size-filtered labeling (large_mask) prevents overlap in treemap; - no collision in sunburst - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: All treemap rectangles and sunburst wedges clearly visible in both - themes with alpha=0.92 - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito departments provide high contrast; adjacent segments distinguishable - by hue and luminance - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: Treemap fills left panel well; sunburst undersized in right panel - leaving empty space above/below; legend positioned usably - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Panel headers serve as section titles; axes intentionally hidden - (correct for hierarchy plots) - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Engineering=#009E73 (brand green) first; Okabe-Ito order maintained - for departments; backgrounds #FAF8F1/#1A1A17; chrome adapts correctly' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Intentional color hierarchy (parent Okabe-Ito → derived child shades), - vertical divider, indented legend; polished above default 4 - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Axes/grid hidden, clean backgrounds, panel headers, alpha=0.92; segment - borders near-invisible in dark mode (line_color=PAGE_BG) - - id: DE-03 - name: Data Storytelling - score: 3 - max: 6 - passed: false - comment: Dual-view layout is the story mechanism, consistent colors aid cross-view - identification, but no specific insight emphasized or annotated - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Both treemap (nested proportional rectangles) and sunburst (radial - segments) correctly implemented - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Toggle button in HTML, consistent colors, hover tooltips present; - animated transitions not implemented (simple visibility toggle); hover state - not preserved across toggle - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Area (treemap) and arc angle (sunburst) both proportional to budget - value; 2-level hierarchy correctly shown - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title matches spec format exactly; legend Budget Hierarchy with correct - department and sub-department labels - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 4 departments, 13 sub-departments, 18 nodes total; 3 hierarchy levels; - both treemap and sunburst fully covered - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Company budget hierarchy is a real, neutral business scenario with - authentic department names - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Sub-department budgets $300K-$1500K; total ~$9.1M plausible for mid-size - company; proportions internally consistent - code_quality: - score: 9 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Linear flow; no functions or classes - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set; data is deterministic - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports used; selenium correctly required for Selenium screenshot - pattern - - id: CQ-04 - name: Code Elegance - score: 1 - max: 2 - passed: false - comment: Custom squarify algorithm (~50 lines) necessary; static and HTML - figures duplicate significant code; acceptable but verbose - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.html + plot-{THEME}.png via Selenium; correct - per bokeh.md guide - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Proper ColumnDataSource, CustomJS, Button, Legend/LegendItem, add_layout, - Label; row/column layout used correctly; Selenium pattern follows library - guide - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: CustomJS callbacks for server-less toggle is distinctly Bokeh; patches - glyph for sunburst; Button model with js_on_click; HoverTool via tooltips - shorthand - verdict: APPROVED -impl_tags: - dependencies: - - selenium - techniques: - - hover-tooltips - - html-export - - patches - - custom-legend - patterns: - - data-generation - - columndatasource - - iteration-over-groups - dataprep: [] - styling: - - alpha-blending - - minimal-chrome - - edge-highlighting diff --git a/plots/hierarchy-toggle-view/metadata/python/letsplot.yaml b/plots/hierarchy-toggle-view/metadata/python/letsplot.yaml deleted file mode 100644 index dfceee9ec6..0000000000 --- a/plots/hierarchy-toggle-view/metadata/python/letsplot.yaml +++ /dev/null @@ -1,252 +0,0 @@ -library: letsplot -language: python -specification_id: hierarchy-toggle-view -created: '2026-01-11T09:33:07Z' -updated: '2026-05-19T09:10:59Z' -generated_by: claude-sonnet -workflow_run: 26085537431 -issue: 3697 -language_version: 3.13.13 -library_version: 4.9.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/letsplot/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/letsplot/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/letsplot/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/letsplot/plot-dark.html -quality_score: 80 -review: - strengths: - - Correct dual-view implementation using letsplot grammar-of-graphics (geom_rect - treemap + geom_polygon sunburst) - - gggrid composition is idiomatic and distinctive letsplot usage - - 'Perfect theme-adaptive chrome: #FAF8F1/#1A1A17 backgrounds, INK tokens throughout, - no dark-on-dark failures' - - Genuine interactive HTML toggle (Canvas API with real JS drawing) — not faked - drawn UI - - 'Perfect data quality: realistic company budget scenario with correct hierarchical - value propagation' - - All axes, spines, and grid removed cleanly; intentional color hierarchy (Okabe-Ito - departments + lighter child tints) - weaknesses: - - Narrow Marketing and Operations treemap columns cause letsplot to truncate department - labels to 'Mark.' / 'Oper.' — treemap column width calculation should reserve - minimum column width or use abbreviated labels proactively - - HTML output bypasses letsplot's native ggsave-to-HTML export in favor of hand-written - JavaScript, missing opportunity to showcase letsplot's distinctive interactive - export - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct theme surface, not pure white - Chrome: Title "hierarchy-toggle-view · python · letsplot · anyplot.ai" in dark #1A1A17 ink, clearly readable. Subtitle in INK_SOFT. Panel titles "Treemap View" / "Sunburst View" in dark ink, readable. No tick labels (appropriately removed). - Data: Treemap uses Okabe-Ito: Engineering=#009E73 (green, first series), Sales=#D55E00 (vermillion), Marketing=#0072B2 (blue), Operations=#CC79A7 (purple). Child cells use lighter tints of parent colors. Sunburst inner ring matches department colors; outer ring uses lighter child variants. Percentage labels embedded in treemap cells. - Issue: Marketing and Operations columns are narrow; letsplot truncates department headers to "Mark." and "Oper." Child labels in those columns are small but marginally readable. - Legibility verdict: PASS (with narrow-column truncation caveat) - - Dark render (plot-dark.png): - Background: Near-black #1A1A17 — correct dark theme surface - Chrome: Title and subtitle in light #F0EFE8 ink, clearly readable against dark background. Panel titles in light ink. No dark-on-dark failures detected. INK_SOFT token correctly applied to secondary text. - Data: All data colors identical to light render — Engineering green, Sales vermillion, Marketing blue, Operations purple in both treemap and sunburst. White text labels on colored cell/segment backgrounds read well. Child labels on sunburst outer ring use INK (dark) ink which reads against the light-ish tinted child segments. - Same narrow-column truncation ("Mark.", "Oper.") present as in light render. - Legibility verdict: PASS (with same narrow-column caveats) - criteria_checklist: - visual_quality: - score: 22 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 5 - max: 8 - passed: true - comment: Sizes explicitly set (title 24pt, panel titles 22pt, dept labels - 16pt, child labels 10-11pt), but narrow Marketing/Operations columns cause - cramped rendering - - id: VQ-02 - name: No Overlap - score: 3 - max: 6 - passed: true - comment: Department headers truncated to 'Mark.' and 'Oper.' in narrow columns; - child labels cramped but main content readable - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: All treemap cells and sunburst segments clearly visible; sunburst - proportions well-balanced - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito positions 1-4 for departments; CVD-safe; adequate luminance - contrast between parent/child tones - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: Side-by-side gggrid layout works; some wasted canvas space as 750x750 - panels don't fill 1600x900 grid evenly - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title in correct format; axes appropriately removed (element_blank) - for this chart type - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Engineering first=#009E73; Okabe-Ito positions 1-4 for departments; - #FAF8F1/#1A1A17 backgrounds; INK tokens correct' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Intentional color hierarchy (Okabe-Ito departments + lighter child - variants); clean dual-panel composition; above library defaults - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: All spines, axes, grid, ticks removed; white cell borders give clean - separation; narrow treemap columns reduce polish - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Dual-view provides complementary perspectives; percentage labels - guide interpretation; color hierarchy establishes visual structure - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Both treemap (geom_rect) and sunburst (geom_polygon concentric rings) - correctly implemented - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Toggle present in HTML; consistent colors across views; both views - in same container; hover state not preserved (acceptable) - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: 15-node hierarchy; values correctly drive cell area and arc angle; - parent values summed from children - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title matches required format; no legend appropriate as labels embedded - in chart elements - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 3-level hierarchy with 15 nodes; varied value distribution; percentage - labels show relative contribution - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Company budget allocation across plausible departments; neutral business - scenario - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Values 65-180K with ~$1.2M total; department ratios internally consistent - and realistic - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Linear Imports -> Data -> Plot -> Save; no functions or classes - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports used; standard library used appropriately - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean implementation; HTML uses genuine Canvas API JS (real interactivity, - not fake drawn UI) - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png (scale=3) and plot-{THEME}.html; current API - library_mastery: - score: 6 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Grammar-of-graphics for both chart types; gggrid for composition; - coord_fixed, scale_fill_manual, theme customization all idiomatic - - id: LM-02 - name: Distinctive Features - score: 2 - max: 5 - passed: false - comment: gggrid is distinctive letsplot feature used well; HTML is hand-crafted - JS Canvas rather than letsplot's native ggsave HTML export - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - html-export - - layer-composition - patterns: - - data-generation - - iteration-over-groups - dataprep: [] - styling: - - minimal-chrome - - alpha-blending diff --git a/plots/hierarchy-toggle-view/metadata/python/matplotlib.yaml b/plots/hierarchy-toggle-view/metadata/python/matplotlib.yaml deleted file mode 100644 index ea4c5b4ec6..0000000000 --- a/plots/hierarchy-toggle-view/metadata/python/matplotlib.yaml +++ /dev/null @@ -1,250 +0,0 @@ -library: matplotlib -language: python -specification_id: hierarchy-toggle-view -created: '2026-05-19T08:25:20Z' -updated: '2026-05-19T08:46:57Z' -generated_by: claude-sonnet -workflow_run: 26084770970 -issue: 3697 -language_version: 3.13.13 -library_version: 3.10.9 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/matplotlib/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/matplotlib/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 81 -review: - strengths: - - Alpha-gradient within each category color elegantly distinguishes subcategories - without introducing extra hues - - 'Perfect Okabe-Ito palette compliance: Engineering = #009E73 as first series, - canonical order maintained' - - 'Fully theme-adaptive chrome: all text, backgrounds, and legend frame correctly - flip between light/dark using token variables' - - Both treemap and sunburst built from matplotlib's low-level patch primitives — - idiomatic and distinctive - - KISS code structure with deterministic data and clean linear flow - weaknesses: - - 'Font sizes consistently below guidelines: suptitle 20pt (rec >=24pt), item labels - 9-11pt (rec >=16pt for labels inside segments)' - - 'Hierarchy is shallow: only 2 levels, 15 nodes at the spec minimum; adding a third - level would better demonstrate the toggle view''s depth-exploration advantage' - - Sunburst panel wastes some vertical space due to forced equal aspect ratio; could - be addressed with tighter subplot sizing - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct theme surface - Chrome: Suptitle in dark INK (#1A1A17); subchart titles "Treemap"/"Sunburst" in dark INK; legend text in INK_SOFT (#4A4A44). All readable. - Data: Engineering = #009E73 (green, bottom band/inner wedge), Marketing = #D55E00 (orange), Operations = #0072B2 (blue), Sales = #CC79A7 (purple/pink). First series correctly uses brand green. White text labels on colored cells/wedges are clearly readable. - Legibility verdict: PASS - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct dark surface - Chrome: Suptitle in light #F0EFE8 (INK dark-mode); subchart titles in same INK token (light); legend text in #B8B7B0 (INK_SOFT dark-mode); legend frame uses ELEVATED_BG #242420. All readable against dark background. - Data: Colors identical to light render — Engineering green, Marketing orange, Operations blue, Sales purple. White hardcoded text on colored wedges reads well on dark surface. Center labels ($1000M, Total) use adaptive INK/INK_SOFT tokens and appear in light gray. - No dark-on-dark failures observed. - Legibility verdict: PASS - criteria_checklist: - visual_quality: - score: 25 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 4 - max: 8 - passed: false - comment: 'Sizes explicitly set but below guidelines: suptitle 20pt (rec >=24pt), - item labels 9-11pt (rec >=16pt)' - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No text collisions; small segments correctly left unlabeled (item_sweep - threshold) - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: All treemap cells and sunburst wedges clearly visible; alpha variation - readable - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: White text on colored wedges, Okabe-Ito CVD-safe palette - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: Good side-by-side layout; sunburst set_aspect('equal') leaves moderate - empty vertical space in panel - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Treemap/Sunburst subchart titles descriptive; $M units embedded in - cell labels - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Engineering=#009E73 first, canonical Okabe-Ito order; backgrounds - #FAF8F1/#1A1A17; full theme-adaptive chrome' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Alpha-gradient within category hues is an intentional design choice - above library defaults - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Axes off, no grid, styled legend frame, thin gap between treemap - cells - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Engineering's 40% dominance visually clear through area and angle; - center $1000M total provides context - spec_compliance: - score: 12 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 4 - max: 5 - passed: true - comment: Treemap and sunburst both implemented; toggle impossible in static - matplotlib, side-by-side is correct static alternative - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Both views present with consistent colors; interactive toggle/animation - not applicable - - id: SC-03 - name: Data Mapping - score: 2 - max: 3 - passed: true - comment: Area/angle proportional to values; only 2 hierarchy levels and 15 - nodes (spec minimum) - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title matches required format exactly; legend labels match category - names - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: Both treemap and sunburst perspectives demonstrated; only 2 hierarchy - levels limits full coverage - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Corporate annual budget ($M) — neutral, realistic, comprehensible - business domain - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: $1,000M total with plausible department splits; Engineering 40% realistic - for tech company - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear flow: imports -> theme -> data -> treemap -> sunburst -> - legend -> save' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Fully deterministic hardcoded data; no random seed needed - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All four imports (os, mpatches, plt, np) actively used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean Pythonic code; alpha computation appropriate; no fake UI elements - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png with dpi=300 and bbox_inches='tight' - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Uses mpatches.Rectangle and mpatches.Wedge via add_patch() — canonical - approach for custom geometric charts - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Building both treemap and sunburst from low-level patch primitives - without external helpers is distinctive matplotlib usage - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - patches - - subplots - - annotations - - custom-legend - patterns: - - explicit-figure - - iteration-over-groups - dataprep: [] - styling: - - minimal-chrome - - alpha-blending - - edge-highlighting diff --git a/plots/hierarchy-toggle-view/metadata/python/plotly.yaml b/plots/hierarchy-toggle-view/metadata/python/plotly.yaml deleted file mode 100644 index 8e2cf00af2..0000000000 --- a/plots/hierarchy-toggle-view/metadata/python/plotly.yaml +++ /dev/null @@ -1,275 +0,0 @@ -library: plotly -language: python -specification_id: hierarchy-toggle-view -created: '2026-01-11T10:08:45Z' -updated: '2026-05-19T08:40:46Z' -generated_by: claude-sonnet -workflow_run: 26084957040 -issue: 3697 -language_version: 3.13.13 -library_version: 6.7.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/plotly/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/plotly/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/plotly/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/plotly/plot-dark.html -quality_score: 87 -review: - strengths: - - 'Creative dual-output strategy: side-by-side PNG for static comparison, interactive - HTML with toggle buttons for dynamic switching — exactly the right approach for - a plotly implementation' - - 'Perfect Okabe-Ito palette compliance with thoughtful color inheritance: departments - use positions 1-4, teams inherit their parent''s color creating clear visual hierarchy' - - 'Excellent library mastery: uses go.Treemap + go.Sunburst, make_subplots with - type-specific specs, updatemenus toggle, insidetextorientation=''radial'', hovertemplate - with percentParent formatting' - - All font sizes explicitly set (title 28px, view labels 22px, treemap text 20px, - sunburst text 16px, footer 18px) — fully readable at 4800×2700px - - 'Perfect theme-adaptive chrome: PAGE_BG, ELEVATED_BG, INK, INK_SOFT all applied - correctly — both renders pass readability checks' - - 'Realistic and internally consistent data: Engineering budget subdivisions sum - exactly to department total (1800+1200+900+600=4500), same for all other departments' - - Explanatory footer annotation articulates the purpose of each view type, enhancing - data storytelling - weaknesses: - - Some repetition between PNG figure and HTML figure trace definitions — the Treemap - and Sunburst traces are defined twice with nearly identical parameters; a shared - data structure could reduce duplication - - Sunburst outer ring has some small segments (Partners, Finance, HR) where labels - are slightly cramped and hard to read at normal viewing size - - No explicit animation/transition parameters for the HTML toggle (spec mentions - smooth animated transitions) — Plotly has default transitions but they aren't - configured - - DE-01 could be pushed higher with more distinctive styling choices — the current - design is solid but follows standard treemap/sunburst defaults beyond the color - scheme - image_description: |- - Light render (plot-light.png): - Background: warm off-white #FAF8F1 — correct theme surface - Chrome: title "hierarchy-toggle-view · python · plotly · anyplot.ai" in dark ink (#1A1A17) clearly visible; "Treemap View" and "Sunburst View" section labels in INK_SOFT (#4A4A44) readable; footer annotation visible; "Company" root label in light ELEVATED_BG - Data: Engineering segments in #009E73 (brand green) — dominant and immediately visible in both views; Marketing in #D55E00 (orange); Sales in #0072B2 (blue); Operations in #CC79A7 (purple); team-level segments inherit parent department color for clear hierarchy - Legibility verdict: PASS — all text readable; minor crowding on smallest sunburst outer-ring segments (Partners, Finance, HR) but not severe - - Dark render (plot-dark.png): - Background: warm near-black #1A1A17 — correct dark theme surface - Chrome: title in light #F0EFE8 clearly readable on dark background; "Treemap View" / "Sunburst View" labels in #B8B7B0 visible; footer annotation legible; no dark-on-dark failures detected - Data: colors identical to light render — Engineering #009E73, Marketing #D55E00, Sales #0072B2, Operations #CC79A7 — all vivid and distinguishable against dark background - Legibility verdict: PASS — all chrome correctly flipped to light text on dark surface; data colors unchanged between themes - criteria_checklist: - visual_quality: - score: 29 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: 'All font sizes explicitly set: title 28px, view labels 22px, treemap - text 20px, sunburst text 16px, footer 18px — fully readable at 4800x2700px - in both themes' - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: No significant overlap; minor crowding on smallest sunburst outer-ring - segments (Partners, Finance, HR) but main content fully readable - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Both treemap and sunburst segments clearly visible with good contrast; - color separation between departments is excellent - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette is CVD-safe; good luminance contrast between all - four department colors; no red-green-only encoding - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Side-by-side layout fills canvas well; balanced margins (t=130, l=20, - r=20, b=90); both charts proportionally sized - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title matches required format; 'Treemap View' / 'Sunburst View' labels; - footer explains purpose of each view; texttemplate shows label+value within - segments - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Engineering=#009E73 (first position), Marketing=#D55E00, Sales=#0072B2, - Operations=#CC79A7 — canonical Okabe-Ito order; backgrounds #FAF8F1 light - / #1A1A17 dark; data colors identical in both renders' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Above configured defaults: intentional color inheritance (dept → - team), ELEVATED_BG root node, branded toggle button styling in HTML, clean - segment borders using PAGE_BG — but not publication-ready polish' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Clean segment borders via PAGE_BG line color, explicit margins, appropriate - font hierarchy; treemap/sunburst have no spines to remove but available - refinements are applied - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Footer annotation explicitly explains complementary value of each - view; Engineering's dominance is visually evident; color hierarchy guides - viewer through dept → team structure; no annotation highlighting any specific - insight - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Both treemap and sunburst implemented correctly; side-by-side PNG - + toggle HTML is a well-designed two-output approach - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Toggle control in HTML ✓, consistent colors ✓, hover state in HTML - ✓; smooth animated transitions not explicitly configured (uses Plotly defaults) - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: labels/parents/values correctly mapped; branchvalues='total' ensures - proportional areas; all 18 nodes visible in both views - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title exactly 'hierarchy-toggle-view · python · plotly · anyplot.ai'; - no separate legend needed as segment labels identify all nodes - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 3 hierarchy levels (Company > Department > Team); 18 nodes within - 15-60 range; meaningful value variation across all levels; both views demonstrate - their respective strengths - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Company budget allocation — professional, neutral, immediately comprehensible - business scenario - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: 'All department budgets sum exactly to their total (Engineering: - 1800+1200+900+600=4500 ✓, Marketing: 900+600+600=2100 ✓, etc.); realistic - relative proportions for a tech company' - code_quality: - score: 8 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 2 - max: 3 - passed: true - comment: No functions/classes; clear linear structure; but Treemap+Sunburst - traces defined twice (once for PNG make_subplots, once for HTML toggle figure) - — slight verbosity from dual-output approach - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Fully deterministic data with no randomness needed; comment confirms - this explicitly - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only os, plotly.graph_objects, and plotly.subplots — all used - - id: CQ-04 - name: Code Elegance - score: 1 - max: 2 - passed: true - comment: No fake UI elements; real Plotly updatemenus used; but trace parameter - duplication between PNG and HTML paths is verbose - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html; current Plotly API; - include_plotlyjs='cdn' - library_mastery: - score: 10 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: Idiomatic go.Treemap and go.Sunburst; make_subplots with type-specific - specs; updatemenus for toggle; branchvalues='total'; hovertemplate with - percentParent formatting; include_plotlyjs='cdn' - - id: LM-02 - name: Distinctive Features - score: 5 - max: 5 - passed: true - comment: 'Uses features uniquely Plotly: go.Treemap + go.Sunburst in combined - subplots, make_subplots with type annotations for mixed chart types, updatemenus - toggle, insidetextorientation=''radial'', dual write_image + write_html - output strategy' - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - subplots - - hover-tooltips - - html-export - - annotations - patterns: - - iteration-over-groups - dataprep: [] - styling: - - edge-highlighting diff --git a/plots/hierarchy-toggle-view/metadata/python/plotnine.yaml b/plots/hierarchy-toggle-view/metadata/python/plotnine.yaml deleted file mode 100644 index 88ffcb4dd4..0000000000 --- a/plots/hierarchy-toggle-view/metadata/python/plotnine.yaml +++ /dev/null @@ -1,247 +0,0 @@ -library: plotnine -language: python -specification_id: hierarchy-toggle-view -created: '2026-01-11T21:04:35Z' -updated: '2026-05-19T09:20:22Z' -generated_by: claude-sonnet -workflow_run: 26085242963 -issue: 3697 -language_version: 3.13.13 -library_version: 0.15.4 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/plotnine/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/plotnine/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 70 -review: - strengths: - - Creative use of geom_polygon with manually computed arc coordinates to render - sunburst rings — a clever approach for a static grammar-of-graphics library - - Side-by-side static dual-view is the correct interpretation of an interactive - spec in plotnine; both views share consistent color mapping - - Realistic, neutral research budget data with values labeled in both views aids - interpretation - - Clean theme_void() usage eliminates chart chrome; white cell borders on rectangles - and polygons provide definition - weaknesses: - - 'Dark-on-dark legibility failure: annotate(color="#333333") for section headings - and color="#555555" for center dollar value produce ~1.4:1 contrast on #1A1A17 - background — add ANYPLOT_THEME env var handling and replace all hardcoded dark - annotation colors with theme-adaptive tokens (INK / INK_SOFT)' - - 'Wrong output filename: plot.save("plot.png") must be plot.save(f"plot-{THEME}.png", - dpi=300); the pipeline expects plot-light.png and plot-dark.png' - - 'Non-Okabe-Ito palette: replace #306998, #FFD43B, #2E8B57, #E07B39 with Okabe-Ito - order: #009E73, #D55E00, #0072B2, #CC79A7; first series must be #009E73' - - Title format in code (line 266) says hierarchy-toggle-view · plotnine · pyplots.ai - but required format is hierarchy-toggle-view · python · plotnine · anyplot.ai - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correctly theme-adapted - Chrome: Title "hierarchy-toggle-view · python · plotnine · anyplot.ai" in blue (#306998) — readable; subtitle "Research Department Budget Allocation" in gray — readable; section headings "Treemap View" and "Sunburst View" in dark gray (#333333) — readable on light background - Data: Treemap uses four color families (teal/blue-green Biology, orange-brown Chemistry, blue Physics, pink/purple Computing); sunburst uses same colors in concentric rings; white borders separate segments; "Total $1370K" center annotation visible - Legibility verdict: PASS - - Dark render (plot-dark.png): - Background: Near-black (#1A1A17) — correctly theme-adapted by pipeline - Chrome: FAIL — section headings "Treemap View" and "Sunburst View" use hardcoded #333333 (dark gray) producing ~1.4:1 contrast on #1A1A17 background (WCAG minimum 4.5:1); subtitle color #666666 also low-contrast; "$1370K" center annotation uses #555555 — dark-on-dark; title uses #306998 (blue) which provides marginal contrast only - Data: Colors identical to light render — teal, orange-brown, blue, pink/purple fills unchanged (data colors correctly theme-independent) - Legibility verdict: FAIL — multiple chrome text elements (section headings, subtitle, center annotation) are dark-on-dark in the dark render; no ANYPLOT_THEME handling in code means all hardcoded dark annotation colors fail on dark background - criteria_checklist: - visual_quality: - score: 19 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 4 - max: 8 - passed: false - comment: 'Light render fully readable; dark render has dark-on-dark failures: - #333333 section headings and #555555 dollar annotation invisible on #1A1A17 - background (~1.4:1 contrast)' - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: Labels mostly well-spaced; minor crowding on smaller sunburst outer - segments - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: All rectangles and polygon wedges clearly distinguishable in both - renders - - id: VQ-04 - name: Color Accessibility - score: 1 - max: 2 - passed: false - comment: Four-color scheme reasonably CVD-distinguishable but uses custom - hexes instead of Okabe-Ito - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: Good side-by-side composition; nothing cut off - - id: VQ-06 - name: Axis Labels & Title - score: 1 - max: 2 - passed: false - comment: Section titles and subtitle present; code title annotation (line - 266) has wrong format - - id: VQ-07 - name: Palette Compliance - score: 0 - max: 2 - passed: false - comment: Custom colors not Okabe-Ito; no ANYPLOT_THEME handling; hardcoded - dark chrome fails in dark mode - design_excellence: - score: 10 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: true - comment: Creative dual-view side-by-side layout; consistent color-family mapping; - limited typographic refinement - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: true - comment: theme_void() removes chrome; white borders on shapes add definition - - id: DE-03 - name: Data Storytelling - score: 3 - max: 6 - passed: true - comment: Dual-view enables comparison; values labeled in both views; Total - center annotation - spec_compliance: - score: 12 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 4 - max: 5 - passed: true - comment: Both treemap and sunburst correctly implemented as static side-by-side; - plotnine cannot produce interactive toggles - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Both views present; consistent colors; toggle control not implemented - (acceptable for static library); per-segment labels shown - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Treemap area proportional to values; sunburst sweep angle proportional - to values; two-level hierarchy correct - - id: SC-04 - name: Title & Legend - score: 2 - max: 3 - passed: false - comment: Image shows correct title format but code (line 266) has wrong format; - no legend needed - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: Two-level hierarchy; both views; all 15 nodes labeled with values - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Research department budget allocation — realistic, domain-neutral, - plausible - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Subdepartment values $70K-$200K with $1370K total realistic for academic - department - code_quality: - score: 9 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Flat procedural code, no functions or classes - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Fully deterministic data; no random elements - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imported symbols are used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean iteration over hierarchy; coordinate math for sunburst well-organized - - id: CQ-05 - name: Output & API - score: 0 - max: 1 - passed: false - comment: Saves as plot.png not plot-{THEME}.png; no ANYPLOT_THEME env var - handling - library_mastery: - score: 6 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Layer composition with geom_rect + geom_polygon + geom_text + annotate - is idiomatic; scale_fill_identity correctly maps literal colors - - id: LM-02 - name: Distinctive Features - score: 2 - max: 5 - passed: false - comment: geom_polygon for sunburst arc construction is creative; annotate(point) - for center circle; limited use of plotnine-specific scaling or stat features - verdict: REJECTED -impl_tags: - dependencies: [] - techniques: - - layer-composition - - annotations - patterns: - - iteration-over-groups - dataprep: [] - styling: - - minimal-chrome - - edge-highlighting diff --git a/plots/hierarchy-toggle-view/metadata/python/pygal.yaml b/plots/hierarchy-toggle-view/metadata/python/pygal.yaml deleted file mode 100644 index b77e889db4..0000000000 --- a/plots/hierarchy-toggle-view/metadata/python/pygal.yaml +++ /dev/null @@ -1,249 +0,0 @@ -library: pygal -language: python -specification_id: hierarchy-toggle-view -created: '2026-01-11T21:05:27Z' -updated: '2026-05-19T09:34:19Z' -generated_by: claude-sonnet -workflow_run: 26085340441 -issue: 3697 -language_version: 3.13.13 -library_version: 3.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/pygal/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/pygal/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/pygal/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/pygal/plot-dark.html -quality_score: 81 -review: - strengths: - - 'Excellent data quality: realistic file-system scenario with plausible GB values - and full 3-level hierarchy coverage' - - 'Thoughtful color design: Okabe-Ito tints within color families convey parent-child - relationships without explicit hierarchy rings' - - Both HTML (interactive toggle with fade animation) and PNG (side-by-side composite) - outputs are well-executed for their respective formats - - 'Perfect code quality: clean KISS structure, deterministic data, correct output - naming' - - 'Theme adaptation is thorough: PIL-drawn outer elements and pygal SVG internals - both respond to ANYPLOT_THEME' - weaknesses: - - Sunburst is a flat donut chart (pygal.Pie), not a true concentric sunburst — spec - calls for concentric radial segments showing hierarchical depth; inner ring (categories) - + outer ring (sub-items) structure is missing - - Smallest donut segments (Logs 6 GB, Cache 10 GB) are thin slivers difficult to - identify visually - - 'Right-panel whitespace: donut hollow center and surrounding area leaves significant - canvas unused' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct theme surface - Chrome: PIL-composed outer title in #009E73 (green), subtitle in INK_MUTED (#6B6A63); pygal chart titles ("Treemap View", "Sunburst View") in dark INK (#1A1A17); legend text in dark INK; all readable - Data: Left half = treemap with teal-green (Media), orange (Documents), blue (Applications), pink/purple (System) rectangles with GB value labels; right half = donut chart with 12 proportional segments in same color families; first series is #009E73 (Videos/Media) - Legibility verdict: PASS - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct dark theme surface - Chrome: PIL outer title remains #009E73 (green); subtitle in INK_MUTED (#A8A79F); pygal chart titles and legend text in light cream #F0EFE8 via foreground token; value labels within segments appear in light foreground color; no dark-on-dark failures observed - Data: Identical data colors to light render — same teal greens, oranges, blues, and pinks in both treemap and donut; only chrome (background, text, legend) flips between themes - Legibility verdict: PASS - criteria_checklist: - visual_quality: - score: 25 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: Font sizes explicitly set in png_style (64px title, 36px labels); - all text readable in both themes; -1 for pygal SVG value-label color not - explicitly overridden per-element - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: No meaningful text collisions; small donut segments have minimal - label space but no overlap - - id: VQ-03 - name: Element Visibility - score: 4 - max: 6 - passed: true - comment: Treemap segments all well-sized and labeled; smallest donut segments - (Logs 6 GB, Cache 10 GB) are thin slivers - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito with tints, CVD-safe, distinguishable by luminance as well - as hue - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: Side-by-side composite uses 4800x2700 canvas well; donut hollow center - leaves ~30% of right half unused - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title follows spec format; values labeled with GB units; subtitle - contextualizes both views - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73 (Media/Videos); Okabe-Ito order followed; backgrounds - #FAF8F1/#1A1A17 correct in both themes; data colors identical across themes' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Thoughtful design: custom tint palette within color families for - hierarchy depth, brand-green PIL title, clean composite layout; above defaults - but not publication-ready' - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: true - comment: Custom pygal Style applied, font sizing scaled for large canvas; - pygal SVG-based rendering limits spine/grid control - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Subtitle explains each view's purpose; value labels guide eye; color-family - tints convey parent-child hierarchy - spec_compliance: - score: 12 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 3 - max: 5 - passed: true - comment: Treemap correctly implemented via pygal.Treemap; sunburst approximated - as flat donut (pygal.Pie with inner_radius=0.35) — lacks concentric radial - segments; pygal has no native multi-ring sunburst - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Toggle button with fade animation (HTML) ✓; consistent colors ✓; - smooth transitions ✓; sunburst missing concentric rings for hierarchy depth - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: All 12 leaf nodes correctly sized in both views; parent sums calculated - correctly - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'PIL title: ''Disk Storage · hierarchy-toggle-view · python · pygal - · anyplot.ai'' ✓; HTML title also correct; legend labels match' - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 4 top-level categories × 3 sub-folders = 12 leaf nodes; values vary - from 6–52 GB; 3-level hierarchy demonstrated - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: 'File-system storage allocation: neutral, universally comprehensible - scenario' - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: 240 GB total with plausible breakdown; Videos 52 GB (largest), Logs - 6 GB (smallest); proportions match real-world storage patterns - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear: imports → data → parent-sum calc → HTML charts → PIL composite - → save; no functions or classes' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Fully deterministic data; no random calls - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: 'All imports used: PIL, pygal+Style, os, sys, pathlib' - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean readable code; PIL assembly appropriately complex; no over-engineering - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html correctly - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Uses pygal Style object correctly, per-item color dicts in treemap, - print_values_position, value_formatter lambda, legend options - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Leverages pygal's interactive HTML rendering (SVG+JS), pygal.Treemap - chart type, and multi-chart HTML toggle interface - verdict: APPROVED -impl_tags: - dependencies: - - pillow - techniques: - - html-export - patterns: - - iteration-over-groups - dataprep: [] - styling: [] diff --git a/plots/hierarchy-toggle-view/metadata/python/seaborn.yaml b/plots/hierarchy-toggle-view/metadata/python/seaborn.yaml deleted file mode 100644 index d94d03185e..0000000000 --- a/plots/hierarchy-toggle-view/metadata/python/seaborn.yaml +++ /dev/null @@ -1,254 +0,0 @@ -library: seaborn -language: python -specification_id: hierarchy-toggle-view -created: '2026-01-11T09:31:16Z' -updated: '2026-05-19T08:47:20Z' -generated_by: claude-sonnet -workflow_run: 26084863327 -issue: 3697 -language_version: 3.13.13 -library_version: 0.13.2 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/seaborn/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/hierarchy-toggle-view/python/seaborn/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 75 -review: - strengths: - - Correct dual-panel approach presenting treemap and sunburst side-by-side — best - possible static interpretation of an interactive toggle spec - - FancyBboxPatch with rounded corners and alpha layering (0.35 parent / 0.90 children) - creates effective visual depth in the treemap - - Consistent Okabe-Ito color coding across both views enables category tracing between - treemap and sunburst - - Theme tokens fully applied — both light and dark renders pass the dark-on-dark - / light-on-light legibility check - - Realistic and neutral data (software budget allocation) well-suited for hierarchical - demonstration - weaknesses: - - 'Bottom treemap category labels clipped: text like ''Development: $600K'' appears - as ''Development: $6'' because the label bbox extends below the axes ylim boundary' - - Narrow columns (Operations ~18% width, Research ~15% width) clip child text labels - — 'DevOp' and 'Prototyp' appear truncated - - Seaborn high-level API unused (only sns.set_theme used); all visualization is - via matplotlib FancyBboxPatch/Wedge primitives — inherent library limitation for - hierarchical charts - - Child cell label fontsize 11–13pt falls below the recommended 16pt minimum for - large-canvas (4800x2700) readability - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct theme surface, not pure white - Chrome: Suptitle "hierarchy-toggle-view · python · seaborn · anyplot.ai" in dark #1A1A17 ink at 24pt, clearly legible. Panel titles "Treemap View" / "Sunburst View" at 20pt readable. Subtitle in muted INK_MUTED italic at 15pt visible. Legend text at 14pt readable. Bottom category labels partially clipped (show "Development: $6" instead of "$600K"). - Data: Left treemap — Development (green #009E73) widest column, Infrastructure (orange #D55E00), Operations (blue #0072B2), Research (pink #CC79A7). Level-1 as translucent alpha=0.35 backgrounds, Level-2 as solid alpha=0.90 rounded rectangles with white labels. Right sunburst — inner ring for L1 categories, outer ring for children, same Okabe-Ito colors, white rotated labels. Center circle shows "Total / $1350K" in theme-ink colors. - Legibility verdict: PASS — all major text readable; minor clipping on narrow column labels and bottom treemap labels - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct dark surface, not pure black - Chrome: All text flips correctly to light tones — suptitle in #F0EFE8, subtitle in #A8A79F, panel titles in #F0EFE8, legend box in elevated #242420 with light label text. Center circle text in light ink. No dark-on-dark failures observed — all labels clearly visible against dark background. - Data: Colors identical to light render — Development #009E73, Infrastructure #D55E00, Operations #0072B2, Research #CC79A7. Same alpha layering. Same layout. Same minor label clipping issues as light render. - Legibility verdict: PASS — theme adaptation correct throughout both renders - criteria_checklist: - visual_quality: - score: 23 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 6 - max: 8 - passed: true - comment: Most sizes explicitly set (suptitle 24pt, panel titles 20pt); child - labels at 11pt and legend at 14pt fall below recommended 16pt minimum - - id: VQ-02 - name: No Overlap - score: 3 - max: 6 - passed: false - comment: Bottom treemap category labels clipped; narrow column child labels - truncated ('DevOp', 'Prototyp'); sunburst outer labels crowded - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: Treemap rectangles and sunburst wedges well-sized and visible; minor - clipping in narrow columns - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette CVD-safe; white text on solid fills provides adequate - contrast - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: Good dual-panel 16:9 canvas use; tight_layout applied; minor label - clipping at boundaries - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Suptitle in correct format; panel titles descriptive; subtitle informative - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Development first at #009E73; Okabe-Ito order correct; #FAF8F1/#1A1A17 - backgrounds; data colors identical across renders' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: FancyBboxPatch rounded corners, alpha layering creates depth; above - defaults but not publication-ready - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Both axes off, no grid, consistent theme backgrounds, tight_layout - applied - - id: DE-03 - name: Data Storytelling - score: 3 - max: 6 - passed: true - comment: Dual-view comparison enables size vs hierarchy storytelling; consistent - color coding aids cross-view tracing - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Both treemap and sunburst correctly implemented as static dual-panel - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Both hierarchy views present with consistent colors; interactive - toggle inherently unavailable in static library - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Correct hierarchical proportions in both views; 3-level hierarchy - mapped correctly - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title 'hierarchy-toggle-view · python · seaborn · anyplot.ai' exact - format; 4-category legend correct - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: 3-level hierarchy with varied proportions; both chart types demonstrate - hierarchical structure - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Software project budget allocation — realistic, neutral business - scenario - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: $1350K total with plausible sub-allocations; proportions realistic - code_quality: - score: 9 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear flow: imports → tokens → data → hierarchy build → plot → - save; no functions/classes' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Deterministic data — no random generation needed - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All 7 imports verified as used - - id: CQ-04 - name: Code Elegance - score: 1 - max: 2 - passed: true - comment: 'Slightly verbose: repeated parallel structures for L1/L2 in both - treemap and sunburst loops' - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png with dpi=300, bbox_inches='tight'; current - API - library_mastery: - score: 3 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 2 - max: 5 - passed: false - comment: Seaborn used only for sns.set_theme(); all visualization via matplotlib - FancyBboxPatch/Wedge; spec requires chart types seaborn doesn't natively - support - - id: LM-02 - name: Distinctive Features - score: 1 - max: 5 - passed: false - comment: No distinctive seaborn API features leveraged; visualization is entirely - matplotlib-based - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - subplots - - patches - - annotations - - custom-legend - patterns: - - iteration-over-groups - - explicit-figure - dataprep: [] - styling: - - minimal-chrome - - alpha-blending - - edge-highlighting diff --git a/plots/hierarchy-toggle-view/specification.md b/plots/hierarchy-toggle-view/specification.md deleted file mode 100644 index 7eac3be8cf..0000000000 --- a/plots/hierarchy-toggle-view/specification.md +++ /dev/null @@ -1,28 +0,0 @@ -# hierarchy-toggle-view: Interactive Treemap-Sunburst Toggle View - -## Description - -An interactive hierarchical visualization that allows users to toggle between treemap and sunburst representations of the same data. The treemap displays hierarchy as nested rectangles with area proportional to values, while the sunburst shows the same structure as concentric radial segments. This dual-view approach enables users to explore hierarchical data from different perspectives: rectangular layouts excel at precise size comparison, while radial layouts better reveal hierarchical depth and parent-child relationships. - -## Applications - -- File system explorers allowing users to switch between space-efficient treemap and relationship-focused sunburst views -- Budget visualization dashboards where analysts toggle views to compare allocations (treemap) vs drill down hierarchy (sunburst) -- Organizational structure tools that switch between compact department overview and radial reporting chain visualization -- Portfolio analysis platforms toggling between market cap comparison and sector hierarchy exploration - -## Data - -- `id` (string) - unique identifier for each node -- `parent` (string) - parent node id (null/empty for root) -- `label` (string) - display name for the node -- `value` (numeric) - size/magnitude determining area (treemap) or angle (sunburst) -- Size: 15-60 nodes across 2-4 hierarchy levels - -## Notes - -- Implement a clear toggle button or switch control to change between views -- Maintain consistent colors across both views so nodes are easily identifiable -- Preserve selection/hover state when switching between views if possible -- Smooth animated transitions between layouts enhance user experience -- Both views should fit within the same container dimensions diff --git a/plots/hierarchy-toggle-view/specification.yaml b/plots/hierarchy-toggle-view/specification.yaml deleted file mode 100644 index 54bea878c1..0000000000 --- a/plots/hierarchy-toggle-view/specification.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# Specification-level metadata for hierarchy-toggle-view -# Auto-synced to PostgreSQL on push to main - -spec_id: hierarchy-toggle-view -title: Interactive Treemap-Sunburst Toggle View - -# Specification tracking -created: 2026-01-11T09:15:24Z -updated: null -issue: 3697 -suggested: MarkusNeusinger - -# Classification tags (applies to all library implementations) -# See docs/reference/tagging-system.md for detailed guidelines -tags: - plot_type: - - treemap - - sunburst - data_type: - - hierarchical - - numeric - domain: - - general - - business - features: - - interactive - - animated - - comparison diff --git a/plots/line-navigator/implementations/julia/makie.jl b/plots/line-navigator/implementations/julia/makie.jl deleted file mode 100644 index 9b5ff1db1f..0000000000 --- a/plots/line-navigator/implementations/julia/makie.jl +++ /dev/null @@ -1,147 +0,0 @@ -# anyplot.ai -# line-navigator: Line Chart with Mini Navigator -# Library: makie 0.22.10 | Julia 1.11.9 -# Quality: 88/100 | Created: 2026-05-27 - -using CairoMakie -using Colors -using Random - -Random.seed!(42) - -# Theme tokens -const THEME = get(ENV, "ANYPLOT_THEME", "light") -const PAGE_BG = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17" -const ELEVATED_BG = THEME == "light" ? colorant"#FFFDF6" : colorant"#242420" -const INK = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8" -const INK_SOFT = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0" -const INK_MUTED = THEME == "light" ? colorant"#6B6A63" : colorant"#A8A79F" - -const IMPRINT_PALETTE = [ - colorant"#009E73", - colorant"#C475FD", - colorant"#4467A3", - colorant"#BD8233", - colorant"#AE3030", - colorant"#2ABCCD", - colorant"#954477", - colorant"#99B314", -] - -# Data: daily temperature sensor readings over 3 years (1095 days) -const N_DAYS = 1095 -t = collect(0:N_DAYS-1) -seasonal = 10.0 .* sin.(2π .* t ./ 365.0 .- 1.4) .+ 3.5 .* sin.(4π .* t ./ 365.0) -temperature = 17.0 .+ seasonal .+ 1.5 .* randn(N_DAYS) - -# Selection window: 90-day summer peak in year 2 -const WIN_START = 450 -const WIN_END = 540 -detail_t = t[WIN_START+1:WIN_END+1] -detail_vals = temperature[WIN_START+1:WIN_END+1] - -# Title — at 59 chars, no scaling needed (under 67 baseline) -const TITLE_STR = "Sensor Data · line-navigator · julia · makie · anyplot.ai" -const TITLE_SIZE = length(TITLE_STR) > 67 ? max(round(Int, 20 * 67 / length(TITLE_STR)), 13) : 20 - -# Grid colour helper (15% alpha) -const GRID_COLOR = RGBAf(red(INK), green(INK), blue(INK), 0.15f0) - -# Figure: size=(1600,900) × px_per_unit=2 → 3200×1800 px -fig = Figure( - size = (1600, 900), - fontsize = 14, - backgroundcolor = PAGE_BG, -) - -# Main detail axis — zoomed into selected window -ax_main = Axis( - fig[1, 1]; - title = TITLE_STR, - titlesize = TITLE_SIZE, - titlecolor = INK, - xlabel = "Day", - xlabelsize = 11, - xlabelcolor = INK, - ylabel = "Temperature (°C)", - ylabelsize = 14, - ylabelcolor = INK, - xticklabelsize = 11, - yticklabelsize = 11, - xticklabelcolor = INK_SOFT, - yticklabelcolor = INK_SOFT, - xtickcolor = INK_SOFT, - ytickcolor = INK_SOFT, - backgroundcolor = PAGE_BG, - topspinevisible = false, - rightspinevisible = false, - leftspinecolor = INK_SOFT, - bottomspinecolor = INK_SOFT, - xgridvisible = false, - ygridcolor = GRID_COLOR, - yminorgridvisible = false, - xminorgridvisible = false, -) - -# Navigator axis — full time series overview with elevated background for visual hierarchy -ax_nav = Axis( - fig[2, 1]; - xlabel = "Day", - xlabelsize = 11, - xlabelcolor = INK, - xticklabelsize = 9, - yticklabelsize = 9, - xticklabelcolor = INK_SOFT, - yticklabelcolor = INK_SOFT, - xtickcolor = INK_SOFT, - ytickcolor = INK_SOFT, - backgroundcolor = ELEVATED_BG, - topspinevisible = false, - rightspinevisible = false, - leftspinecolor = INK_SOFT, - bottomspinecolor = INK_SOFT, - xgridvisible = false, - ygridvisible = false, - yticklabelsvisible = false, -) - -# Row proportions: detail 78%, navigator 22% (set after axes are created) -rowsize!(fig.layout, 1, Relative(0.78)) -rowsize!(fig.layout, 2, Relative(0.22)) -rowgap!(fig.layout, 1, 8) - -# Detail view: selected window -brand = IMPRINT_PALETTE[1] -lines!(ax_main, detail_t, detail_vals; color = brand, linewidth = 2.0f0) - -# Annotation: selected range with insight context -detail_ymin, detail_ymax = extrema(detail_vals) -label_x = Float32(detail_t[1] + 3) -label_y = Float32(detail_ymin + 0.87 * (detail_ymax - detail_ymin)) -text!(ax_main, - "Days $(WIN_START)–$(WIN_END) of $(N_DAYS) · Summer temperature peak"; - position = Point2f(label_x, label_y), - align = (:left, :center), - color = INK_MUTED, - fontsize = 12) - -# Navigator: full series at reduced opacity with matching linewidth for visual consistency -lines!(ax_nav, t, temperature; - color = RGBAf(red(brand), green(brand), blue(brand), 0.65f0), - linewidth = 2.0f0) - -# Selection highlight — filled polygon over the selected range -nav_ylo, nav_yhi = extrema(temperature) -nav_pad = (nav_yhi - nav_ylo) * 0.08 -sel_verts = Point2f[ - (WIN_START, nav_ylo - nav_pad), (WIN_END, nav_ylo - nav_pad), - (WIN_END, nav_yhi + nav_pad), (WIN_START, nav_yhi + nav_pad), -] -poly!(ax_nav, sel_verts; - color = RGBAf(red(brand), green(brand), blue(brand), 0.30f0), - strokewidth = 0) - -# Selection edge lines -vlines!(ax_nav, [WIN_START, WIN_END]; color = brand, linewidth = 2.0f0) - -save("plot-$(THEME).png", fig; px_per_unit = 2) diff --git a/plots/line-navigator/implementations/python/altair.py b/plots/line-navigator/implementations/python/altair.py deleted file mode 100644 index 6f76902b73..0000000000 --- a/plots/line-navigator/implementations/python/altair.py +++ /dev/null @@ -1,145 +0,0 @@ -""" anyplot.ai -line-navigator: Line Chart with Mini Navigator -Library: altair 6.1.0 | Python 3.13.13 -Quality: 93/100 | Updated: 2026-05-27 -""" - -import os -import sys - - -# Prevent this file (altair.py) from shadowing the installed altair package. -# Python adds the script's directory as sys.path[0] when running a .py file; -# removing it (by absolute path match) lets site-packages take precedence. -_this_dir = os.path.dirname(os.path.abspath(__file__)) -sys.path = [p for p in sys.path if os.path.abspath(p or ".") != _this_dir] - -import altair as alt -import numpy as np -import pandas as pd -from PIL import Image - - -# 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" -RULE = "rgba(26,26,23,0.15)" if THEME == "light" else "rgba(240,239,232,0.15)" -BRAND = "#009E73" # Imprint palette position 1 — ALWAYS first series -HIGHLIGHT = "#DDCC77" # amber — selection window indicator - -# Data — daily sensor readings over 3 years (1095 data points) -np.random.seed(42) -dates = pd.date_range("2022-01-01", periods=1095, freq="D") -trend = np.linspace(20, 35, 1095) -seasonal = 8 * np.sin(2 * np.pi * np.arange(1095) / 365) -weekly = 2 * np.sin(2 * np.pi * np.arange(1095) / 7) -noise = np.random.randn(1095) * 2 -values = trend + seasonal + weekly + noise -df = pd.DataFrame({"date": dates, "value": values}) - -title = "line-navigator · python · altair · anyplot.ai" -title_fontsize = 16 # len=45 < 67 baseline, no shrink needed - -# Initial selection covers first half of 2023 to demonstrate navigation -start_ms = int(pd.Timestamp("2023-01-01").timestamp() * 1000) -end_ms = int(pd.Timestamp("2023-07-01").timestamp() * 1000) -brush = alt.selection_interval(encodings=["x"], value={"x": [start_ms, end_ms]}) - -NAV_H = 40 # navigator height (18% of main 220px — within spec's 15-20% guideline) - -# Main chart — detail view linked to brush selection -main_chart = ( - alt.Chart(df) - .mark_line(color=BRAND, strokeWidth=2) - .encode( - x=alt.X("date:T", title="Date", scale=alt.Scale(domain=brush)), - y=alt.Y("value:Q", title="Sensor Reading (°C)", scale=alt.Scale(zero=False)), - tooltip=[ - alt.Tooltip("date:T", title="Date", format="%Y-%m-%d"), - alt.Tooltip("value:Q", title="Value", format=".1f"), - ], - ) - .properties(width=620, height=220, title=alt.Title(title, fontSize=title_fontsize)) - .interactive() -) - -# Navigator chart — year-only x-axis labels reduce redundancy with main chart -navigator_line = ( - alt.Chart(df) - .mark_line(color=BRAND, strokeWidth=1) - .encode( - x=alt.X("date:T", title="", axis=alt.Axis(tickCount="year", format="%Y")), - y=alt.Y("value:Q", title="", axis=alt.Axis(labels=False, ticks=False), scale=alt.Scale(zero=False)), - ) - .properties(width=620, height=NAV_H) - .add_params(brush) -) - -# Amber highlight fills from data line down to chart bottom for selected range -selection_highlight = ( - alt.Chart(df) - .mark_area(color=HIGHLIGHT, opacity=0.35) - .encode(x=alt.X("date:T"), y=alt.Y("value:Q", scale=alt.Scale(zero=False)), y2=alt.value(NAV_H)) - .transform_filter(brush) -) - -# Date range text labels showing selected window start and end dates -range_start_label = ( - alt.Chart(df) - .transform_filter(brush) - .transform_aggregate(min_date="min(date)") - .mark_text(align="left", baseline="top", fontSize=11, color=INK_SOFT) - .encode(text=alt.Text("min_date:T", format="%Y-%m-%d"), x=alt.value(4), y=alt.value(2)) -) - -range_end_label = ( - alt.Chart(df) - .transform_filter(brush) - .transform_aggregate(max_date="max(date)") - .mark_text(align="right", baseline="top", fontSize=11, color=INK_SOFT) - .encode(text=alt.Text("max_date:T", format="%Y-%m-%d"), x=alt.value(616), y=alt.value(2)) -) - -navigator = alt.layer(navigator_line, selection_highlight, range_start_label, range_end_label).properties( - width=620, height=NAV_H -) - -# Combine main chart and navigator vertically -combined = ( - alt.vconcat(main_chart, navigator, spacing=10) - .properties(background=PAGE_BG) - .configure_view(fill=PAGE_BG, strokeWidth=0) - .configure_axis( - labelFontSize=10, - titleFontSize=12, - gridColor=RULE, - gridOpacity=1.0, - domainColor=INK_SOFT, - tickColor=INK_SOFT, - labelColor=INK_SOFT, - titleColor=INK, - ) - .configure_title(color=INK) -) - -# Save PNG then pad to exact 3200×1800 target -combined.save(f"plot-{THEME}.png", scale_factor=4.0) - -TW, TH = 3200, 1800 -_img = Image.open(f"plot-{THEME}.png").convert("RGB") -_w, _h = _img.size -if _w > TW or _h > TH: - raise SystemExit( - f"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. " - f"Shrink chart .properties(width=, height=) values and re-render." - ) -if _w < TW or _h < TH: - _canvas = Image.new("RGB", (TW, TH), PAGE_BG) - _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2)) - _canvas.save(f"plot-{THEME}.png") - -# Save HTML (interactive version) -combined.save(f"plot-{THEME}.html") diff --git a/plots/line-navigator/implementations/python/bokeh.py b/plots/line-navigator/implementations/python/bokeh.py deleted file mode 100644 index f8d6f30ece..0000000000 --- a/plots/line-navigator/implementations/python/bokeh.py +++ /dev/null @@ -1,150 +0,0 @@ -""" anyplot.ai -line-navigator: Line Chart with Mini Navigator -Library: bokeh 3.9.0 | Python 3.13.13 -Quality: 90/100 | Updated: 2026-05-27 -""" - -import io -import os -import time -from pathlib import Path - -import numpy as np -import pandas as pd -from bokeh.io import output_file, save -from bokeh.layouts import column -from bokeh.models import ColumnDataSource, HoverTool, RangeTool -from bokeh.plotting import figure -from PIL import Image -from selenium import webdriver -from selenium.webdriver.chrome.options import Options - - -# 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" -BRAND = "#009E73" # Imprint palette position 1 — ALWAYS first series -ANYPLOT_AMBER = "#DDCC77" # selection highlight (semantic anchor) - -# Data — daily sensor readings over 3 years (1095 points) -np.random.seed(42) -n_points = 1095 -dates = pd.date_range(start="2022-01-01", periods=n_points, freq="D") - -trend = np.linspace(50, 80, n_points) -seasonal = 15 * np.sin(2 * np.pi * np.arange(n_points) / 365) -noise = np.random.randn(n_points) * 5 -values = trend + seasonal + noise - -source = ColumnDataSource(data={"date": dates, "value": values}) - -title = "line-navigator · python · bokeh · anyplot.ai" - -# Main chart — shows selected range in detail -main_plot = figure( - width=3200, - height=1500, - title=title, - x_axis_type="datetime", - x_axis_label="Date", - y_axis_label="Sensor Reading (units)", - x_range=(dates[700], dates[900]), - toolbar_location=None, - min_border_bottom=160, - min_border_left=180, - min_border_top=110, - min_border_right=50, -) - -main_plot.line("date", "value", source=source, line_width=4, line_color=BRAND, alpha=0.9) - -hover = HoverTool(tooltips=[("Date", "@date{%F}"), ("Reading", "@value{0.1f} units")], formatters={"@date": "datetime"}) -main_plot.add_tools(hover) - -# Theme-adaptive chrome — main plot -main_plot.background_fill_color = PAGE_BG -main_plot.border_fill_color = PAGE_BG -main_plot.outline_line_color = None -main_plot.title.text_font_size = "65pt" -main_plot.title.text_color = INK -main_plot.xaxis.axis_label_text_font_size = "42pt" -main_plot.yaxis.axis_label_text_font_size = "42pt" -main_plot.xaxis.axis_label_text_color = INK -main_plot.yaxis.axis_label_text_color = INK -main_plot.xaxis.major_label_text_font_size = "34pt" -main_plot.yaxis.major_label_text_font_size = "34pt" -main_plot.xaxis.major_label_text_color = INK_SOFT -main_plot.yaxis.major_label_text_color = INK_SOFT -main_plot.xaxis.axis_line_color = INK_SOFT -main_plot.yaxis.axis_line_color = INK_SOFT -main_plot.xaxis.major_tick_line_color = INK_SOFT -main_plot.yaxis.major_tick_line_color = INK_SOFT -main_plot.ygrid.grid_line_color = INK -main_plot.ygrid.grid_line_alpha = 0.15 - -# Navigator — shows full data extent at reduced scale -navigator = figure( - width=3200, - height=300, - x_axis_type="datetime", - y_axis_type=None, - y_range=main_plot.y_range, - toolbar_location=None, - min_border_bottom=100, - min_border_left=180, - min_border_top=30, - min_border_right=50, -) - -navigator.line("date", "value", source=source, line_width=2, line_color=BRAND, alpha=0.6) - -range_tool = RangeTool(x_range=main_plot.x_range) -range_tool.overlay.fill_color = ANYPLOT_AMBER -range_tool.overlay.fill_alpha = 0.3 -navigator.add_tools(range_tool) - -# Theme-adaptive chrome — navigator -navigator.background_fill_color = PAGE_BG -navigator.border_fill_color = PAGE_BG -navigator.outline_line_color = None -navigator.xaxis.major_label_text_font_size = "28pt" -navigator.xaxis.major_label_text_color = INK_SOFT -navigator.xaxis.axis_line_color = INK_SOFT -navigator.xaxis.major_tick_line_color = INK_SOFT -navigator.ygrid.grid_line_color = INK -navigator.ygrid.grid_line_alpha = 0.15 - -# Layout — total canvas 3200 × 1800 -layout = column(main_plot, navigator, sizing_mode="fixed", spacing=0, width=3200, height=1800) - -# Save HTML (required catalog artifact) -output_file(f"plot-{THEME}.html") -save(layout) - -# Screenshot with headless Chrome — Selenium Manager resolves the driver. -# Chrome's inner viewport is ~139px shorter than the requested window height, -# so request a larger window and crop the PNG to the exact target dimensions. -W, H = 3200, 1800 -WIN_W, WIN_H = W, H + 200 # overshoot so viewport comfortably contains W×H content -opts = Options() -for arg in ( - "--headless=new", - "--no-sandbox", - "--disable-dev-shm-usage", - "--disable-gpu", - f"--window-size={WIN_W},{WIN_H}", - "--hide-scrollbars", - "--force-device-scale-factor=1", -): - opts.add_argument(arg) -driver = webdriver.Chrome(options=opts) -driver.set_window_size(WIN_W, WIN_H) -driver.get(f"file://{Path(f'plot-{THEME}.html').resolve()}") -time.sleep(3) -png_bytes = driver.get_screenshot_as_png() -driver.quit() -img = Image.open(io.BytesIO(png_bytes)).crop((0, 0, W, H)) -img.save(f"plot-{THEME}.png") diff --git a/plots/line-navigator/implementations/python/letsplot.py b/plots/line-navigator/implementations/python/letsplot.py deleted file mode 100644 index 75b3737214..0000000000 --- a/plots/line-navigator/implementations/python/letsplot.py +++ /dev/null @@ -1,185 +0,0 @@ -""" anyplot.ai -line-navigator: Line Chart with Mini Navigator -Library: letsplot 4.10.1 | Python 3.13.13 -Quality: 87/100 | Updated: 2026-05-27 -""" - -import os - -import numpy as np -import pandas as pd -from lets_plot import ( - LetsPlot, - aes, - element_blank, - element_line, - element_rect, - element_text, - geom_area, - geom_line, - geom_rect, - geom_text, - geom_vline, - gggrid, - ggplot, - ggsize, - ggtitle, - labs, - layer_tooltips, - scale_x_datetime, - scale_y_continuous, - theme, - theme_minimal, -) -from lets_plot.export import ggsave - - -LetsPlot.setup_html() - -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" -RULE = "rgba(26,26,23,0.15)" if THEME == "light" else "rgba(240,239,232,0.15)" -RULE_FAINT = "rgba(26,26,23,0.08)" if THEME == "light" else "rgba(240,239,232,0.08)" - -IMPRINT_PALETTE = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477", "#99B314"] -BRAND = IMPRINT_PALETTE[0] - -# Data — 3 years of daily temperature sensor readings -np.random.seed(42) -n_days = 1095 -dates = pd.date_range(start="2023-01-01", periods=n_days, freq="D") - -trend = np.linspace(20, 35, n_days) -seasonality = 10 * np.sin(2 * np.pi * np.arange(n_days) / 365) -noise = np.random.normal(0, 2, n_days) -temperature = trend + seasonality + noise - -df_full = pd.DataFrame({"date": dates, "temperature": temperature}) - -# Selected range — last 6 months shown in detail -selected_days = 180 -range_start_idx = len(df_full) - selected_days -df_selected = df_full.iloc[range_start_idx:].copy() - -# Y-axis limits for main chart -main_y_min = df_selected["temperature"].min() -main_y_max = df_selected["temperature"].max() -main_y_range = main_y_max - main_y_min -main_y_lower = main_y_min - main_y_range * 0.05 -main_y_upper = main_y_max + main_y_range * 0.12 - -# Y-axis limits for navigator (full data extent) -nav_y_min = df_full["temperature"].min() - 2 -nav_y_max = df_full["temperature"].max() + 2 - -# Date range for selection window -select_xmin = df_full["date"].iloc[range_start_idx] -select_xmax = df_full["date"].iloc[-1] -start_label = select_xmin.strftime("%b %d, %Y") -end_label = select_xmax.strftime("%b %d, %Y") - -range_label_df = pd.DataFrame( - { - "x": [select_xmin + (select_xmax - select_xmin) / 2], - "y": [main_y_upper - main_y_range * 0.06], - "label": [f"Selected: {start_label} – {end_label}"], - } -) - -# Navigator selection rectangle (highlights the visible range) -selection_rect = pd.DataFrame({"xmin": [select_xmin], "xmax": [select_xmax], "ymin": [nav_y_min], "ymax": [nav_y_max]}) - -# Tooltips for main chart -main_tooltips = ( - layer_tooltips() - .title("Temperature Reading") - .line("Date: @date") - .line("Temp: @{temperature}°C") - .format("temperature", ".1f") -) - -# Shared theme settings -main_theme = theme_minimal() + theme( - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - panel_background=element_rect(fill=PAGE_BG), - axis_title=element_text(size=12, color=INK), - axis_text=element_text(size=10, color=INK_SOFT), - panel_grid_major=element_line(color=RULE, size=0.5), - panel_grid_minor=element_blank(), - axis_line=element_line(color=INK_SOFT), -) - -nav_theme = theme_minimal() + theme( - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - panel_background=element_rect(fill=ELEVATED_BG), - axis_title=element_text(size=10, color=INK), - axis_text=element_text(size=8, color=INK_SOFT), - panel_grid_major=element_line(color=RULE_FAINT, size=0.3), - panel_grid_minor=element_blank(), - axis_line=element_line(color=INK_SOFT), -) - -# Main chart — detailed view of the selected range -main_chart = ( - ggplot(df_selected, aes(x="date", y="temperature")) - + geom_area(fill=BRAND, alpha=0.15) - + geom_line(color=BRAND, size=1.5, tooltips=main_tooltips) - + geom_text( - aes(x="x", y="y", label="label"), data=range_label_df, inherit_aes=False, size=4.5, color=BRAND, fontface="bold" - ) - + labs(x="", y="Temperature (°C)") - + scale_x_datetime(format="%b %Y") - + scale_y_continuous(limits=[main_y_lower, main_y_upper]) - + main_theme - + ggsize(800, 360) -) - -# Yearly break points for navigator x-axis — one label per calendar year -yearly_breaks = pd.date_range("2023-01-01", "2026-01-01", freq="YS").tolist() - -# Resize-handle indicators at left/right edges of selection window -handle_df = pd.DataFrame({"x": [select_xmin, select_xmax]}) - -# Navigator mini chart — full data overview with selection window -navigator = ( - ggplot(df_full, aes(x="date", y="temperature")) - + geom_rect( - aes(xmin="xmin", xmax="xmax", ymin="ymin", ymax="ymax"), - data=selection_rect, - inherit_aes=False, - fill=BRAND, - alpha=0.2, - color=BRAND, - linetype="solid", - size=2.5, - ) - + geom_vline(aes(xintercept="x"), data=handle_df, inherit_aes=False, color=BRAND, size=2) - + geom_area(fill=BRAND, alpha=0.12, tooltips="none") - + geom_line(color=BRAND, size=0.8, tooltips="none") - + labs(x="Date", y="") - + scale_x_datetime(breaks=yearly_breaks, format="%Y") - + scale_y_continuous(limits=[nav_y_min, nav_y_max]) - + nav_theme - + ggsize(800, 90) -) - -# Combine main chart and navigator — navigator is ~18% of main chart height -combined = gggrid([main_chart, navigator], ncol=1, heights=[5.5, 1], align=True) - -title = "line-navigator · python · letsplot · anyplot.ai" -combined = ( - combined - + ggtitle(title) - + ggsize(800, 450) - + theme( - plot_title=element_text(size=16, color=INK, face="bold"), - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - ) -) - -# Save PNG (scale 4x → 3200 × 1800 px) and interactive HTML -ggsave(combined, f"plot-{THEME}.png", path=".", scale=4) -ggsave(combined, f"plot-{THEME}.html", path=".") diff --git a/plots/line-navigator/implementations/python/matplotlib.py b/plots/line-navigator/implementations/python/matplotlib.py deleted file mode 100644 index f6e432fdbb..0000000000 --- a/plots/line-navigator/implementations/python/matplotlib.py +++ /dev/null @@ -1,130 +0,0 @@ -""" anyplot.ai -line-navigator: Line Chart with Mini Navigator -Library: matplotlib 3.10.9 | Python 3.13.13 -Quality: 88/100 | Updated: 2026-05-27 -""" - -import os - -import matplotlib.patches as mpatches -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd - - -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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -BRAND = "#009E73" -ANYPLOT_AMBER = "#DDCC77" - -# Data — daily temperature sensor readings over 3 years (1095 data points) -np.random.seed(42) -dates = pd.date_range("2022-01-01", periods=1095, freq="D") - -trend = np.linspace(20, 25, 1095) -seasonal = 8 * np.sin(2 * np.pi * np.arange(1095) / 365) -noise = np.random.randn(1095) * 1.5 -values = trend + seasonal + noise - -# Selected range shown in main chart (~4 months) -selected_start = 400 -selected_end = 520 - -fig, (ax_main, ax_nav) = plt.subplots(2, 1, figsize=(8, 4.5), dpi=400, height_ratios=[4, 1], sharex=False) -fig.patch.set_facecolor(PAGE_BG) -fig.subplots_adjust(left=0.08, right=0.97, top=0.91, bottom=0.12, hspace=0.30) - -# --- Main chart --- -ax_main.set_facecolor(PAGE_BG) -ax_main.plot(dates[selected_start:selected_end], values[selected_start:selected_end], linewidth=2.5, color=BRAND) -ax_main.fill_between(dates[selected_start:selected_end], values[selected_start:selected_end], alpha=0.15, color=BRAND) - -title = "line-navigator · python · matplotlib · anyplot.ai" -title_fontsize = max(8, round(12 * 67 / len(title))) if len(title) > 67 else 12 -ax_main.set_title(title, fontsize=title_fontsize, fontweight="medium", color=INK, pad=6) -ax_main.set_ylabel("Temperature (°C)", fontsize=10, color=INK) -ax_main.tick_params(axis="both", labelsize=8, colors=INK_SOFT, labelcolor=INK_SOFT) -ax_main.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK) -ax_main.spines["top"].set_visible(False) -ax_main.spines["right"].set_visible(False) -ax_main.spines["left"].set_color(INK_SOFT) -ax_main.spines["bottom"].set_color(INK_SOFT) - -ax_main.set_xlim(dates[selected_start], dates[selected_end]) -y_min = values[selected_start:selected_end].min() -y_max = values[selected_start:selected_end].max() -y_pad = (y_max - y_min) * 0.12 -ax_main.set_ylim(y_min - y_pad, y_max + y_pad) - -date_range_text = f"{dates[selected_start].strftime('%b %d, %Y')} — {dates[selected_end - 1].strftime('%b %d, %Y')}" -ax_main.annotate( - date_range_text, - xy=(0.99, 0.97), - xycoords="axes fraction", - fontsize=8, - ha="right", - va="top", - color=INK_SOFT, - bbox={"boxstyle": "round,pad=0.3", "facecolor": ELEVATED_BG, "edgecolor": INK_SOFT, "alpha": 0.9}, -) - -# --- Navigator chart --- -ax_nav.set_facecolor(PAGE_BG) -ax_nav.plot(dates, values, linewidth=1.0, color=BRAND, alpha=0.7) -ax_nav.fill_between(dates, values, alpha=0.13, color=BRAND) - -ax_nav.set_xlabel("Date", fontsize=10, color=INK) -ax_nav.tick_params(axis="both", labelsize=7, colors=INK_SOFT, labelcolor=INK_SOFT) -ax_nav.spines["top"].set_visible(False) -ax_nav.spines["right"].set_visible(False) -ax_nav.spines["left"].set_visible(False) -ax_nav.spines["bottom"].set_color(INK_SOFT) -ax_nav.set_yticks([]) - -ax_nav.set_xlim(dates[0], dates[-1]) -v_range = values.max() - values.min() -y_nav_min = values.min() - v_range * 0.08 -y_nav_max = values.max() + v_range * 0.08 -ax_nav.set_ylim(y_nav_min, y_nav_max) - -# Gray out non-selected regions -ax_nav.axvspan(dates[0], dates[selected_start], alpha=0.3, color=INK_MUTED) -ax_nav.axvspan(dates[selected_end], dates[-1], alpha=0.3, color=INK_MUTED) - -# Selection window rectangle -selection_rect = mpatches.Rectangle( - (dates[selected_start], y_nav_min), - dates[selected_end] - dates[selected_start], - y_nav_max - y_nav_min, - linewidth=1.5, - edgecolor=ANYPLOT_AMBER, - facecolor=ANYPLOT_AMBER, - alpha=0.25, - zorder=5, -) -ax_nav.add_patch(selection_rect) - -# Selection edge handles -ax_nav.axvline(dates[selected_start], color=ANYPLOT_AMBER, linewidth=2.0, zorder=6) -ax_nav.axvline(dates[selected_end], color=ANYPLOT_AMBER, linewidth=2.0, zorder=6) - -# Navigator label -ax_nav.annotate( - "Navigator", - xy=(0.01, 0.90), - xycoords="axes fraction", - fontsize=8, - ha="left", - va="top", - fontweight="bold", - color=INK_MUTED, -) - -plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG) -plt.close() diff --git a/plots/line-navigator/implementations/python/plotly.py b/plots/line-navigator/implementations/python/plotly.py deleted file mode 100644 index 542f4c3e7c..0000000000 --- a/plots/line-navigator/implementations/python/plotly.py +++ /dev/null @@ -1,136 +0,0 @@ -"""anyplot.ai -line-navigator: Line Chart with Mini Navigator -Library: plotly 6.7.0 | Python 3.13.13 -Quality: 87/100 | Updated: 2026-05-27 -""" - -import os -import sys - - -# Prevent this file from shadowing the installed plotly package -_script_dir = os.path.dirname(os.path.abspath(__file__)) -sys.path[:] = [p for p in sys.path if p and os.path.abspath(p) != _script_dir] - -import numpy as np -import pandas as pd -import plotly.graph_objects as go - - -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.15)" if THEME == "light" else "rgba(240,239,232,0.15)" -BRAND = "#009E73" -FILL_COLOR = "rgba(0,158,115,0.12)" if THEME == "light" else "rgba(0,158,115,0.15)" - -# Data - Daily temperature sensor data over 3 years (~1100 data points) -np.random.seed(42) -dates = pd.date_range("2022-01-01", periods=1100, freq="D") -days_of_year = np.arange(1100) % 365 -seasonal = 15 * np.sin(2 * np.pi * days_of_year / 365) -trend = np.linspace(0, 3, 1100) -noise = np.random.randn(1100) * 2 -values = 20 + seasonal + trend + noise - -df = pd.DataFrame({"date": dates, "value": values}) - -title = "Temperature Sensor Data · line-navigator · python · plotly · anyplot.ai" -n = len(title) -ratio = 67 / n if n > 67 else 1.0 -title_fontsize = max(11, round(16 * ratio)) - -# Plot -fig = go.Figure() - -fig.add_trace( - go.Scatter( - x=df["date"], - y=df["value"], - mode="lines", - name="Temperature", - line={"color": BRAND, "width": 2}, - fill="tozeroy", - fillcolor=FILL_COLOR, - hovertemplate="%{x|%Y-%m-%d}
%{y:.1f}°C", - showlegend=False, - ) -) - -fig.update_layout( - autosize=False, - paper_bgcolor=PAGE_BG, - plot_bgcolor=PAGE_BG, - font={"color": INK}, - title={"text": title, "font": {"size": title_fontsize, "color": INK}, "x": 0.5, "xanchor": "center"}, - xaxis={ - "title": {"text": "Date", "font": {"size": 12, "color": INK}}, - "tickfont": {"size": 10, "color": INK_SOFT}, - "showgrid": False, - "showline": False, - "linecolor": INK_SOFT, - "zerolinecolor": INK_SOFT, - "showspikes": True, - "spikemode": "across", - "spikesnap": "cursor", - "spikethickness": 1, - "spikecolor": INK_SOFT, - "spikedash": "dot", - "rangeslider": { - "visible": True, - "thickness": 0.15, - "bgcolor": ELEVATED_BG, - "bordercolor": INK_SOFT, - "borderwidth": 1, - }, - "rangeselector": { - "buttons": [ - {"count": 1, "label": "1M", "step": "month", "stepmode": "backward"}, - {"count": 3, "label": "3M", "step": "month", "stepmode": "backward"}, - {"count": 6, "label": "6M", "step": "month", "stepmode": "backward"}, - {"count": 1, "label": "1Y", "step": "year", "stepmode": "backward"}, - {"step": "all", "label": "All"}, - ], - "font": {"size": 10, "color": INK}, - "bgcolor": ELEVATED_BG, - "activecolor": BRAND, - "bordercolor": INK_SOFT, - "borderwidth": 1, - "x": 0, - "y": 1.08, - }, - "type": "date", - }, - yaxis={ - "title": {"text": "Temperature (°C)", "font": {"size": 12, "color": INK}}, - "tickfont": {"size": 10, "color": INK_SOFT}, - "gridcolor": GRID, - "showline": False, - "zeroline": False, - "showspikes": True, - "spikemode": "across", - "spikesnap": "cursor", - "spikethickness": 1, - "spikecolor": INK_SOFT, - "spikedash": "dot", - }, - hovermode="x unified", - margin={"l": 80, "r": 40, "t": 100, "b": 60}, -) - -fig.add_hline( - y=0, - line_dash="dot", - line_color=INK_SOFT, - line_width=1, - annotation_text="Freezing Point", - annotation_font_size=9, - annotation_font_color=INK_SOFT, - annotation_position="bottom right", -) - -# Save -fig.write_image(f"plot-{THEME}.png", width=800, height=450, scale=4) -fig.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn") diff --git a/plots/line-navigator/implementations/python/plotnine.py b/plots/line-navigator/implementations/python/plotnine.py deleted file mode 100644 index 8df7f223c2..0000000000 --- a/plots/line-navigator/implementations/python/plotnine.py +++ /dev/null @@ -1,150 +0,0 @@ -""" anyplot.ai -line-navigator: Line Chart with Mini Navigator -Library: plotnine 0.15.4 | Python 3.13.13 -Quality: 83/100 | Updated: 2026-05-27 -""" - -import os - -import numpy as np -import pandas as pd -from plotnine import ( - aes, - annotate, - element_blank, - element_line, - element_rect, - element_text, - geom_line, - geom_rect, - geom_vline, - ggplot, - labs, - scale_x_datetime, - scale_y_continuous, - theme, - theme_minimal, -) - - -# 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" -BRAND = "#009E73" # Imprint palette position 1 — ALWAYS first series - -# Data - 3 years of daily industrial temperature sensor readings (1100 data points) -np.random.seed(42) -n_days = 1100 -dates = pd.date_range("2021-01-01", periods=n_days, freq="D") - -day_of_year = np.arange(n_days) % 365 -seasonal = 20 * np.sin(2 * np.pi * day_of_year / 365) -trend = np.linspace(0, 8, n_days) -noise = np.random.normal(0, 3, n_days) -anomalies = np.zeros(n_days) -for idx in [150, 420, 680, 950]: - if idx < n_days: - anomalies[idx : idx + 5] = np.random.uniform(8, 15, min(5, n_days - idx)) - -value = 50 + seasonal + trend + noise + anomalies -df = pd.DataFrame({"date": dates, "value": value}) - -# Selected range for detail view (~4 months) -range_start = pd.Timestamp("2022-06-01") -range_end = pd.Timestamp("2022-10-15") -df_detail = df[(df["date"] >= range_start) & (df["date"] <= range_end)].copy() -range_highlight = pd.DataFrame({"xmin": [range_start], "xmax": [range_end]}) -nav_y_min = df["value"].min() -nav_y_max = df["value"].max() - -# Title (47 chars < 67 baseline → ratio = 1.0, fontsize = 12pt) -title_str = "line-navigator · python · plotnine · anyplot.ai" - -# Shared anyplot theme (theme-adaptive chrome) -shared_theme = theme_minimal() + theme( - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - panel_background=element_rect(fill=PAGE_BG), - panel_border=element_blank(), - panel_grid_major=element_line(color=INK, size=0.3, alpha=0.15), - panel_grid_minor=element_blank(), - axis_title=element_text(color=INK, size=10), - axis_text=element_text(color=INK_SOFT, size=8), - plot_title=element_text(color=INK, size=12), - legend_text=element_text(color=INK_SOFT, size=8), - text=element_text(color=INK), -) - -# Detail view - main chart showing selected range in full resolution -p_detail = ( - ggplot(df_detail, aes(x="date", y="value")) - + geom_line(color=BRAND, size=1.0) - + scale_x_datetime(date_labels="%d %b", date_breaks="2 weeks") - + scale_y_continuous(labels=lambda x: [f"{v:.0f}°C" for v in x]) - + labs(x="", y="Temperature (°C)", title=title_str) - + shared_theme - + theme(axis_text_x=element_text(angle=45, ha="right", color=INK_SOFT)) -) - -# Navigator - mini chart with full data extent and selection highlight -p_navigator = ( - ggplot(df, aes(x="date", y="value")) - + geom_rect( - data=range_highlight, - mapping=aes(xmin="xmin", xmax="xmax"), - ymin=-np.inf, - ymax=np.inf, - fill=BRAND, - alpha=0.2, - inherit_aes=False, - ) - + geom_vline(data=range_highlight, mapping=aes(xintercept="xmin"), color=BRAND, size=1.2, linetype="solid") - + geom_vline(data=range_highlight, mapping=aes(xintercept="xmax"), color=BRAND, size=1.2, linetype="solid") - + geom_line(color=BRAND, size=0.7, alpha=0.7) - + annotate( - "text", - x=range_start + (range_end - range_start) / 2, - y=nav_y_max - (nav_y_max - nav_y_min) * 0.12, - label=f"{range_start.strftime('%b %Y')} – {range_end.strftime('%b %Y')}", - size=3.5, - color=INK_SOFT, - ) - + scale_x_datetime(date_labels="%Y", date_breaks="1 year") - + scale_y_continuous(breaks=[30, 50, 70, 90], labels=lambda x: [f"{v:.0f}" for v in x]) - + labs(x="Date", y="", title="Overview") - + shared_theme - + theme( - plot_title=element_text(size=8, color=INK_SOFT), - axis_text=element_text(size=8, color=INK_SOFT), - axis_title=element_text(size=8, color=INK_SOFT), - panel_grid_major=element_blank(), - ) -) - -# Compose: detail on top, navigator below -grid = p_detail / p_navigator -fig = grid.draw() - -# Canvas: exactly 8 × 4.5 in @ 400 dpi = 3200 × 1800 px -fig.set_size_inches(8, 4.5) -fig.patch.set_facecolor(PAGE_BG) - -# Set panel heights: main chart ~82%, navigator ~18% of content area -axes = fig.axes -if len(axes) >= 2: - left = axes[0].get_position().x0 - width = axes[0].get_position().width - bottom_margin = 0.12 - top_margin = 0.06 - gap = 0.04 - available = 1.0 - bottom_margin - top_margin - gap - nav_ratio = 0.155 # nav_h / main_h ≈ 18% - nav_h = available * nav_ratio - main_h = available * (1 - nav_ratio) - axes[1].set_position([left, bottom_margin, width, nav_h]) - axes[0].set_position([left, bottom_margin + nav_h + gap, width, main_h]) - -# Save (no bbox_inches='tight' — preserves exact 3200 × 1800) -fig.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG) diff --git a/plots/line-navigator/implementations/python/pygal.py b/plots/line-navigator/implementations/python/pygal.py deleted file mode 100644 index a5dbf4504b..0000000000 --- a/plots/line-navigator/implementations/python/pygal.py +++ /dev/null @@ -1,283 +0,0 @@ -"""anyplot.ai -line-navigator: Line Chart with Mini Navigator -Library: pygal 3.1.0 | Python 3.13.13 -Quality: 86/100 | Updated: 2026-05-27 -""" - -import io -import os -import sys - - -# Remove current directory from sys.path to prevent self-import conflict -# (this file is named pygal.py, same as the library package) -_cwd = os.path.abspath(".") -sys.path = [p for p in sys.path if os.path.abspath(p or ".") != _cwd] - -import numpy as np -import pandas as pd -import pygal -from PIL import Image, ImageDraw, ImageFont -from pygal.style import Style - - -# 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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -IMPRINT_PALETTE = ("#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477", "#99B314") - - -def _hex_to_rgb(h): - h = h.lstrip("#") - return tuple(int(h[i : i + 2], 16) for i in (0, 2, 4)) - - -# Data — daily sensor readings over 3 years (1095 points) -np.random.seed(42) -dates = pd.date_range("2022-01-01", periods=1095, freq="D") -trend = np.linspace(50, 80, 1095) -seasonal = 15 * np.sin(2 * np.pi * np.arange(1095) / 365) -noise = np.random.normal(0, 5, 1095) -values = trend + seasonal + noise - -# Navigator selection: Feb 2023 – Aug 2023 -selection_start, selection_end = 400, 600 -start_label = dates[selection_start].strftime("%b %Y") -end_label = dates[selection_end - 1].strftime("%b %Y") -detail_label = f"{start_label} – {end_label}" - -TITLE = "line-navigator · python · pygal · anyplot.ai" - -# Main chart: 3200 × 1530 px (85 % of 1800) -main_style = Style( - background=PAGE_BG, - plot_background=PAGE_BG, - foreground=INK, - foreground_strong=INK, - foreground_subtle=INK_MUTED, - colors=IMPRINT_PALETTE, - title_font_size=66, - label_font_size=56, - major_label_font_size=44, - legend_font_size=44, - value_font_size=36, - stroke_width=3, -) - -main_chart = pygal.Line( - width=3200, - height=1530, - style=main_style, - title=TITLE, - x_title="Date", - y_title="Sensor Reading (mV)", - show_x_guides=False, - show_y_guides=True, - show_legend=True, - legend_at_bottom=True, - legend_box_size=24, - truncate_legend=-1, - x_label_rotation=45, - truncate_label=-1, - show_dots=False, - fill=False, - stroke_style={"width": 3}, - interpolate="cubic", - margin=80, -) - -selected_values = list(values[selection_start:selection_end]) -n_sel = selection_end - selection_start # 200 -selected_dates = [dates[i].strftime("%b %d") for i in range(selection_start, selection_end)] -step = max(1, n_sel // 6) # ~33 → ~6 evenly-spaced labels -main_x_labels = [selected_dates[i] if i % step == 0 else "" for i in range(n_sel)] -main_chart.x_labels = main_x_labels -main_chart.add(detail_label, selected_values) - -# Navigator: 3200 × 270 px (15 % of 1800; ~17.6 % of main height) -# Legend removed — selection range labelled via PIL annotation at top of pane -nav_style = Style( - background=ELEVATED_BG, - plot_background=ELEVATED_BG, - foreground=INK_SOFT, - foreground_strong=INK_SOFT, - foreground_subtle=INK_MUTED, - colors=IMPRINT_PALETTE, - title_font_size=40, - label_font_size=32, - major_label_font_size=28, - legend_font_size=30, - value_font_size=22, - stroke_width=2, - opacity=".75", -) - -nav_chart = pygal.Line( - width=3200, - height=270, - style=nav_style, - title=None, - x_title="", - y_title="", - show_x_guides=False, - show_y_guides=False, - show_y_labels=False, - show_legend=False, - show_dots=False, - fill=True, - stroke_style={"width": 1.5}, - margin=30, -) - -nav_x_labels = [dates[i].strftime("%Y") if i % 365 == 0 else "" for i in range(len(dates))] -nav_chart.x_labels = nav_x_labels -nav_chart.add("Full Dataset (2022–2024)", list(values)) - -selection_series = [None] * len(values) -for i in range(selection_start, selection_end): - selection_series[i] = values[i] -nav_chart.add(f"Selected: {detail_label}", selection_series) - -# Render both charts to PNG bytes -main_png = main_chart.render_to_png() -nav_png = nav_chart.render_to_png() -main_img = Image.open(io.BytesIO(main_png)) -nav_img = Image.open(io.BytesIO(nav_png)) - -# Resize to exact targets in case cairosvg introduces a pixel offset -TARGET_W, TARGET_MAIN_H, TARGET_NAV_H = 3200, 1530, 270 -if main_img.size != (TARGET_W, TARGET_MAIN_H): - main_img = main_img.resize((TARGET_W, TARGET_MAIN_H), Image.LANCZOS) -if nav_img.size != (TARGET_W, TARGET_NAV_H): - nav_img = nav_img.resize((TARGET_W, TARGET_NAV_H), Image.LANCZOS) - -# ── PIL post-processing ─────────────────────────────────────────────────────── - - -# Try to load DejaVu font; fall back to PIL built-in -def _load_font(size): - for path in ( - "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", - "/usr/share/fonts/dejavu/DejaVuSans.ttf", - "/usr/share/fonts/TTF/DejaVuSans.ttf", - ): - if os.path.exists(path): - return ImageFont.truetype(path, size) - return ImageFont.load_default() - - -ann_font = _load_font(48) -nav_font = _load_font(30) - -# Add date-range subtitle annotation to main chart -# Placed just below the pygal-rendered title (estimated at y≈80–150) -main_draw = ImageDraw.Draw(main_img) -ann_text = f"Viewing: {detail_label} · Full dataset: Jan 2022 – Dec 2024" -try: - bbox = main_draw.textbbox((0, 0), ann_text, font=ann_font) - text_w = bbox[2] - bbox[0] -except AttributeError: - text_w = len(ann_text) * 26 -x_ann = max(80, (TARGET_W - text_w) // 2) -main_draw.text((x_ann, 155), ann_text, fill=INK_SOFT, font=ann_font) - -# ── Navigator selection-window overlay ─────────────────────────────────────── -# Estimate the navigator's data-area x-bounds. -# pygal Line with margin=30, no y-labels: data region ≈ x[70, 3170]. -NAV_LEFT, NAV_RIGHT = 70, 3170 -NAV_PLOT_W = NAV_RIGHT - NAV_LEFT -N_TOTAL = len(values) - -x_sel_s = int(NAV_LEFT + (selection_start / (N_TOTAL - 1)) * NAV_PLOT_W) -x_sel_e = int(NAV_LEFT + ((selection_end - 1) / (N_TOTAL - 1)) * NAV_PLOT_W) - -ink_rgb = _hex_to_rgb(INK) -lavender_rgb = _hex_to_rgb("#C475FD") - -# Composite transparent overlay onto navigator for selection window -nav_rgba = nav_img.convert("RGBA") -overlay = Image.new("RGBA", nav_img.size, (0, 0, 0, 0)) -ov_draw = ImageDraw.Draw(overlay) - -# Shaded selection window -ov_draw.rectangle([(x_sel_s, 5), (x_sel_e, TARGET_NAV_H - 5)], fill=(*lavender_rgb, 40)) -# Solid boundary lines at selection start and end -for x in (x_sel_s, x_sel_e): - ov_draw.line([(x, 5), (x, TARGET_NAV_H - 5)], fill=(*ink_rgb, 200), width=3) - -nav_img = Image.alpha_composite(nav_rgba, overlay).convert("RGB") - -# Add selection range label inside the top margin of the navigator pane -nav_draw = ImageDraw.Draw(nav_img) -nav_label = f"Selected: {detail_label} · Full dataset: 2022–2024" -try: - nbbox = nav_draw.textbbox((0, 0), nav_label, font=nav_font) - nav_text_w = nbbox[2] - nbbox[0] -except AttributeError: - nav_text_w = len(nav_label) * 16 -x_nav_label = max(30, (TARGET_W - nav_text_w) // 2) -nav_draw.text((x_nav_label, 6), nav_label, fill=INK_SOFT, font=nav_font) - -# ───────────────────────────────────────────────────────────────────────────── - -combined = Image.new("RGB", (TARGET_W, TARGET_MAIN_H + TARGET_NAV_H), PAGE_BG) -combined.paste(main_img, (0, 0)) -combined.paste(nav_img, (0, TARGET_MAIN_H)) -combined.save(f"plot-{THEME}.png") - -# HTML output — interactive pygal charts in browser viewport dimensions -main_html_chart = pygal.Line( - width=1200, - height=540, - style=main_style, - title=TITLE, - x_title="Date", - y_title="Sensor Reading (mV)", - show_x_guides=False, - show_y_guides=True, - show_legend=True, - legend_at_bottom=True, - x_label_rotation=45, - truncate_label=-1, - show_dots=False, - fill=False, - stroke_style={"width": 3}, - interpolate="cubic", -) -main_html_chart.x_labels = main_x_labels -main_html_chart.add(detail_label, selected_values) - -nav_html_chart = pygal.Line( - width=1200, height=150, style=nav_style, title=None, show_legend=False, show_dots=False, fill=True -) -nav_html_chart.x_labels = nav_x_labels -nav_html_chart.add("Full Dataset (2022–2024)", list(values)) -nav_html_chart.add(f"Selected: {detail_label}", selection_series) - -html_content = f""" - - - line-navigator · python · pygal · anyplot.ai - - - -
- {main_html_chart.render(is_unicode=True)} - - {nav_html_chart.render(is_unicode=True)} -
- - -""" - -with open(f"plot-{THEME}.html", "w") as f: - f.write(html_content) diff --git a/plots/line-navigator/implementations/python/seaborn.py b/plots/line-navigator/implementations/python/seaborn.py deleted file mode 100644 index d6c5989cc6..0000000000 --- a/plots/line-navigator/implementations/python/seaborn.py +++ /dev/null @@ -1,150 +0,0 @@ -""" anyplot.ai -line-navigator: Line Chart with Mini Navigator -Library: seaborn 0.13.2 | Python 3.13.13 -Quality: 85/100 | Updated: 2026-05-27 -""" - -import os - -import matplotlib.dates as mdates -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd -import seaborn as sns - - -# 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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -BRAND = "#009E73" -SELECTION = "#BD8233" - -sns.set_theme( - context="notebook", - style="ticks", - rc={ - "figure.facecolor": PAGE_BG, - "axes.facecolor": PAGE_BG, - "axes.edgecolor": INK_SOFT, - "axes.labelcolor": INK, - "text.color": INK, - "xtick.color": INK_SOFT, - "ytick.color": INK_SOFT, - "grid.color": INK, - "grid.alpha": 0.15, - "legend.facecolor": ELEVATED_BG, - "legend.edgecolor": INK_SOFT, - }, -) - -# Data — production server CPU utilization over 2 years (daily averages) -np.random.seed(42) -dates = pd.date_range("2023-01-01", periods=730, freq="D") - -trend = np.linspace(0, 10, 730) -weekly_cycle = 6 * np.sin(2 * np.pi * np.arange(730) / 7) -seasonal = 5 * np.cos(2 * np.pi * np.arange(730) / 365) -spikes = np.random.exponential(scale=3, size=730) * (np.random.rand(730) < 0.05) -noise = np.random.randn(730) * 3 -cpu = np.clip(32 + trend + weekly_cycle + seasonal + spikes + noise, 8, 95) - -df = pd.DataFrame({"date": dates, "cpu_pct": cpu}) - -# Selection: 3-month window in the second year -start_idx, end_idx = 450, 545 -selected_start = dates[start_idx] -selected_end = dates[end_idx] -df_selected = df[(df["date"] >= selected_start) & (df["date"] <= selected_end)].copy() - -# Figure layout: main detail chart (80%) + slim navigator (20%) -fig = plt.figure(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG) -gs = fig.add_gridspec(2, 1, height_ratios=[4, 1], hspace=0.30, left=0.09, right=0.97, top=0.90, bottom=0.11) -ax_main = fig.add_subplot(gs[0]) -ax_nav = fig.add_subplot(gs[1]) - -# --- Main chart: detail view of selected range --- -ax_main.set_facecolor(PAGE_BG) - -sns.lineplot(data=df_selected, x="date", y="cpu_pct", ax=ax_main, linewidth=2.0, color=BRAND, zorder=2) - -# Soft fill under the line — adds visual depth (DE-01: intentional design choice) -ax_main.fill_between( - df_selected["date"], df_selected["cpu_pct"], df_selected["cpu_pct"].min() - 3, alpha=0.12, color=BRAND, zorder=1 -) - -# Mean reference line — visual anchor orienting the viewer -mean_cpu = df_selected["cpu_pct"].mean() -ax_main.axhline(mean_cpu, color=INK_SOFT, linewidth=0.8, linestyle="--", alpha=0.5, zorder=0) -ax_main.text( - df_selected["date"].iloc[-1], - mean_cpu + 0.7, - f"mean {mean_cpu:.0f}%", - fontsize=7, - color=INK_MUTED, - va="bottom", - ha="right", -) - -# Peak annotation — tells the CPU story (DE-03: data storytelling) -peak_idx = df_selected["cpu_pct"].idxmax() -peak_date = df_selected.loc[peak_idx, "date"] -peak_val = df_selected.loc[peak_idx, "cpu_pct"] -ax_main.annotate( - f"Peak {peak_val:.0f}%\n{peak_date.strftime('%b %d')}", - xy=(peak_date, peak_val), - xytext=(10, -28), - textcoords="offset points", - fontsize=7, - color=INK_SOFT, - arrowprops={"arrowstyle": "->", "color": INK_SOFT, "lw": 0.7}, -) - -ax_main.set_xlabel("") -ax_main.set_ylabel("CPU Usage (%)", fontsize=10, color=INK) -ax_main.set_title("line-navigator · python · seaborn · anyplot.ai", fontsize=12, fontweight="medium", color=INK) -ax_main.tick_params(axis="both", labelsize=8, colors=INK_SOFT) -ax_main.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK) -ax_main.set_axisbelow(True) -ax_main.xaxis.set_major_formatter(mdates.DateFormatter("%b %Y")) -ax_main.xaxis.set_major_locator(mdates.MonthLocator()) - -date_label = f"{selected_start.strftime('%b %Y')} – {selected_end.strftime('%b %Y')}" -ax_main.text(0.98, 0.97, date_label, transform=ax_main.transAxes, fontsize=8, color=INK_MUTED, va="top", ha="right") - -sns.despine(ax=ax_main) - -# --- Navigator: weekly aggregation with seaborn SD bands (LM-02: seaborn-distinctive) --- -# Group daily data into calendar weeks so sns.lineplot can compute mean ± SD per week -df_nav = df.copy() -df_nav["week"] = df_nav["date"].dt.to_period("W").dt.start_time - -ax_nav.set_facecolor(PAGE_BG) -sns.lineplot(data=df_nav, x="week", y="cpu_pct", ax=ax_nav, estimator="mean", errorbar="sd", linewidth=0.8, color=BRAND) - -ax_nav.set_xlim(dates[0], dates[-1]) - -# Shade non-selected regions -ax_nav.axvspan(dates[0], selected_start, color=INK_MUTED, alpha=0.25, zorder=3) -ax_nav.axvspan(selected_end, dates[-1], color=INK_MUTED, alpha=0.25, zorder=3) -# Subtle fill inside selection window -ax_nav.axvspan(selected_start, selected_end, color=BRAND, alpha=0.08, zorder=2) -# Selection edge markers (resize handles) -ax_nav.axvline(x=selected_start, color=SELECTION, linewidth=1.5, zorder=4) -ax_nav.axvline(x=selected_end, color=SELECTION, linewidth=1.5, zorder=4) - -ax_nav.set_xlabel("Date", fontsize=10, color=INK) -ax_nav.set_ylabel("") -ax_nav.tick_params(axis="x", labelsize=7, colors=INK_SOFT) -ax_nav.set_yticks([]) -ax_nav.xaxis.set_major_formatter(mdates.DateFormatter("%Y")) -ax_nav.xaxis.set_major_locator(mdates.YearLocator()) - -sns.despine(ax=ax_nav, left=True) - -# Save -plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG) diff --git a/plots/line-navigator/implementations/r/ggplot2.R b/plots/line-navigator/implementations/r/ggplot2.R deleted file mode 100644 index 7dc8c1e854..0000000000 --- a/plots/line-navigator/implementations/r/ggplot2.R +++ /dev/null @@ -1,125 +0,0 @@ -#' anyplot.ai -#' line-navigator: Line Chart with Mini Navigator -#' Library: ggplot2 3.5.1 | R 4.4.1 -#' Quality: 86/100 | Created: 2026-05-27 - -library(ggplot2) -library(ragg) -library(gridExtra) - -set.seed(42) - -# --- Theme tokens ----------------------------------------------------------- -THEME <- Sys.getenv("ANYPLOT_THEME", "light") -PAGE_BG <- if (THEME == "light") "#FAF8F1" else "#1A1A17" -ELEVATED_BG <- if (THEME == "light") "#FFFDF6" else "#242420" -INK <- if (THEME == "light") "#1A1A17" else "#F0EFE8" -INK_SOFT <- if (THEME == "light") "#4A4A44" else "#B8B7B0" - -IMPRINT_PALETTE <- c( - "#009E73", "#C475FD", "#4467A3", "#BD8233", - "#AE3030", "#2ABCCD", "#954477", "#99B314" -) -LINE_COLOR <- IMPRINT_PALETTE[1] -SEL_ALPHA <- if (THEME == "light") 0.22 else 0.38 - -# --- Data ------------------------------------------------------------------- -# Daily temperature sensor over 2 years (730 data points) -dates <- seq.Date(as.Date("2022-01-01"), as.Date("2023-12-31"), by = "day") -n <- length(dates) -day_of_yr <- as.numeric(format(dates, "%j")) -trend <- seq(0, 8, length.out = n) -seasonal <- 14 * sin(2 * pi * day_of_yr / 365 - pi / 2) -noise <- cumsum(rnorm(n, 0, 0.35)) -temperature <- 20 + trend + seasonal + noise -df <- data.frame(date = dates, temperature = temperature) - -# Selection window: summer 2023 — the detail view shown in the main chart -sel_start <- as.Date("2023-06-01") -sel_end <- as.Date("2023-08-31") -df_main <- df[df$date >= sel_start & df$date <= sel_end, ] - -# --- Title ------------------------------------------------------------------ -TITLE <- "line-navigator · r · ggplot2 · anyplot.ai" -title_len <- nchar(TITLE) -title_size <- min(12, max(8, round(12 * 67 / title_len))) - -# --- Shared theme ----------------------------------------------------------- -base_theme <- theme_minimal(base_size = 8) + - theme( - plot.background = element_rect(fill = PAGE_BG, color = PAGE_BG), - panel.background = element_rect(fill = PAGE_BG, color = NA), - panel.grid.major = element_line(color = INK_SOFT, linewidth = 0.15), - panel.grid.minor = element_blank(), - axis.title = element_text(color = INK, size = 10), - axis.text = element_text(color = INK_SOFT, size = 8), - axis.line = element_line(color = INK_SOFT, linewidth = 0.4), - axis.ticks = element_line(color = INK_SOFT), - plot.title = element_text(color = INK, size = title_size, face = "bold"), - plot.subtitle = element_text(color = INK_SOFT, size = 9), - plot.margin = margin(t = 8, r = 10, b = 4, l = 10, unit = "pt") - ) - -# --- Main chart: detail view of selected range ------------------------------ -p_main <- ggplot(df_main, aes(x = date, y = temperature)) + - geom_line(color = LINE_COLOR, linewidth = 1.0) + - scale_x_date( - date_labels = "%b %d", - date_breaks = "2 weeks", - expand = expansion(mult = c(0.02, 0.02)) - ) + - scale_y_continuous( - labels = function(x) paste0(round(x, 0), "°C"), - expand = expansion(mult = c(0.05, 0.12)) - ) + - labs( - title = TITLE, - subtitle = sprintf( - "Detail view: %s – %s", - format(sel_start, "%b %d, %Y"), - format(sel_end, "%b %d, %Y") - ), - x = NULL, - y = "Temperature (°C)" - ) + - base_theme + - theme( - panel.grid.major.x = element_blank(), - axis.text.x = element_text(angle = 30, hjust = 1, size = 8) - ) - -# --- Navigator: full history with selection window highlighted -------------- -p_nav <- ggplot(df, aes(x = date, y = temperature)) + - annotate( - "rect", - xmin = sel_start, xmax = sel_end, - ymin = -Inf, ymax = Inf, - fill = LINE_COLOR, alpha = SEL_ALPHA - ) + - geom_line(color = LINE_COLOR, linewidth = 0.45, alpha = 0.75) + - scale_x_date( - date_labels = "%b '%y", - date_breaks = "3 months", - expand = expansion(mult = c(0.01, 0.01)) - ) + - scale_y_continuous(labels = NULL, breaks = NULL) + - labs(x = "Full history – Jan 2022 to Dec 2023", y = NULL) + - base_theme + - theme( - panel.grid.major = element_blank(), - axis.title.x = element_text(color = INK_SOFT, size = 8), - axis.text.x = element_text(size = 7, color = INK_SOFT), - plot.margin = margin(t = 2, r = 10, b = 6, l = 10, unit = "pt") - ) - -# --- Combine and save ------------------------------------------------------- -ragg::agg_png( - filename = sprintf("plot-%s.png", THEME), - width = 8, - height = 4.5, - units = "in", - res = 400, - background = PAGE_BG -) -gridExtra::grid.arrange(p_main, p_nav, nrow = 2, heights = c(5, 1)) -invisible(dev.off()) diff --git a/plots/line-navigator/metadata/julia/makie.yaml b/plots/line-navigator/metadata/julia/makie.yaml deleted file mode 100644 index 5ebd7bf65c..0000000000 --- a/plots/line-navigator/metadata/julia/makie.yaml +++ /dev/null @@ -1,267 +0,0 @@ -library: makie -language: julia -specification_id: line-navigator -created: '2026-05-27T11:55:50Z' -updated: '2026-05-27T12:21:01Z' -generated_by: claude-sonnet -workflow_run: 26508621721 -issue: 3785 -language_version: 1.11.9 -library_version: 0.22.10 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/line-navigator/julia/makie/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/line-navigator/julia/makie/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 88 -review: - strengths: - - 'Perfect spec compliance: dual-panel design with detail chart + navigator pane, - selection highlight (poly + vlines!), and contextual annotation' - - 'Correct Imprint palette — brand green #009E73 as the single data series across - both themes; all chrome tokens (INK, INK_SOFT, INK_MUTED) applied to both axes' - - 'Proper theme-adaptive chrome throughout: titlecolor, xlabelcolor, ylabelcolor, - xticklabelcolor, yticklabelcolor, spinecolor all wired to theme tokens' - - 'Clean Makie layout: rowsize!(fig.layout, 1, Relative(0.78)) and rowgap! for proportional - panel sizing — idiomatic use of the Makie layout system' - - 'Appropriate data-density handling: alpha=0.65 for the dense 1095-point navigator - line vs full opacity for the 90-point detail line' - - ELEVATED_BG differentiates the navigator panel from the main chart, creating visual - hierarchy between overview and detail - - Annotation 'Days 450–540 of 1095 · Summer temperature peak' adds meaningful narrative - context to the selected range - - 'Flawless code quality: flat KISS script, Random.seed!(42), no unused imports, - saves via plot-$(THEME).png' - weaknesses: - - 'Minor axis label imbalance: xlabelsize=11 (''Day'') vs ylabelsize=14 (''Temperature - (°C)'') in the main chart — short label should not be significantly smaller than - the longer descriptive label; normalise both to 12 or 13' - - 'Selection highlight in dark mode has reduced visual prominence: 30% alpha #009E73 - on ELEVATED_BG #242420 produces a dark-teal rectangle that is harder to distinguish - than the light-mode version — consider raising alpha to 0.40 or adding a slightly - brighter stroke (strokewidth=1, color=brand) on the poly! call for the dark theme' - - Navigator panel proportion at 22% slightly exceeds the spec target of 15–20%; - consider Relative(0.80) / Relative(0.20) to stay within spec - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct anyplot light surface. Navigator panel slightly lighter (ELEVATED_BG #FFFDF6); the differentiation is subtle but present. - Chrome: Title "Sensor Data · line-navigator · julia · makie · anyplot.ai" in dark ink, centred, well-proportioned (~65% of plot width). Y-axis label "Temperature (°C)" at size 14 rotated vertically, readable. X-axis "Day" label at size 11 visible at the bottom of both panels. Tick labels at sizes 9–11 in INK_SOFT (#4A4A44), all readable. Top/right spines removed; only L-shaped frame visible. - Data: Main chart shows 90-day detail window (days 450–540) as a crisp #009E73 line at linewidth 2. Y-range roughly 16–27 °C with an upward summer trend. Navigator shows the full 1095-day time series in 65%-alpha green; seasonal sinusoidal pattern clearly visible. Selection window (days ~450–540) shown as a soft green filled rectangle with two vertical edge lines marking boundaries. Annotation "Days 450–540 of 1095 · Summer temperature peak" in INK_MUTED (#6B6A63) positioned in upper-left of detail pane. - Legibility verdict: PASS — all text readable; no light-on-light issues. - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct anyplot dark surface. Navigator panel in ELEVATED_BG #242420 is noticeably distinct from the main panel. - Chrome: Title, axis labels, tick labels all rendered in light INK (#F0EFE8 / #B8B7B0) — clearly readable against the dark background. No dark-on-dark failures detected. Spines in INK_SOFT, grid lines subtle at 15% alpha. - Data: Line colours identical to light render — brand green #009E73 throughout. Navigator selection highlight appears as a darker teal rectangle (30% alpha green on #242420) — visible but lower-contrast than in the light theme. Vertical edge lines in full green remain clearly visible. Both the detail line and navigator line are correctly coloured. - Legibility verdict: PASS — all text readable. Minor concern: the selection fill in the navigator is less visually prominent in dark mode than in light mode, but the vertical boundary lines compensate. - criteria_checklist: - visual_quality: - score: 28 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 6 - max: 8 - passed: true - comment: 'All text readable in both themes. Minor imbalance: xlabelsize=11 - (''Day'') vs ylabelsize=14 (''Temperature (°C)'') in main chart; annotation - at fontsize=12 is small but readable; navigator x-tick labels at size 9 - are legible.' - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text or data elements in either render. Annotation - positioned clear of the data line. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Main detail line at full opacity clearly visible. Navigator line - at 0.65 alpha appropriate for 1095 dense points. Selection highlight visible - in both themes via poly! fill and vlines! boundaries. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: 'Single data series in #009E73; good contrast on both surfaces. No - red-green sole-signal issue.' - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 3200x1800 px landscape canvas (canonical). Two-panel layout well-proportioned. - Navigator at 22% slightly over the 15-20% spec target but visually appropriate. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Y-axis 'Temperature (°C)' descriptive with units. X-axis 'Day' clear. - Title follows spec format with descriptive prefix. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First (only) series is #009E73. Light bg #FAF8F1, dark bg #1A1A17. - Navigator uses ELEVATED_BG correctly. Data colors identical across themes.' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Thoughtful two-panel design with visual hierarchy. ELEVATED_BG panel - differentiation. Consistent linewidth across both axes. Above default but - annotation styling and panel separation could be more refined. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Spines removed (L-frame), y-only grid at 15% alpha, navigator y-tick - labels hidden. Row gap between panels is subtle. Well above default. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Annotation clearly identifies selected range with 'Summer temperature - peak' context. Navigator selection visually communicates the current zoom - window. Strong narrative design. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct line chart with mini navigator pane. Both the main detail - view and the full-extent navigator are implemented. - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Main chart shows selected range in detail; navigator shows full data - extent; selection window highlighted with filled polygon + vertical edge - lines (static equivalents of draggable handles per makie.md guidance); contextual - text label; consistent line color/width across both charts. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X=Day (temporal), Y=Temperature (°C). 1095 data points meets the - 1000+ spec requirement. Selected window correctly shown in detail. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title 'Sensor Data · line-navigator · julia · makie · anyplot.ai' - matches the optional {Descriptive} · {spec-id} · {lang} · {lib} · anyplot.ai - format. No legend needed (single series). - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Full 3-year seasonal sinusoidal temperature data with realistic noise. - 90-day zoom window shows day-to-day detail. Navigator shows complete seasonal - cycles. Selection placed at summer peak — demonstrates the navigator's purpose. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Daily sensor temperature readings over 3 years. Range ~15-27°C is - plausible for a temperate climate. Seasonal pattern and noise are realistic. - Neutral topic. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: 1095 data points exceeds the 1000+ spec requirement. Temperature - scale sensible. 90-day zoom window demonstrates the navigator use case well. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Flat script, no functions or classes. Clear sequential structure. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Random.seed!(42) present. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: CairoMakie, Colors, Random — all used. No unused imports. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Appropriate complexity. No fake UI. Variable names clear. RGBAf used - correctly for alpha colors. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-$(THEME).png with px_per_unit=2. Current Makie API. - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Correct use of Axis, lines!, poly!, vlines!, text!, rowsize!, rowgap!, - fig.layout, Relative(). All idiomatic CairoMakie patterns. - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Makie layout system (rowsize! + Relative()) for proportional panels, - poly! for selection highlight polygon, RGBAf for semi-transparent colors. - Demonstrates Makie-specific capabilities. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - subplots - - annotations - patterns: - - data-generation - dataprep: - - time-series - styling: - - alpha-blending diff --git a/plots/line-navigator/metadata/python/altair.yaml b/plots/line-navigator/metadata/python/altair.yaml deleted file mode 100644 index e9cf395bf2..0000000000 --- a/plots/line-navigator/metadata/python/altair.yaml +++ /dev/null @@ -1,266 +0,0 @@ -library: altair -language: python -specification_id: line-navigator -created: '2026-01-20T20:58:10Z' -updated: '2026-05-27T19:13:40Z' -generated_by: claude-sonnet -workflow_run: 26508053384 -issue: 3785 -language_version: 3.13.13 -library_version: 6.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/altair/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/altair/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/altair/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/altair/plot-dark.html -quality_score: 93 -review: - strengths: - - Excellent use of Altair linked brushing — selection_interval with transform_filter - and scale=domain=brush is the most idiomatic Altair pattern for overview+detail - navigation - - 'Perfect theme adaptation in both renders: warm off-white light background, warm - near-black dark background, all chrome tokens correctly applied' - - Pre-initialized selection window (H1 2023) with amber highlight clearly demonstrates - navigator functionality - - Date range labels computed via transform_aggregate show the selected window start/end - dates informatively - - Navigator proportionally smaller (~18% of main height, within spec 15-20%), with - year-only x-axis labels reducing redundancy - - Clean KISS structure, reproducible (seed=42), all required outputs (PNG + HTML) - correctly saved - - Sensor data with trend + seasonality + noise creates realistic, feature-rich time - series that showcases navigation well - weaknesses: - - Main chart in static PNG shows the full 2022-2024 range rather than the pre-selected - H1 2023 range — scale=alt.Scale(domain=brush) with the initialized brush value - does not appear to constrain the main chart x-axis domain in the vl-convert static - export (works correctly in interactive HTML) - - Altair default selection-box outline renders as a thin light rectangle around - the amber area in the dark navigator — consider hiding or styling the brush outline - (configure_selection or strokeWidth=0 on the brush) for a cleaner dark-theme appearance - - Navigator green line (strokeWidth=1) is very faint at the small height — barely - distinguishable from baseline in both renders; could use strokeWidth=1.5 or a - slightly higher navigator height to improve visibility - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct light surface, not pure white. - Chrome: Title "line-navigator · python · altair · anyplot.ai" in dark bold text, clearly readable. Y-axis label "Sensor Reading (°C)" readable. X-axis label "Date" readable. Tick labels readable in soft dark color. All text passes legibility on light background. - Main chart: Brand green (#009E73) line showing 3 years of sensor data (2022–2024). Subtle horizontal grid with ~15% opacity. Good proportions. - Navigator: Small pane at bottom (~18% of main height). Brand green thin line. Amber (#DDCC77) filled area highlights selected H1 2023 range. Date range labels "2023-01-01" (left) and "2023-07-01" (right) visible. Year-only x-axis (2022, 2023, 2024). - Legibility verdict: PASS — all elements readable on warm off-white background. - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct dark surface, not pure black. - Chrome: Title and axis labels render in light-colored text (#F0EFE8 / #B8B7B0 tokens), clearly readable against dark background. No dark-on-dark failures observed. Grid lines subtle (light opacity). - Data: Brand green (#009E73) line identical to light render — palette positions unchanged. Amber selection in navigator appears slightly more muted/olive on dark background but still visible. Light border (Altair default brush outline) visible around the selection box. - Legibility verdict: PASS — all text readable, correct light text on dark background throughout. No dark-on-dark failures. - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All font sizes explicitly set (title=16, axis=12, labels=10, range-labels=11). - All text readable in both light and dark renders. Proportions good. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text or data elements. Date range labels well-positioned - at navigator corners. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Main line (strokeWidth=2) clearly visible for 1095-point density. - Navigator line thin but appropriate for 40px height. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Single green series + amber selection anchor — both colorblind-safe. - Good contrast in both themes. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 'No canvas gate. Layout well-proportioned: main chart + navigator - with proper spacing. Plot fills canvas well, centered margins balanced.' - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: 'Y: ''Sensor Reading (°C)'' (with units). X: ''Date''. Title: correct - mandated format.' - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73 (brand green). Amber (#DDCC77) used as semantic - selection anchor — correct usage. Background #FAF8F1 light / #1A1A17 dark. - All chrome theme-adaptive.' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Above configured defaults: amber selection highlight, date range - labels, proportional navigator, semantic color choices. Not yet exceptional - design, but thoughtful.' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: strokeWidth=0 removes view borders, subtle grid (15% opacity), navigator - strips y-axis chrome, year-only x-axis on navigator reduces redundancy. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Overview+detail pattern provides strong visual hierarchy. Pre-selected - H1 2023 demonstrates the navigator feature. Seasonal patterns in data naturally - visible. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Line chart with mini navigator — correct type with all sub-components. - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Navigator with draggable selection (selection_interval), main chart - linked to brush, amber highlight fills selected range, date range labels, - proportional navigator height, shared line styling. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Date on x-axis (temporal), sensor value on y-axis (quantitative). - Both charts correctly mapped. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title 'line-navigator · python · altair · anyplot.ai' — correct format. - Single series, no legend needed. - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 1095 data points covering 3 years, demonstrates navigation across - long time series. Trend + seasonal + noise creates realistic variability. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Daily sensor readings — plausible real-world scenario, neutral domain. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Temperature range 10-45°C with seasonal amplitude ~8°C and gradual - trend — physically plausible sensor data. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear structure: imports → data → chart components → combine → - save. No functions or classes.' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: 'All imports used: altair, numpy, pandas, PIL.Image, os, sys.' - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean Altair declarative style. Layer composition and vconcat are - idiomatic. No fake UI. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png (with padding to 3200x1800) and plot-{THEME}.html. - Altair 6.1.0 API. - library_mastery: - score: 10 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: Expertly uses selection_interval, transform_filter, transform_aggregate, - vconcat, layer, alt.Scale(domain=brush) — the canonical Altair patterns - for linked interactive charts. - - id: LM-02 - name: Distinctive Features - score: 5 - max: 5 - passed: true - comment: Linked brushing with overview+detail is Altair's signature declarative - interaction pattern. The combination of scale=domain=brush + transform_filter(brush) - + transform_aggregate for range labels is distinctly Altair. - verdict: APPROVED -impl_tags: - dependencies: - - pillow - techniques: - - hover-tooltips - - html-export - - layer-composition - - annotations - patterns: - - data-generation - dataprep: - - time-series - styling: - - alpha-blending diff --git a/plots/line-navigator/metadata/python/bokeh.yaml b/plots/line-navigator/metadata/python/bokeh.yaml deleted file mode 100644 index e453d66d6d..0000000000 --- a/plots/line-navigator/metadata/python/bokeh.yaml +++ /dev/null @@ -1,249 +0,0 @@ -library: bokeh -language: python -specification_id: line-navigator -created: '2026-01-20T20:57:33Z' -updated: '2026-05-27T11:50:50Z' -generated_by: claude-sonnet -workflow_run: 26507960457 -issue: 3785 -language_version: 3.13.13 -library_version: 3.9.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/bokeh/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/bokeh/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/bokeh/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/bokeh/plot-dark.html -quality_score: 90 -review: - strengths: - - Excellent use of Bokeh's RangeTool — the defining library feature for this spec, - implemented correctly with linked x_range synchronization between main and navigator - figures - - Amber semantic anchor (#DDCC77) for the selection window is a smart, palette-compliant - color choice that avoids spending a categorical palette slot - - Navigator height (300/1800 = 16.7%) is perfectly within the spec's 15-20% requirement - - Differential alpha (0.9 main / 0.6 navigator) creates intuitive visual hierarchy - between detail and overview views - - Full theme-adaptive chrome implementation with all required tokens; both light - and dark renders pass readability checks - - 1095-point dataset with trend + seasonal + noise components is an ideal demo for - the navigator concept - weaknesses: - - Title font size (65pt) exceeds the 50pt style-guide default for a ~44-character - title; consider reducing to 50-55pt to stay closer to the standard - - xgrid is not explicitly disabled on either figure; for a line chart, y-only grid - is preferred per style guide — add main_plot.xgrid.grid_line_color = None and - navigator.xgrid.grid_line_color = None - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct theme surface - Chrome: Title "line-navigator · python · bokeh · anyplot.ai" in bold dark text, spans ~55-60% of plot width. Y-axis label "Sensor Reading (units)" and X-axis label "Date" in dark text, clearly readable. Tick labels in INK_SOFT (dark gray) are legible. - Data: Brand green (#009E73) line showing sensor readings from Jan 2024 to Jun 2024 in main chart. Navigator below shows full 2022-2025 range with amber (#DDCC77, alpha=0.3) selection window highlighting the visible portion. Subtle horizontal grid lines (alpha=0.15). - Legibility verdict: PASS — all text readable against light background, no light-on-light issues. - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct theme surface - Chrome: Title and all axis labels/tick labels render in light cream text (INK = #F0EFE8), clearly visible against the dark surface. Tick labels use INK_SOFT (#B8B7B0) — sufficient contrast, no dark-on-dark failure. - Data: Brand green (#009E73) data line is identical to the light render — color preserved across themes as required. Amber selection window in navigator appears slightly olive/muted due to alpha compositing on dark background but remains visually distinct. Grid lines subtle and appropriately muted. - Legibility verdict: PASS — all title, axis label, and tick label text is clearly readable against the dark background. No dark-on-dark failure detected. - criteria_checklist: - visual_quality: - score: 29 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: All text readable in both themes. Title at 65pt is slightly above - the 50pt style-guide default for a ~44-char title; minor deduction. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No text or data collisions in either render. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Line at width=4 with alpha=0.9 clearly visible for 1095-point dense - dataset. Navigator uses alpha=0.6 for differentiation. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Single series in brand green; high contrast against both backgrounds. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Canvas gate passed (3200x1800). Navigator height 16.7% within spec's - 15-20%. Clean proportions. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title matches required format. Y-axis label includes units. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Data line uses #009E73 (brand green). Selection overlay uses #DDCC77 - (amber semantic anchor). Correct backgrounds for both themes.' - design_excellence: - score: 11 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Intentional amber semantic anchor for selection, differential alpha - for visual hierarchy, no redundant legend. Above defaults. - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: true - comment: outline_line_color=None removes box border; ygrid at alpha=0.15 is - subtle; min_border settings create generous whitespace. xgrid not explicitly - disabled (minor). - - id: DE-03 - name: Data Storytelling - score: 3 - max: 6 - passed: true - comment: Two-pane design with linked ranges directly embodies the navigator - concept; amber window creates focal point. Above defaults. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Line chart with mini navigator — exactly matches spec. - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Main detail view, mini overview, draggable selection window via RangeTool, - resize handles, shared line styling, navigator at 16.7% height. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Date/value mapping correct; 1095 data points exceeds 1000+ requirement. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title is 'line-navigator · python · bokeh · anyplot.ai' — exact required - format. No legend for single-series (correct). - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Large dataset, detail view, overview, selection window, realistic - trend+seasonal+noise signal. All navigator aspects shown. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Daily sensor readings over 3 years with upward trend, seasonal cycle, - and realistic noise — plausible, neutral, non-controversial. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: 1095 data points over 3 years at daily frequency is ideal for demonstrating - the necessity of a navigator widget. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Flat script, no functions or classes. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) ensures deterministic output. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imported modules are used. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Well-organized, appropriate complexity for the navigator pattern. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.html and plot-{THEME}.png. Uses current Bokeh - API with Selenium screenshot. - library_mastery: - score: 10 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: ColumnDataSource, RangeTool with linked x_range, HoverTool with datetime - formatter, column() layout — all highly idiomatic Bokeh patterns. - - id: LM-02 - name: Distinctive Features - score: 5 - max: 5 - passed: true - comment: RangeTool is a signature Bokeh widget unavailable in most other libraries. - Linked x_range synchronization is quintessentially Bokeh. Outstanding library - mastery. - verdict: APPROVED -impl_tags: - dependencies: - - selenium - - pillow - techniques: - - hover-tooltips - - html-export - - subplots - patterns: - - data-generation - - columndatasource - dataprep: - - time-series - styling: - - alpha-blending diff --git a/plots/line-navigator/metadata/python/letsplot.yaml b/plots/line-navigator/metadata/python/letsplot.yaml deleted file mode 100644 index 9024e6adf3..0000000000 --- a/plots/line-navigator/metadata/python/letsplot.yaml +++ /dev/null @@ -1,248 +0,0 @@ -library: letsplot -language: python -specification_id: line-navigator -created: '2026-01-20T20:58:06Z' -updated: '2026-05-27T12:03:29Z' -generated_by: claude-sonnet -workflow_run: 26508425012 -issue: 3785 -language_version: 3.13.13 -library_version: 4.10.1 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/letsplot/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/letsplot/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/letsplot/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/letsplot/plot-dark.html -quality_score: 87 -review: - strengths: - - Proper theme-adaptive chrome (PAGE_BG, ELEVATED_BG, INK, INK_SOFT) threads through - both charts; both light and dark renders pass readability checks - - Navigator proportion (~18% height via gggrid heights=[5.5, 1]) matches spec requirement - precisely - - geom_vline handle indicators + geom_rect selection window give a convincing visual - navigator representation in the static PNG - - layer_tooltips() with formatted date and temperature provides genuine interactivity - in the HTML output - - Distinct elevated background for the navigator panel (ELEVATED_BG) visually separates - the two panels without a hard border - - 'Date range annotation (''Selected: Jul 04, 2025 - Dec 30, 2025'') adds meaningful - temporal context to the main chart' - weaknesses: - - True brush-and-drag navigator interactivity is absent -- letsplot HTML export - provides tooltip interactivity but not a linked brush widget; static geom_rect - + geom_vline representation falls short of the spec's 'draggable selection window' - and 'smooth animated transitions' - - Navigator tick labels at size=8 are at the lower readable limit; increase to size=9 - for better robustness at small display widths - - Navigator geom_line at size=0.8 on a 90px-tall panel is very thin; size=1.0 would - improve overview visibility without adding clutter - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) -- correct, not pure white - Chrome: Title "line-navigator · python · letsplot · anyplot.ai" in bold dark (#1A1A17) text -- fully readable. Y-axis label "Temperature (°C)" and X-axis month/year tick labels in INK_SOFT (#4A4A44) -- all readable. Navigator year labels (2023-2026) in INK_SOFT at size=8 -- readable at full resolution but borderline small. Range annotation "Selected: Jul 04, 2025 - Dec 30, 2025" in brand green -- readable and well-placed. - Data: Brand green (#009E73) line (size=1.5) with soft area fill (alpha=0.15) in main chart. Navigator uses same green at size=0.8. Selection rectangle has green border (size=2.5, alpha=0.2 fill) and two geom_vline handle markers. First (and only) series is #009E73 -- correct. - Legibility verdict: PASS - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) -- correct, not pure black - Chrome: Title and axis labels rendered in light near-white (#F0EFE8 / #B8B7B0) -- fully readable against dark surface. No dark-on-dark failures detected; all chrome elements use the correct dark-theme tokens. Navigator elevated panel uses #242420. - Data: Brand green (#009E73) data line and area fill are identical to light render -- color identity preserved across themes. Navigator selection rectangle and handles same green -- visible on dark elevated panel. - Legibility verdict: PASS - criteria_checklist: - visual_quality: - score: 28 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: Main chart text fully readable in both themes; navigator tick labels - at size=8 are borderline small but legible at full resolution - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No text collisions; range label well-positioned above data - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: Main chart line prominent; navigator line at size=0.8 is intentionally - thin for overview but borderline at smallest display sizes - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: 'Single series brand green #009E73; CVD-safe' - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Canvas gate passed (3200x1800); navigator ~18% height as specified; - no clipping or overflow - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Y-axis has units (°C); title in mandated format - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Single series = #009E73; backgrounds correct (#FAF8F1 light / #1A1A17 - dark); chrome flips correctly' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Above default (4): theme-adaptive chrome, geom_area alpha fill for - depth, navigator ELEVATED_BG panel, range annotation' - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: true - comment: 'Above default (2): theme_minimal removes spines; grid opacity tiered - (RULE 0.15 main, RULE_FAINT 0.08 navigator); panel_grid_minor blanked' - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: 'Above default (2): navigator communicates full-vs-selected context; - date range annotation anchors viewer in time; clear visual hierarchy' - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: 'Correct: line chart with mini navigator overview pane' - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: All visual features present; missing true drag/brush interactivity - (letsplot platform limitation, not a code defect) - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Date on X, temperature on Y, 1095 data points exceeds 1000+ requirement - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title in mandated format; no legend needed for single series - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 1095-point time series; navigator + detail view fully demonstrated - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Daily temperature sensor readings; plausible seasonal sinusoidal - pattern; neutral domain - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Temperature range ~18-40°C with seasonal swing; appropriate for warm-climate - sensor - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Flat script, no unnecessary functions or classes - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Every import is used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: DataFrames for selection_rect and handle_df are clean; theme layering - is idiomatic - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html correctly - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: gggrid() for multi-panel, layer_tooltips() for interactive data, - scale_x_datetime with format, theme stacking -- all idiomatic letsplot - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: layer_tooltips() native tooltip customization, gggrid() with proportional - heights, HTML export -- distinctive letsplot capabilities - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - subplots - - annotations - - layer-composition - - html-export - - hover-tooltips - patterns: - - data-generation - dataprep: - - time-series - styling: - - alpha-blending - - grid-styling diff --git a/plots/line-navigator/metadata/python/matplotlib.yaml b/plots/line-navigator/metadata/python/matplotlib.yaml deleted file mode 100644 index 0da8a6eabd..0000000000 --- a/plots/line-navigator/metadata/python/matplotlib.yaml +++ /dev/null @@ -1,246 +0,0 @@ -library: matplotlib -language: python -specification_id: line-navigator -created: '2026-01-20T20:57:35Z' -updated: '2026-05-27T11:32:39Z' -generated_by: claude-sonnet -workflow_run: 26507680506 -issue: 3785 -language_version: 3.13.13 -library_version: 3.10.9 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/matplotlib/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/matplotlib/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 88 -review: - strengths: - - 'Navigator concept executed fully in static matplotlib: gray-masked non-selected - regions, amber selection rectangle and vertical handles, and date-range annotation - all communicate the interactive navigator idea as a static snapshot' - - 'Perfect palette compliance: brand green as sole data color, amber used correctly - as semantic anchor (selection/warning role), theme-adaptive chrome throughout - both renders' - - Full spec coverage with 1000+ realistic data points, correct proportional navigator - height (~18%), and shared styling between panels - - 'Clean code: flat structure, seed set, all imports used, no bbox_inches=''tight'', - correct subplots_adjust usage' - weaknesses: - - 'Design excellence could be pushed further: the navigator panel label (''Navigator'') - is functional but small; the fill-under in the main chart could use a gradient - or slightly richer alpha for more visual depth' - - Navigator tick labels at 7pt are the minimum legible size; at mobile display widths - (~400px) they become very tight — consider 8pt to match the main chart - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct - Chrome: Title "line-navigator · python · matplotlib · anyplot.ai" in dark ink (#1A1A17), fully readable. Y-axis label "Temperature (°C)" and X-axis label "Date" in dark ink at 10pt, readable. Tick labels in soft dark (#4A4A44) at 8pt (main) and 7pt (navigator), readable. Date annotation box on elevated #FFFDF6 background with dark border, readable. "Navigator" label in INK_MUTED, readable. - Data: Brand green #009E73 line (linewidth=2.5) with subtle fill-under in main chart. Same green at linewidth=1.0 alpha=0.7 in navigator. Gray axvspan overlays on non-selected regions. Amber #DDCC77 selection rectangle + two vertical handle lines. - Legibility verdict: PASS - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct - Chrome: Title in light #F0EFE8, clearly visible. Axis labels in light ink, readable. Tick labels in #B8B7B0 (INK_SOFT dark), readable — no dark-on-dark failures. Date annotation box on elevated #242420 background with light-ink border, readable. "Navigator" label in INK_MUTED dark (#A8A79F), readable. - Data: Brand green #009E73 line and fill — identical to light render. Gray overlays and amber handles render correctly on dark surface. - Legibility verdict: PASS - criteria_checklist: - visual_quality: - score: 29 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: All font sizes explicitly set; readable in both themes. Navigator - tick labels at 7pt are functional but tight at mobile scale. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No text or element collisions in either render. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: 1095-point navigator at linewidth=1.0 alpha=0.7 is density-appropriate. - Main line at 2.5 is prominent. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Single data series green, amber as semantic anchor. No red-green - conflict. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Canvas gate passed (3200x1800). height_ratios=[4,1] gives ~18% navigator - height matching spec. No clipping. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Temperature (°C) with units, Date for x-axis, correct mandated title. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73, amber #DDCC77 as semantic anchor, backgrounds - #FAF8F1/#1A1A17 correct in both themes.' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Deliberate axvspan gray-out creates visual focus; amber handles are - thoughtful; fill-under adds dimension. Above generic defaults. - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: true - comment: Top/right spines removed; navigator strips left spine; subtle grid; - rounded annotation box. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Gray-masked navigator communicates selection window concept clearly; - date annotation confirms context; amber handles suggest interactivity boundary. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Line chart with mini navigator at bottom — matches spec exactly. - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: 'All features present: detail main chart, full-extent mini chart, - selection window, resize handles, shared styling, proportional navigator - height, date range label.' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X = datetime, Y = temperature. Main chart zooms [400:520]; navigator - shows all 1095 points. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title is exactly 'line-navigator · python · matplotlib · anyplot.ai'. - No legend (single series — correct omission). - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'All navigator aspects demonstrated: zoom view, overview, selection - highlight, grayout, handles, date label.' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Daily temperature readings over 3 years with realistic trend+seasonal+noise. - Neutral domain. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Temperature ~20-35°C, 1095 daily data points — perfectly sized for - the navigator concept. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Flat script, no functions or classes. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) present. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All 5 imports (os, mpatches, plt, np, pd) are used. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Appropriate complexity. No fake UI. Clean logical flow. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png at dpi=400, no bbox_inches='tight', uses fig.subplots_adjust. - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Uses axes-level methods throughout; mpatches.Rectangle, axvspan, - axvline, height_ratios all idiomatic. - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: mpatches.Rectangle for selection window, axvspan for non-selected - region masking, axvline for handles, height_ratios for proportional panels - are matplotlib-specific and well-suited. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - subplots - - annotations - - patches - patterns: - - data-generation - - explicit-figure - dataprep: - - time-series - styling: - - alpha-blending diff --git a/plots/line-navigator/metadata/python/plotly.yaml b/plots/line-navigator/metadata/python/plotly.yaml deleted file mode 100644 index 28cba103e9..0000000000 --- a/plots/line-navigator/metadata/python/plotly.yaml +++ /dev/null @@ -1,255 +0,0 @@ -library: plotly -language: python -specification_id: line-navigator -created: '2026-01-20T20:58:02Z' -updated: '2026-05-27T11:35:17Z' -generated_by: claude-sonnet -workflow_run: 26507868601 -issue: 3785 -language_version: 3.13.13 -library_version: 6.7.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/plotly/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/plotly/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/plotly/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/plotly/plot-dark.html -quality_score: 87 -review: - strengths: - - Perfect use of Plotly's built-in rangeslider and rangeselector — exactly the right - native features for the spec - - 'Full theme-adaptive implementation: INK, INK_SOFT, GRID, ELEVATED_BG, PAGE_BG - all correctly threaded through to every element including the rangeslider and - rangeselector' - - Smart dynamic title font size (clips to 16px for this title length, correctly - calculated) - - Spike lines + unified hover mode add meaningful interactive polish on the HTML - output - - 1100-point seasonal dataset with noise and trend is an ideal example for demonstrating - navigator usefulness - weaknesses: - - 'DE-01/DE-02: X-axis gridlines shown alongside Y-axis gridlines — for a time series - line chart, Y-axis-only grid is preferred. Remove xaxis gridlines (showgrid=False - on xaxis).' - - 'DE-02: Axis borders are visible on both sides without spine-equivalent removal - — set showline=False on both xaxis and yaxis to achieve the cleaner, open-axes - look recommended by the style guide.' - - 'DE-03: No storytelling focal point — add a horizontal dashed reference line at - y=0 labeled ''Freezing Point'' (using fig.add_hline or a shape) to create a meaningful - semantic anchor for temperature data.' - - 'DE-01: The area fill uses a flat semi-transparent color (rgba(0,158,115,0.07)). - A slightly more opaque fill (0.10-0.12 light / 0.14-0.16 dark) with the area bounded - to y-axis minimum (not zero) would produce a more visually polished chart where - the filled region does not nearly vanish in winter months.' - image_description: |- - Light render (plot-light.png): - Background: warm off-white #FAF8F1 — correct - Chrome: centered title "Temperature Sensor Data · line-navigator · python · plotly · anyplot.ai" in dark ink, clearly readable; Y-axis label "Temperature (°C)" and X-axis label "Date" both visible in dark text at 12px; tick labels (Jan 2022–Jan 2025, 0–40°C) in INK_SOFT at 10px — all legible; range selector buttons (1M, 3M, 6M, 1Y, All) styled with brand-green active state - Data: brand green #009E73 line with subtle mint-tinted fill-to-zero; 1100 data points rendered as continuous line; area fill clearly visible; navigator pane shows full series with handles - Legibility verdict: PASS - - Dark render (plot-dark.png): - Background: warm near-black #1A1A17 — correct - Chrome: title, axis labels (Temperature °C, Date), and all tick labels render in light cream (#F0EFE8 / #B8B7B0) — clearly legible against the dark surface; no dark-on-dark failures; range selector buttons adapt to dark background; "All" button retains brand green; navigator pane uses dark elevated background #242420 - Data: same brand green #009E73 line and fill — identical data colors to light render; only chrome elements flip between themes as expected - Legibility verdict: PASS - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All font sizes explicitly set (title 16px dynamic, axis titles 12px, - ticks 10px, rangeselector 10px); well-proportioned in both themes; title - ~70% of width as expected - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No text or element collisions in either render - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Line at width=2 clearly visible; 1100 dense points as continuous - line appropriate; area fill adds depth - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Single series brand green; adequate contrast on both surfaces; CVD-safe - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 3200x1800 correct; plot fills 65-70% canvas; balanced margins; navigator - proportionally sized at thickness=0.15 - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Y-axis has unit 'Temperature (°C)'; X-axis 'Date'; title correct - format - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Single series #009E73; light bg #FAF8F1, dark bg #1A1A17; full theme-adaptive - chrome correctly applied' - design_excellence: - score: 9 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: false - comment: 'Well-configured Plotly default: brand green line+fill, rangeselector - with active brand color, spike lines. No standout design decisions beyond - polished defaults.' - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: false - comment: Grid subtly set to 0.15 opacity (good); rangeslider uses ELEVATED_BG - (good); but both axes show gridlines (Y-only preferred), axis borders present - without spine-removal equivalent - - id: DE-03 - name: Data Storytelling - score: 2 - max: 6 - passed: false - comment: Data displayed but not interpreted; 3 seasonal cycles visible but - no visual anchor, annotation, or emphasis guiding viewer to an insight - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Line chart with rangeslider navigator perfectly matches spec - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Main chart, rangeslider mini-navigator, handles, rangeselector buttons - (1M/3M/6M/1Y/All), thickness=0.15 (within 15-20% spec range) — all present - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Date on X, temperature on Y; 1100 points exceeds spec's 1000+ requirement - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title follows {Descriptive Title} · {spec-id} · {language} · {library} - · anyplot.ai format; single-series, no legend needed - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'Full navigator pattern demonstrated: line + area fill + rangeslider - + rangeselector; seasonal variation ideal for showing navigation usefulness' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Daily temperature sensor data over 3 years — neutral, realistic, - science domain - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Temperature range ~0-42°C for 3-year daily sensor series; seasonal - amplitude 15°C, trend +3°C, noise σ=2°C — all factually plausible - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear: theme constants → data generation → plot → save; no functions - or classes' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: os, sys, numpy, pandas, plotly.graph_objects — all used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean Pythonic code; sys.path guard is standard pattern; no fake - UI; appropriate complexity - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html; current Plotly API - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Uses go.Figure, go.Scatter, update_layout, write_image/write_html - — all idiomatic; scores above default 3 for correct high-level patterns - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: rangeslider, rangeselector with time-step buttons, spike lines (showspikes/spikemode/spikethickness), - hovermode=x unified — all genuinely Plotly-distinctive features - verdict: REJECTED -impl_tags: - dependencies: [] - techniques: - - hover-tooltips - - html-export - patterns: - - data-generation - dataprep: - - time-series - styling: - - alpha-blending diff --git a/plots/line-navigator/metadata/python/plotnine.yaml b/plots/line-navigator/metadata/python/plotnine.yaml deleted file mode 100644 index dbf4d946ed..0000000000 --- a/plots/line-navigator/metadata/python/plotnine.yaml +++ /dev/null @@ -1,264 +0,0 @@ -library: plotnine -language: python -specification_id: line-navigator -created: '2026-01-20T21:00:14Z' -updated: '2026-05-27T11:51:00Z' -generated_by: claude-sonnet -workflow_run: 26508145992 -issue: 3785 -language_version: 3.13.13 -library_version: 0.15.4 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/plotnine/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/plotnine/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 83 -review: - strengths: - - Clean dual-panel composition using plotnine's patchwork '/' operator — idiomatic - and concise - - 'Theme-adaptive chrome is fully correct in both renders: correct backgrounds (#FAF8F1 - light / #1A1A17 dark), all text uses INK/INK_SOFT tokens, no dark-on-dark failures' - - Brand green (#009E73) used correctly as the sole data series color, consistent - across both themes - - Navigator selection highlight (geom_rect + geom_vline + date range annotation) - clearly communicates the selected range concept - - 'Excellent data quality: 3-year industrial temperature sensor dataset with seasonal - cycles, upward trend, and anomaly spikes — all aspects visible in the overview' - - 'Perfect code quality: KISS structure, seed set, clean imports, saves plot-{THEME}.png - correctly' - - Subtle grid in main chart (alpha=0.15), no grid in navigator — good visual hierarchy - between panels - weaknesses: - - Navigator panel appears visually larger (~35-40% of image height) than the spec's - required 15-20% of main chart height — manual axis positioning via fig.axes may - not be fully respected by plotnine's patchwork engine; consider using matplotlib - subplots with height_ratios=[5,1] as the underlying figure layout instead - - The 'Overview' title (size=8, INK_SOFT) and date range annotation 'Jun 2022 – - Oct 2022' (size=3.5) are quite small in the navigator panel — the annotation especially - may be hard to read at mobile widths (~400px); increase annotation size to 4.5-5 - and 'Overview' title to 10pt - - 'Minor: navigator y-axis has no title label — a short label like ''°C'' would - clarify the unit in the overview pane' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct, not pure white - Chrome: Title "line-navigator · python · plotnine · anyplot.ai" in dark ink (#1A1A17), clearly readable. Y-axis label "Temperature (°C)" in dark ink, readable. X-axis tick labels rotated 45° in INK_SOFT, readable. Navigator "Overview" title in INK_SOFT (small but visible). Date range annotation "Jun 2022 – Oct 2022" in INK_SOFT, small but visible. - Data: Single brand green (#009E73) line in detail view showing temperature data May–Oct 2022. Navigator shows full 2021–2024 range at reduced alpha (0.7). Selection highlight is a semi-transparent brand green rectangle with vertical boundary lines. Data line clearly visible against warm off-white background. - Legibility verdict: PASS — all text is readable against the light background; no light-on-light issues - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct, not pure black - Chrome: Title in light text (F0EFE8 equivalent), clearly readable against dark background. Axis labels in light text, readable. Tick labels in INK_SOFT light equivalent (#B8B7B0), readable. "Overview" title readable. No dark-on-dark failures detected. - Data: Data line color is identical brand green (#009E73) — same as light render. Selection highlight renders as a slightly more opaque green overlay on the dark background (expected with alpha=0.2 on near-black). Grid lines subtle but present in main chart, absent in navigator. - Legibility verdict: PASS — all text is readable against the dark background; no dark-on-dark issues detected - criteria_checklist: - visual_quality: - score: 25 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 6 - max: 8 - passed: true - comment: Font sizes explicitly set (12pt title, 10pt axis, 8pt ticks). All - text readable in both themes. Navigator annotation (size=3.5) and 'Overview' - title (size=8) are small but not illegible. Deducted 2 for these being undersized - for mobile readability. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping elements in either render. Rotated x-axis labels in - main chart are well-spaced. - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: Line in detail view (size=1.0) is well-visible. Navigator line (size=0.7, - alpha=0.7) is slightly thin for 1100 points but still readable. Deducted - 1. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Single-series brand green (#009E73) is CVD-safe. Good contrast against - both backgrounds. - - id: VQ-05 - name: Layout & Canvas - score: 2 - max: 4 - passed: true - comment: Navigator panel appears visually larger (~35-40% of image height) - than the spec's required 15-20% of main chart height. Functional but proportions - are off from spec. Canvas is exactly 3200x1800. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: 'Main chart: Y-axis ''Temperature (°C)'' with units, X-axis empty - (dates are self-explanatory). Navigator: X-axis ''Date'', Y-axis empty (no - unit shown).' - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First (only) series is brand green #009E73. Backgrounds are #FAF8F1 - (light) and #1A1A17 (dark). Chrome uses correct INK/INK_SOFT tokens in both - renders.' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: true - comment: Clean, professional design with brand-correct palette. Well-configured - theme_minimal with custom chrome. Not exceptional but above generic defaults. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Navigator has no grid (clean), main chart has very subtle grid (alpha=0.15). - theme_minimal removes unnecessary spines. Good separation between panels. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: The navigator selection highlight clearly shows which portion of - the full time series is displayed in the detail view. Seasonal patterns - and anomalies in the full dataset are visible. Good visual narrative. - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: 'Correct: line chart with mini navigator overview pane below.' - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Main chart with detail view ✓, mini chart with full extent ✓, selection - window highlight ✓, date range text ✓, shared styling ✓. Navigator proportion - off from spec's 15-20% requirement. Deducted 1. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Date on X-axis, temperature values on Y-axis. Correct in both panels. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title is exactly 'line-navigator · python · plotnine · anyplot.ai'. - No legend needed for single series. - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'Dataset shows all key aspects: 3 years of data (long time series - requiring navigation), seasonal cycles, upward trend, anomaly spikes at - realistic intervals.' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Industrial temperature sensor readings — realistic, neutral, non-controversial - domain. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Temperature range ~25-75°C with baseline 50°C is plausible for industrial - equipment. Seasonal variation of 20°C and 8°C upward trend are realistic. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Imports → Data → Plot → Save. No functions or classes. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports used. No unnecessary imports. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean, Pythonic code. Patchwork composition with '/' operator is - elegant. No fake UI elements. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png. Current plotnine API used. - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: 'Idiomatic plotnine: ggplot() + geom_*() layers, theme_minimal() - + theme() composition, scale_x_datetime with date_labels/date_breaks. Patchwork - with ''/'' operator is idiomatic.' - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Uses plotnine's patchwork '/' composition operator (inherited from - ggplot2/patchwork) — a distinctive feature for multi-panel layouts. geom_rect - for selection highlight is library-appropriate. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - annotations - - subplots - patterns: - - data-generation - dataprep: - - time-series - styling: - - alpha-blending diff --git a/plots/line-navigator/metadata/python/pygal.yaml b/plots/line-navigator/metadata/python/pygal.yaml deleted file mode 100644 index cff05062ca..0000000000 --- a/plots/line-navigator/metadata/python/pygal.yaml +++ /dev/null @@ -1,267 +0,0 @@ -library: pygal -language: python -specification_id: line-navigator -created: '2026-01-20T20:59:13Z' -updated: '2026-05-27T11:41:07Z' -generated_by: claude-sonnet -workflow_run: 26508239295 -issue: 3785 -language_version: 3.13.13 -library_version: 3.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/pygal/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/pygal/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/pygal/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/pygal/plot-dark.html -quality_score: 86 -review: - strengths: - - Correct two-pane line+navigator layout with main detail chart (85%) and mini overview - (15%) exactly matching the spec ratio - - 'Brand green #009E73 used as first series in both main and navigator charts; lavender - #C475FD correctly used for the selection highlight as palette position 2' - - Both light (#FAF8F1) and dark (#1A1A17) themes render correctly with proper theme-adaptive - chrome — no dark-on-dark or light-on-light failures - - All font sizes explicitly set in both chart styles (title=66, label=56, major_label=44, - legend=44 for main; scaled-down equivalents for navigator) - - PIL composition cleanly stacks the two pygal-rendered PNGs into an exact 3200×1800 - canvas; resize guard prevents pixel-offset artifacts from cairosvg - - Dual HTML+PNG output correctly generated; cubic interpolation on the main line - provides visual smoothness for the 200-point detail view - - 'Elevated background on navigator pane (#FFFDF6 light / #242420 dark) creates - subtle visual separation between the two chart regions' - - 'Data is realistic: 3-year daily sensor readings with trend + seasonality + noise - — a genuinely useful navigator scenario with 1095 points' - weaknesses: - - Design sophistication (DE-01) is above defaults but still reads as a well-configured - library output rather than a publication-ready chart — consider adding a thin - horizontal rule or subtle annotation marking the selection bounds (start/end date - lines) in the navigator to make the selection window more visually explicit and - polished - - 'Navigator legend (''Full Dataset (2022–2024)'' and ''Selected: Feb 2023 – Aug - 2023'') appears compressed at the 270px navigator height — the legend_at_bottom - placement competes with the already-tight vertical space; consider moving the - navigator legend to a one-line label rendered inside the navigator (e.g., a text - annotation) or removing it if the selection range label in the main chart''s legend - is sufficient' - - The navigator fill series for the selected range uses opacity='.6' globally but - the selection-highlighted fill could benefit from stronger contrast (more opaque - fill or a slightly different opacity level) to make the selected window more visually - distinct from the full dataset fill - - No date-range annotation / callout on the main chart beyond the legend — adding - a subtle subtitle or annotation showing the exact range being viewed would improve - data storytelling (DE-03) without adding clutter - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct anyplot light surface, not pure white. - Chrome: Title "line-navigator · python · pygal · anyplot.ai" in dark ink, clearly readable at ~65–70% of chart width. X-axis label "Date" and Y-axis label "Sensor Reading (mV)" visible at appropriate size. Tick labels (Feb 05 through Aug 22 on X; 50–90 on Y) readable in dark ink. Subtle Y-axis grid lines present. Legend at bottom of main chart shows "■ Feb 2023 – Aug 2023" in dark text. - Data: Single brand-green (#009E73) line showing 200 data points (Feb–Aug 2023) with cubic interpolation. No dots (appropriate for this density). Navigator pane below uses elevated background (#FFFDF6) with a filled area chart — full dataset in green (#009E73 with opacity 0.6) and selected range in lavender (#C475FD). - Navigator: 270px tall (~17.6% of main chart height, within spec's 15–20% target). Legend shows "Full Dataset (2022–2024)" and "Selected: Feb 2023 – Aug 2023" — text is small due to compressed height but set explicitly. - Legibility verdict: PASS — all elements readable; first series correctly #009E73. - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct anyplot dark surface, not pure black. - Chrome: Title in light off-white (#F0EFE8), clearly readable against dark background. Axis labels and tick labels in light-colored text (#B8B7B0 range), all readable. Subtle grid lines visible. Legend text light-colored. - Data: Brand-green line identical to light render — data colors are unchanged between themes as required. Lavender selection fill in navigator matches light render. No dark-on-dark failures detected; no near-black text on near-black background. - Legibility verdict: PASS — all text readable; chrome correctly adapts to dark theme; data colors identical to light render. - criteria_checklist: - visual_quality: - score: 28 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: All font sizes explicitly set; main chart text proportioned well; - navigator legend text is small due to 270px height constraint but explicitly - sized - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text in either render; main chart x-labels spaced - correctly - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: Line without dots appropriate for 200-point detail and 1095-point - navigator; navigator fill compressed but functional - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: '#009E73 and #C475FD are CVD-safe; good contrast in both themes' - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Exact 3200x1800 canvas; 85%/15% split within spec range; good margins - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title correct format; 'Sensor Reading (mV)' includes units; 'Date' - appropriate - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73; second series #C475FD (palette position 2); - backgrounds #FAF8F1/#1A1A17 correct' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Above defaults: elevated navigator bg, dual-pane layout, opacity - refinement — not yet publication-ready' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: No dots, Y-only grid, no navigator guides, cubic interpolation, elevated - bg separation — good refinement - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Navigator selection highlight creates clear focal point; dual-pane - inherently tells a zoom story - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct line chart with navigator pane implementation - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Main detail view, mini overview, visual selection window, date labels - present; no interactive draggable handles (static medium limitation) - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Date on X, sensor readings on Y; selected range and full dataset - correctly mapped - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title 'line-navigator · python · pygal · anyplot.ai' exactly correct; - legends clearly label all series - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: Full dataset + selected range highlighted; trend and seasonal variation - visible in navigator; 1095-point series demonstrates navigation need - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Daily sensor readings over 3 years with trend, seasonality, and noise - — real, neutral, scientifically plausible - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: 50–90 mV range appropriate for sensor data; 1095 daily points makes - navigation essential - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear flow: imports → sys.path fix → data → charts → PIL compose - → HTML; no functions or classes' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: 'All imports used: io, os, sys, numpy, pandas, pygal, PIL.Image, - pygal.style.Style' - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean Pythonic code; sys.path manipulation necessary (file named - pygal.py); PIL composition is straightforward - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html correctly - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Correct Style object usage with all theme tokens; render_to_png() - and render() both used; proper constructor configuration - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Cubic interpolation (pygal-native), fill=True for area, dual PNG+HTML - output using render_to_png() + render() — distinctively pygal - verdict: REJECTED -impl_tags: - dependencies: - - pillow - techniques: - - html-export - - subplots - patterns: - - data-generation - dataprep: - - time-series - styling: - - alpha-blending diff --git a/plots/line-navigator/metadata/python/seaborn.yaml b/plots/line-navigator/metadata/python/seaborn.yaml deleted file mode 100644 index ffe73acbb0..0000000000 --- a/plots/line-navigator/metadata/python/seaborn.yaml +++ /dev/null @@ -1,277 +0,0 @@ -library: seaborn -language: python -specification_id: line-navigator -created: '2026-01-20T20:58:20Z' -updated: '2026-05-27T11:43:10Z' -generated_by: claude-sonnet -workflow_run: 26507772652 -issue: 3785 -language_version: 3.13.13 -library_version: 0.13.2 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/seaborn/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/line-navigator/python/seaborn/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 85 -review: - strengths: - - 'Excellent navigator design: non-selected regions dimmed with INK_MUTED overlay, - orange (ochre, palette-consistent #BD8233) selection handles clearly delineate - the visible window' - - 'Seaborn-distinctive use of statistical aggregation in navigator: sns.lineplot - with estimator=''mean'' and errorbar=''sd'' shows weekly mean ± SD bands — uniquely - seaborn, not replicable in plain matplotlib' - - 'Strong data storytelling: peak annotation with arrow, mean reference dashed line, - and date range label in top-right corner all guide the reader through the CPU - utilization story' - - 'Correct theme adaptation across both renders: all chrome tokens (PAGE_BG, INK, - INK_SOFT, INK_MUTED, ELEVATED_BG) properly applied, no dark-on-dark or light-on-light - failures' - - 'Clean code structure: np.random.seed(42) ensures reproducibility, no functions/classes, - all imports used, gridspec layout clearly separates main chart from navigator' - weaknesses: - - 'Dataset has 730 data points but spec requires 1000+ (spec: ''Size: 1000+ data - points (designed for large datasets where navigation is essential)'') — increase - periods to at least 1095 (3 years of daily data) and adjust selection window indices - accordingly' - - Selection window green fill inside navigator (alpha=0.08, color=BRAND) is nearly - invisible in both renders, especially in dark mode — increase alpha to 0.15-0.20 - so the selected region is visually distinct from the non-selected areas - - Main chart x-axis has no label (ax_main.set_xlabel('')) — while common for linked - charts, the navigator carries 'Date' label; this slight asymmetry is acceptable - but adding a subtle date range label or a light x-axis label to the main chart - would improve context - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct anyplot surface color - Chrome: Title "line-navigator · python · seaborn · anyplot.ai" at fontsize=12 in dark ink, clearly readable. Y-axis label "CPU Usage (%)" at fontsize=10 readable. Tick labels at 7-8pt readable. Date range annotation "Mar 2024 – Jun 2024" in top-right corner readable. Mean reference annotation "mean XX%" readable. Peak annotation with arrow readable. - Data: Brand green #009E73 line showing CPU utilization over selected 3-month window (Mar–Jun 2024). Subtle fill under the line adds visual depth. Dashed gray mean reference line. Orange (#BD8233) selection handles in navigator. Non-selected navigator regions shaded in muted gray. Weekly aggregation (mean ± SD) shown via seaborn errorbar bands in navigator. - Legibility verdict: PASS — all text readable against warm off-white background, no light-on-light failures - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct anyplot dark surface - Chrome: Title in light cream text (#F0EFE8), clearly readable against dark background. Axis labels, tick labels, date range annotation, mean annotation all rendered in light theme-adaptive tokens and readable. No dark-on-dark text failures observed. - Data: Brand green #009E73 line is identical to light render — only chrome (background, text, grid colors) has flipped. Orange selection handles (#BD8233) remain visible. Non-selected navigator regions show a lighter gray overlay against the dark background, creating appropriate visual contrast. - Legibility verdict: PASS — all text clearly readable against warm near-black background, no dark-on-dark failures, data colors identical to light render - criteria_checklist: - visual_quality: - score: 27 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: Title at 12pt, axis labels at 10pt, tick labels at 7-8pt — all readable - in both themes. Mean and peak annotations at 7pt are on the smaller side - but still legible at full resolution. - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: Annotations are well-placed with no collisions. The mean text near - the right edge and peak annotation with offset arrow avoid overlap with - the line data. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Main line at linewidth=2.0 appropriate for 95-point detail view. - Navigator uses linewidth=0.8 with weekly aggregation for dense 730-point - overview. Selection handles (orange) clearly visible. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: 'Brand green #009E73 for primary series, ochre #BD8233 for selection - handles — both from Imprint palette, CVD-safe combination.' - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: figsize=(8, 4.5) dpi=400 → 3200×1800 px. Navigator at height_ratio - 1:4 (20% of chart height) within spec 15-20%. No overflow or clipping observed. - Canvas gate passed. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title correctly formatted as 'line-navigator · python · seaborn · - anyplot.ai'. Y-axis 'CPU Usage (%)' with units. X-axis 'Date' on navigator. - Main chart omits x-label for linked-chart convention. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73 (brand green). Selection handles use #BD8233 - (ochre, palette position 4) — semantically appropriate for a selection indicator. - Backgrounds #FAF8F1/#1A1A17 correct for light/dark.' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Above default (4). Intentional design: fill under line for depth, - muted overlay for non-selected regions, orange handles for selection boundaries, - clean two-panel layout.' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Above default (2). sns.despine on both axes, subtle y-grid on main - chart, navigator uses left=True despine. Consistent theme-adaptive chrome - throughout. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Above default (2). Peak annotation with arrow, mean reference dashed - line, date range label in corner, navigator contextualizes the selected - window within the full 2-year history. - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: 'Correct: line chart with mini navigator pane. Main detail view + - overview navigator exactly as specified.' - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: 'Selection window highlighting ✓, selection handles ✓, shared styling - ✓, date range text ✓, navigator proportional height ✓. Minor miss: 730 data - points vs spec-required 1000+.' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X=date, Y=CPU%, main chart shows selected window, navigator shows - full 2-year extent. Correct mapping throughout. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title: ''line-navigator · python · seaborn · anyplot.ai'' correct. - Single series — no legend needed (correctly omitted).' - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: 'Covers: detail view, full overview, selection window, handles, fill, - mean reference, peak annotation. Slightly below full coverage because dataset - is 730 not 1000+ points.' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Production server CPU utilization over 2 years is a realistic, neutral, - widely-understood domain. Values (32-95% range with weekly cycles and trend) - are plausible. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Daily data points over 2 years, CPU % in 8-95 range clipped to reasonable - bounds, weekly periodicity and seasonal trend make the dataset realistic - and appropriate for navigator demonstration. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: No functions or classes. Linear procedural code with clear section - comments. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) ensures deterministic output. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All 6 imports (os, matplotlib.dates, matplotlib.pyplot, numpy, pandas, - seaborn) are actively used. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Appropriate complexity. gridspec for layout, no fake UI. axvspan/axvline - for selection visualization is data marks, not fake interactivity. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: plt.savefig(f'plot-{THEME}.png', dpi=400, facecolor=PAGE_BG) — correct - output name, no bbox_inches='tight'. - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Axes-level API (sns.lineplot with ax=), sns.set_theme for theming, - sns.despine for spine removal — all idiomatic seaborn patterns. - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: 'Notable seaborn-distinctive use: sns.lineplot with estimator=''mean'', - errorbar=''sd'' for weekly aggregation in navigator produces mean ± SD confidence - bands automatically — this requires manual calculation in plain matplotlib - and is a genuine seaborn strength.' - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - subplots - - annotations - - manual-ticks - patterns: - - data-generation - dataprep: - - time-series - - groupby-aggregation - styling: - - alpha-blending - - grid-styling diff --git a/plots/line-navigator/metadata/r/ggplot2.yaml b/plots/line-navigator/metadata/r/ggplot2.yaml deleted file mode 100644 index a037ff7734..0000000000 --- a/plots/line-navigator/metadata/r/ggplot2.yaml +++ /dev/null @@ -1,272 +0,0 @@ -library: ggplot2 -language: r -specification_id: line-navigator -created: '2026-05-27T11:37:05Z' -updated: '2026-05-27T11:44:08Z' -generated_by: claude-sonnet -workflow_run: 26508525153 -issue: 3785 -language_version: 4.4.1 -library_version: 3.5.1 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/line-navigator/r/ggplot2/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/line-navigator/r/ggplot2/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 86 -review: - strengths: - - 'Two-panel layout cleanly implements the navigator concept in a static context: - detail view on top, full-history overview strip below' - - Selection window correctly implemented with annotate("rect") — idiomatic ggplot2 - idiom for rectangular highlights - - Subtitle shows the selected date range as human-readable text, satisfying the - spec's 'show selected range as labels' requirement - - Navigator height ratio c(5,1) gives ~17% to the overview strip, squarely within - the spec's 15–20% guidance - - Theme tokens (PAGE_BG, INK, INK_SOFT) correctly wired throughout both panels — - both renders are fully readable - - 'Synthetic data is realistic: daily temperatures with seasonal variation, trend, - and cumulative noise over 2 years (730 points)' - - Dynamic title-size formula adapts the title fontsize to fill appropriate width - - Navigator grid correctly disabled (panel.grid.major = element_blank()), keeping - the overview strip clean and uncluttered - - Canvas exactly 3200×1800 px via ragg::agg_png at 8×4.5 in / 400 dpi - weaknesses: - - Main chart has both X and Y major grid lines; style guide recommends Y-axis grid - only for line charts — vertical grid lines add clutter without aiding reading - - dplyr is imported but not actually used (base R subsetting is used instead); scales - may also be an unused import - - Title size formula inflates to 19pt for a 42-char spec-id when 12pt would be appropriate; - formula is designed to shrink long titles, not enlarge short ones - - 'Dark render selection window appears as a nearly-opaque dark green block (alpha=0.22 - of #009E73 over #1A1A17) — harder to read as a ''highlighted range'' than in the - light render; consider a slightly higher alpha or a different fill approach for - dark mode' - - No spine removal beyond theme_minimal defaults; removing top spine explicitly - and relying on the L-shaped axis.line would be cleaner - - Library Mastery could be elevated by using grid.arrange more expressively (e.g., - shared x-axis alignment annotations or explicit panel spacing) - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct light surface, not pure white. - Chrome: Title "line-navigator · r · ggplot2 · anyplot.ai" in bold dark ink (~19pt), prominently readable. Subtitle "Detail view: Jun 01, 2023 – Aug 31, 2023" in softer gray (INK_SOFT). Y-axis label "Temperature (°C)" in dark ink. X-axis tick labels rotated 30° at size 7pt — small but readable at full resolution. Navigator strip x-axis label "Full history – Jan 2022 to Dec 2023" in INK_SOFT, readable. - Data: Brand green (#009E73) line in the main chart showing a summer 2023 temperature arc (33–38°C). Navigator strip shows full 2-year history as a thin green line with a semi-transparent green rectangle highlighting the Jun–Aug 2023 selection window. - Legibility verdict: PASS — all text clearly readable against the warm off-white background; no light-on-light issues. - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct dark surface, not pure black. - Chrome: Title in light near-white (#F0EFE8), clearly readable. Subtitle in lighter gray (#B8B7B0), readable. Y-axis and axis labels in appropriate light tokens. Tick labels in INK_SOFT (#B8B7B0). No dark-on-dark failures observed. - Data: Brand green (#009E73) data line is identical to the light render — same hue, same weight, confirming palette constancy. Selection window in the navigator renders as a darker solid green block (alpha=0.22 of #009E73 over near-black) — recognizable but less visually prominent than in the light render. - Legibility verdict: PASS — all text readable against the dark background; no near-black text on near-black background observed. - criteria_checklist: - visual_quality: - score: 29 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: Title, subtitle, axis labels all readable in both themes. X-axis - tick labels in main chart use size=7pt (slightly below recommended 8pt). - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text or data elements. Both panels well-spaced. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Main line clearly visible at linewidth=1.0. Navigator line thinner - (0.45, alpha=0.75) — appropriate for overview context. Selection window - visible in both themes. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Single series using brand green — no CVD conflict. No red-green pairing. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 'Canvas gate passed (3200x1800). Two-panel layout proportionate: - navigator ~17% of total height (within spec''s 15-20%). No overflow or clipping - observed.' - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: 'Y-axis: ''Temperature (°C)'' with units. Navigator x-axis: ''Full - history – Jan 2022 to Dec 2023''. Title format correct.' - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Brand green #009E73 used as first (only) series. PAGE_BG correctly - #FAF8F1 light / #1A1A17 dark. INK/INK_SOFT tokens correctly applied.' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Thoughtful navigator concept realized with annotate rect highlight, - compact overview strip, subtitle date range display. Above generic defaults - but not highly refined. - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: true - comment: Navigator grid disabled (clean). Main chart grid subtle (linewidth=0.15). - However, both X and Y grid lines present in main chart — style guide recommends - Y-only for line charts. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: 'Clear two-level hierarchy: detail on top, context below. Selection - window highlights focal range. Subtitle anchors the selected period. Good - viewer guidance.' - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: 'Correct chart type: line chart with miniature navigator pane. Two-panel - layout correctly implements the spec concept.' - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Detail view, full-history navigator, selection window, date text - labels, shared styling, proportional navigator height all present. Resize - handles and animated transitions omitted — not possible in static ggplot2, - acceptable limitation. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X=date, Y=temperature correctly mapped in both panels. Navigator - shows full extent; main chart shows selected range. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title 'line-navigator · r · ggplot2 · anyplot.ai' matches required - format. No legend needed for single series. - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 730 daily data points, large enough to make navigation meaningful. - Both detail and overview views showcase the navigator concept fully. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Daily temperature sensor over 2 years with trend, seasonal variation, - and cumulative noise — realistic and plausible. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Temperature 29–38°C over summer months is plausible. 2-year span - is appropriate for demonstrating navigator utility. - code_quality: - score: 9 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: No functions or classes. Clean linear procedural structure. Well-organized - sections. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: set.seed(42) present. - - id: CQ-03 - name: Clean Imports - score: 1 - max: 2 - passed: false - comment: dplyr is imported but not used (base R subsetting used throughout). - scales may also be an implicit dependency only. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Dynamic title size formula is appropriate. No fake interactivity. - Sectioned comments aid readability. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png via ragg::agg_png. Correct device usage. - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Correct use of theme layering, scale_x_date with date_labels/date_breaks, - expansion(), annotate('rect'), and element_blank(). gridExtra::grid.arrange - for multi-panel. Modern linewidth= not size=. - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: gridExtra::grid.arrange with heights= for proportional panel sizing; - annotate('rect') for selection window; scale_y_continuous(labels=NULL) to - suppress navigator y-ticks cleanly. - verdict: APPROVED -impl_tags: - dependencies: - - gridextra - techniques: - - subplots - - annotations - patterns: - - data-generation - dataprep: - - time-series - - cumulative-sum - styling: - - alpha-blending diff --git a/plots/line-navigator/specification.md b/plots/line-navigator/specification.md deleted file mode 100644 index 87e28a9b35..0000000000 --- a/plots/line-navigator/specification.md +++ /dev/null @@ -1,30 +0,0 @@ -# line-navigator: Line Chart with Mini Navigator - -## Description - -A line chart featuring a miniature overview pane at the bottom that serves as a navigator for exploring large time series datasets. The main chart displays the selected range in detail while the mini chart below shows the full data extent with a draggable selection window. Users can resize the selection handles to adjust the visible range, enabling intuitive exploration of long time series with smooth zoom transitions between the overview and detail views. - -## Applications - -- Exploring long time series data spanning months or years while maintaining context of the full history -- Stock chart analysis with a history overview for navigating between different market periods -- Sensor data visualization with zoom capability to investigate specific anomalies or patterns -- Log file timeline navigation to quickly jump between different time ranges while seeing overall activity - -## Data - -- `date` (datetime) - Time axis representing the temporal dimension -- `value` (numeric) - Data values to be visualized as a continuous line -- Size: 1000+ data points (designed for large datasets where navigation is essential) -- Example: Daily sensor readings over multiple years, hourly stock prices, system metrics over time - -## Notes - -- Main chart shows the selected range in full detail with appropriate axis formatting -- Mini chart below displays the entire data extent at reduced scale -- Draggable selection window in the navigator highlights the currently visible range -- Resize handles on selection edges for fine-grained range adjustment -- Smooth animated transitions when changing the selected range -- Both charts should share the same styling (line color, thickness) for visual consistency -- Navigator height should be proportionally smaller (15-20% of main chart height) -- Consider showing the selected date range as text labels diff --git a/plots/line-navigator/specification.yaml b/plots/line-navigator/specification.yaml deleted file mode 100644 index f0ddbf242b..0000000000 --- a/plots/line-navigator/specification.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# Specification-level metadata for line-navigator -# Auto-synced to PostgreSQL on push to main - -spec_id: line-navigator -title: Line Chart with Mini Navigator - -# Specification tracking -created: 2026-01-11T22:53:13Z -updated: null -issue: 3785 -suggested: MarkusNeusinger - -# Classification tags (applies to all library implementations) -# See docs/reference/tagging-system.md for detailed guidelines -tags: - plot_type: - - line - data_type: - - timeseries - - numeric - domain: - - general - - finance - - technology - features: - - interactive - - temporal - - navigation diff --git a/plots/linked-views-selection/implementations/python/altair.py b/plots/linked-views-selection/implementations/python/altair.py deleted file mode 100644 index 2013aa3ddf..0000000000 --- a/plots/linked-views-selection/implementations/python/altair.py +++ /dev/null @@ -1,159 +0,0 @@ -""" anyplot.ai -linked-views-selection: Multiple Linked Views with Selection Sync -Library: altair 6.1.0 | Python 3.13.13 -Quality: 93/100 | Updated: 2026-05-17 -""" - -import os -import sys - - -script_dir = os.path.dirname(os.path.abspath(__file__)) -if script_dir in sys.path: - sys.path.remove(script_dir) -sys.path = [p for p in sys.path if p not in ("", ".")] - -import altair as alt # noqa: E402 -import numpy as np # noqa: E402 -import pandas as pd # noqa: E402 - - -# 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" - -# Okabe-Ito palette (first three positions) -IMPRINT = ["#009E73", "#C475FD", "#4467A3"] - -# Data - Iris-like flower measurements with categories -np.random.seed(42) - -n_per_category = 50 -categories = ["Setosa", "Versicolor", "Virginica"] - -data = [] -for i, cat in enumerate(categories): - base_sepal_length = [5.0, 5.9, 6.6][i] - base_sepal_width = [3.4, 2.8, 3.0][i] - base_petal_length = [1.5, 4.3, 5.5][i] - - sepal_length = np.random.normal(base_sepal_length, 0.35, n_per_category) - sepal_width = np.random.normal(base_sepal_width, 0.35, n_per_category) - petal_length = np.random.normal(base_petal_length, 0.45, n_per_category) - - for j in range(n_per_category): - data.append( - { - "Sepal Length (cm)": sepal_length[j], - "Sepal Width (cm)": sepal_width[j], - "Petal Length (cm)": petal_length[j], - "Species": cat, - } - ) - -df = pd.DataFrame(data) - -# Color scale using Okabe-Ito -color_scale = alt.Scale(domain=categories, range=IMPRINT) - -# Selection mechanism -brush = alt.selection_interval() - -# Point styling -point_size = 150 -opacity_selected = 1.0 -opacity_unselected = 0.15 - -# Scatter plot: Sepal Length vs Sepal Width -scatter = ( - alt.Chart(df) - .mark_circle(size=point_size) - .encode( - x=alt.X("Sepal Length (cm):Q", scale=alt.Scale(zero=False)), - y=alt.Y("Sepal Width (cm):Q", scale=alt.Scale(zero=False)), - color=alt.condition( - brush, - alt.Color( - "Species:N", scale=color_scale, legend=alt.Legend(titleFontSize=18, labelFontSize=16, symbolSize=200) - ), - alt.value(INK_SOFT), - ), - opacity=alt.condition(brush, alt.value(opacity_selected), alt.value(opacity_unselected)), - tooltip=["Sepal Length (cm)", "Sepal Width (cm)", "Petal Length (cm)", "Species"], - ) - .properties(width=500, height=400, title=alt.Title("Sepal Dimensions", fontSize=20)) - .add_params(brush) -) - -# Histogram: Petal Length distribution -histogram = ( - alt.Chart(df) - .mark_bar(opacity=0.8) - .encode( - x=alt.X("Petal Length (cm):Q", bin=alt.Bin(maxbins=20)), - y=alt.Y("count():Q", title="Count"), - color=alt.condition(brush, alt.Color("Species:N", scale=color_scale, legend=None), alt.value(INK_SOFT)), - opacity=alt.condition(brush, alt.value(0.9), alt.value(opacity_unselected)), - ) - .properties(width=500, height=400, title=alt.Title("Petal Length Distribution", fontSize=20)) -) - -# Bar chart: Species count -bar_chart = ( - alt.Chart(df) - .mark_bar() - .encode( - x=alt.X("Species:N", title="Species", axis=alt.Axis(labelAngle=0)), - y=alt.Y("count():Q", title="Count"), - color=alt.condition(brush, alt.Color("Species:N", scale=color_scale, legend=None), alt.value(INK_SOFT)), - opacity=alt.condition(brush, alt.value(0.9), alt.value(opacity_unselected)), - ) - .properties(width=500, height=400, title=alt.Title("Species Distribution", fontSize=20)) -) - -# Combine charts -right_column = alt.vconcat(histogram, bar_chart).resolve_scale(color="shared") -combined = alt.hconcat(scatter, right_column).resolve_scale(color="shared") - -# Final chart with theme-adaptive styling -chart = ( - combined.properties( - background=PAGE_BG, - title=alt.Title( - "linked-views-selection · altair · anyplot.ai", - fontSize=28, - anchor="middle", - color=INK, - subtitle="Brush on scatter plot to filter all views | Click and drag to select", - subtitleFontSize=16, - subtitleColor=INK_SOFT, - ), - ) - .configure_axis( - labelFontSize=14, - labelColor=INK_SOFT, - titleFontSize=16, - titleColor=INK, - gridOpacity=0.10, - gridColor=INK, - domainColor=INK_SOFT, - tickColor=INK_SOFT, - ) - .configure_view(fill=PAGE_BG, strokeWidth=0) - .configure_legend( - fillColor=ELEVATED_BG, - strokeColor=INK_SOFT, - labelColor=INK_SOFT, - titleColor=INK, - titleFontSize=18, - labelFontSize=16, - ) - .configure_title(color=INK) -) - -# Save outputs -chart.save(f"plot-{THEME}.png", scale_factor=3.0) -chart.save(f"plot-{THEME}.html") diff --git a/plots/linked-views-selection/implementations/python/bokeh.py b/plots/linked-views-selection/implementations/python/bokeh.py deleted file mode 100644 index bba01ebaed..0000000000 --- a/plots/linked-views-selection/implementations/python/bokeh.py +++ /dev/null @@ -1,412 +0,0 @@ -""" anyplot.ai -linked-views-selection: Multiple Linked Views with Selection Sync -Library: bokeh 3.9.0 | Python 3.13.13 -Quality: 90/100 | Updated: 2026-05-17 -""" - -import os -import time -from pathlib import Path - -import numpy as np -import pandas as pd -from bokeh.io import output_file, save -from bokeh.layouts import column, gridplot, row -from bokeh.models import Button, ColumnDataSource, CustomJS, Div -from bokeh.plotting import figure -from bokeh.transform import factor_cmap -from selenium import webdriver -from selenium.webdriver.chrome.options import Options - - -# 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" - -# Okabe-Ito palette -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233"] - -# Data - using iris-like multivariate data -np.random.seed(42) -n_points = 150 - -categories = ["Species A", "Species B", "Species C"] -category_list = [] -x_data = [] -y_data = [] -value_data = [] - -for i, cat in enumerate(categories): - n = 50 - category_list.extend([cat] * n) - x_data.extend(np.random.normal(loc=4 + i * 2, scale=0.6, size=n)) - y_data.extend(np.random.normal(loc=2 + i * 1.5, scale=0.5, size=n)) - value_data.extend(np.random.normal(loc=10 + i * 5, scale=2, size=n)) - -df = pd.DataFrame({"x": x_data, "y": y_data, "category": category_list, "value": value_data}) - -# Create ColumnDataSource - the key to linked views in Bokeh -source = ColumnDataSource( - data={"x": df["x"].values, "y": df["y"].values, "category": df["category"].values, "value": df["value"].values} -) - -# Color mapping for categories using Okabe-Ito -color_palette = IMPRINT[:3] -color_mapper = factor_cmap("category", palette=color_palette, factors=categories) - -# Create scatter plot (main selection view) -scatter = figure( - width=2400, - height=1350, - title="Scatter Plot - Use Box Select or Lasso to Select Points", - x_axis_label="Sepal Length (cm)", - y_axis_label="Sepal Width (cm)", - tools="pan,wheel_zoom,reset,box_select,lasso_select,tap", -) - -scatter_renderer = scatter.scatter( - "x", - "y", - source=source, - size=15, - color=color_mapper, - alpha=0.8, - selection_color=IMPRINT[1], - selection_alpha=1.0, - nonselection_alpha=0.15, - nonselection_color=INK_SOFT, -) - -# Style scatter plot -scatter.background_fill_color = PAGE_BG -scatter.border_fill_color = PAGE_BG -scatter.outline_line_color = INK_SOFT -scatter.title.text_font_size = "28pt" -scatter.title.text_color = INK -scatter.xaxis.axis_label_text_font_size = "22pt" -scatter.yaxis.axis_label_text_font_size = "22pt" -scatter.xaxis.axis_label_text_color = INK -scatter.yaxis.axis_label_text_color = INK -scatter.xaxis.major_label_text_font_size = "18pt" -scatter.yaxis.major_label_text_font_size = "18pt" -scatter.xaxis.major_label_text_color = INK_SOFT -scatter.yaxis.major_label_text_color = INK_SOFT -scatter.xaxis.axis_line_color = INK_SOFT -scatter.yaxis.axis_line_color = INK_SOFT -scatter.xgrid.grid_line_color = INK -scatter.xgrid.grid_line_alpha = 0.10 -scatter.ygrid.grid_line_color = INK -scatter.ygrid.grid_line_alpha = 0.10 - -# Create histogram of values -hist_values, hist_edges = np.histogram(df["value"], bins=20) -hist_source = ColumnDataSource(data={"top": hist_values, "left": hist_edges[:-1], "right": hist_edges[1:]}) - -histogram = figure( - width=2400, - height=1350, - title="Value Distribution - Updates with Selection", - x_axis_label="Value (units)", - y_axis_label="Count", - tools="pan,wheel_zoom,reset", -) - -histogram.quad( - top="top", - bottom=0, - left="left", - right="right", - source=hist_source, - fill_color=IMPRINT[0], - line_color=PAGE_BG, - alpha=0.8, -) - -# Style histogram -histogram.background_fill_color = PAGE_BG -histogram.border_fill_color = PAGE_BG -histogram.outline_line_color = INK_SOFT -histogram.title.text_font_size = "28pt" -histogram.title.text_color = INK -histogram.xaxis.axis_label_text_font_size = "22pt" -histogram.yaxis.axis_label_text_font_size = "22pt" -histogram.xaxis.axis_label_text_color = INK -histogram.yaxis.axis_label_text_color = INK -histogram.xaxis.major_label_text_font_size = "18pt" -histogram.yaxis.major_label_text_font_size = "18pt" -histogram.xaxis.major_label_text_color = INK_SOFT -histogram.yaxis.major_label_text_color = INK_SOFT -histogram.xaxis.axis_line_color = INK_SOFT -histogram.yaxis.axis_line_color = INK_SOFT -histogram.xgrid.grid_line_color = INK -histogram.xgrid.grid_line_alpha = 0.10 -histogram.ygrid.grid_line_color = INK -histogram.ygrid.grid_line_alpha = 0.10 - -# Create bar chart by category -category_counts = df["category"].value_counts() -bar_source = ColumnDataSource( - data={"categories": categories, "counts": [category_counts.get(c, 0) for c in categories], "colors": color_palette} -) - -bar_chart = figure( - width=2400, - height=1350, - x_range=categories, - title="Category Distribution - Updates with Selection", - x_axis_label="Category", - y_axis_label="Count", - tools="pan,wheel_zoom,reset", -) - -bar_chart.vbar( - x="categories", - top="counts", - width=0.6, - source=bar_source, - color="colors", - alpha=0.8, - line_color=PAGE_BG, - line_width=2, -) - -# Style bar chart -bar_chart.background_fill_color = PAGE_BG -bar_chart.border_fill_color = PAGE_BG -bar_chart.outline_line_color = INK_SOFT -bar_chart.title.text_font_size = "28pt" -bar_chart.title.text_color = INK -bar_chart.xaxis.axis_label_text_font_size = "22pt" -bar_chart.yaxis.axis_label_text_font_size = "22pt" -bar_chart.xaxis.axis_label_text_color = INK -bar_chart.yaxis.axis_label_text_color = INK -bar_chart.xaxis.major_label_text_font_size = "18pt" -bar_chart.yaxis.major_label_text_font_size = "18pt" -bar_chart.xaxis.major_label_text_color = INK_SOFT -bar_chart.yaxis.major_label_text_color = INK_SOFT -bar_chart.xaxis.axis_line_color = INK_SOFT -bar_chart.yaxis.axis_line_color = INK_SOFT -bar_chart.xgrid.grid_line_color = None -bar_chart.ygrid.grid_line_color = INK -bar_chart.ygrid.grid_line_alpha = 0.10 - -# Create second scatter plot (value vs y) -scatter2 = figure( - width=2400, - height=1350, - title="Value vs Sepal Width - Linked Selection", - x_axis_label="Value (units)", - y_axis_label="Sepal Width (cm)", - tools="pan,wheel_zoom,reset,box_select,lasso_select,tap", -) - -scatter2.scatter( - "value", - "y", - source=source, - size=15, - color=color_mapper, - alpha=0.8, - selection_color=IMPRINT[1], - selection_alpha=1.0, - nonselection_alpha=0.15, - nonselection_color=INK_SOFT, -) - -# Style second scatter -scatter2.background_fill_color = PAGE_BG -scatter2.border_fill_color = PAGE_BG -scatter2.outline_line_color = INK_SOFT -scatter2.title.text_font_size = "28pt" -scatter2.title.text_color = INK -scatter2.xaxis.axis_label_text_font_size = "22pt" -scatter2.yaxis.axis_label_text_font_size = "22pt" -scatter2.xaxis.axis_label_text_color = INK -scatter2.yaxis.axis_label_text_color = INK -scatter2.xaxis.major_label_text_font_size = "18pt" -scatter2.yaxis.major_label_text_font_size = "18pt" -scatter2.xaxis.major_label_text_color = INK_SOFT -scatter2.yaxis.major_label_text_color = INK_SOFT -scatter2.xaxis.axis_line_color = INK_SOFT -scatter2.yaxis.axis_line_color = INK_SOFT -scatter2.xgrid.grid_line_color = INK -scatter2.xgrid.grid_line_alpha = 0.10 -scatter2.ygrid.grid_line_color = INK -scatter2.ygrid.grid_line_alpha = 0.10 - -# JavaScript callback to update histogram and bar chart on selection -callback = CustomJS( - args={"source": source, "hist_source": hist_source, "bar_source": bar_source}, - code=""" - const indices = source.selected.indices; - const data = source.data; - const hist_data = hist_source.data; - const bar_data = bar_source.data; - - if (indices.length === 0) { - // Reset histogram - const values = data['value']; - const min_val = Math.min(...values); - const max_val = Math.max(...values); - const n_bins = 20; - const bin_width = (max_val - min_val) / n_bins; - - const counts = new Array(n_bins).fill(0); - const left = []; - const right = []; - - for (let i = 0; i < n_bins; i++) { - left.push(min_val + i * bin_width); - right.push(min_val + (i + 1) * bin_width); - } - - for (let i = 0; i < values.length; i++) { - const bin_idx = Math.min(Math.floor((values[i] - min_val) / bin_width), n_bins - 1); - counts[bin_idx]++; - } - - hist_data['top'] = counts; - hist_data['left'] = left; - hist_data['right'] = right; - - // Reset bar chart - const categories = ['Species A', 'Species B', 'Species C']; - const cat_counts = new Array(categories.length).fill(0); - for (let i = 0; i < data['category'].length; i++) { - const cat_idx = categories.indexOf(data['category'][i]); - if (cat_idx >= 0) cat_counts[cat_idx]++; - } - bar_data['counts'] = cat_counts; - } else { - // Update histogram with selected values - const selected_values = indices.map(i => data['value'][i]); - const min_val = Math.min(...data['value']); - const max_val = Math.max(...data['value']); - const n_bins = 20; - const bin_width = (max_val - min_val) / n_bins; - - const counts = new Array(n_bins).fill(0); - const left = []; - const right = []; - - for (let i = 0; i < n_bins; i++) { - left.push(min_val + i * bin_width); - right.push(min_val + (i + 1) * bin_width); - } - - for (const val of selected_values) { - const bin_idx = Math.min(Math.floor((val - min_val) / bin_width), n_bins - 1); - counts[bin_idx]++; - } - - hist_data['top'] = counts; - hist_data['left'] = left; - hist_data['right'] = right; - - // Update bar chart with selected categories - const categories = ['Species A', 'Species B', 'Species C']; - const cat_counts = new Array(categories.length).fill(0); - for (const idx of indices) { - const cat_idx = categories.indexOf(data['category'][idx]); - if (cat_idx >= 0) cat_counts[cat_idx]++; - } - bar_data['counts'] = cat_counts; - } - - hist_source.change.emit(); - bar_source.change.emit(); -""", -) - -source.selected.js_on_change("indices", callback) - -# Clear Selection button -clear_button = Button(label="Clear Selection", button_type="warning", width=200, height=50) -clear_callback = CustomJS( - args={ - "source": source, - "hist_source": hist_source, - "bar_source": bar_source, - "df_value": df["value"].values.tolist(), - }, - code=""" - source.selected.indices = []; - - // Reset histogram - const values = df_value; - const min_val = Math.min(...values); - const max_val = Math.max(...values); - const n_bins = 20; - const bin_width = (max_val - min_val) / n_bins; - - const counts = new Array(n_bins).fill(0); - const left = []; - const right = []; - - for (let i = 0; i < n_bins; i++) { - left.push(min_val + i * bin_width); - right.push(min_val + (i + 1) * bin_width); - } - - for (let i = 0; i < values.length; i++) { - const bin_idx = Math.min(Math.floor((values[i] - min_val) / bin_width), n_bins - 1); - counts[bin_idx]++; - } - - hist_source.data['top'] = counts; - hist_source.data['left'] = left; - hist_source.data['right'] = right; - hist_source.change.emit(); - - // Reset bar chart - const categories = ['Species A', 'Species B', 'Species C']; - bar_source.data['counts'] = [50, 50, 50]; - bar_source.change.emit(); - - source.change.emit(); -""", -) -clear_button.js_on_click(clear_callback) - -# Title as Div element -title_div = Div( - text=f"

linked-views-selection · bokeh · anyplot.ai

", - width=4800, -) - -# Button container centered -button_div = Div(text="", width=2200) -button_row = row(button_div, clear_button, width=4800) - -# Create grid layout -grid = gridplot([[scatter, scatter2], [histogram, bar_chart]], merge_tools=True) - -layout = column(title_div, button_row, grid) - -# Save as HTML (interactive) -output_file(f"plot-{THEME}.html") -save(layout) - -# Screenshot with Selenium -W, H = 4800, 2700 -opts = Options() -for arg in ( - "--headless=new", - "--no-sandbox", - "--disable-dev-shm-usage", - "--disable-gpu", - f"--window-size={W},{H}", - "--hide-scrollbars", -): - opts.add_argument(arg) - -driver = webdriver.Chrome(options=opts) -driver.set_window_size(W, H) -driver.get(f"file://{Path(f'plot-{THEME}.html').resolve()}") -time.sleep(3) -driver.save_screenshot(f"plot-{THEME}.png") -driver.quit() diff --git a/plots/linked-views-selection/implementations/python/letsplot.py b/plots/linked-views-selection/implementations/python/letsplot.py deleted file mode 100644 index 12070620f8..0000000000 --- a/plots/linked-views-selection/implementations/python/letsplot.py +++ /dev/null @@ -1,210 +0,0 @@ -""" anyplot.ai -linked-views-selection: Multiple Linked Views with Selection Sync -Library: letsplot 4.9.0 | Python 3.13.13 -Quality: 81/100 | Updated: 2026-05-17 -""" - -import numpy as np -import pandas as pd -from lets_plot import ( - LetsPlot, - aes, - element_blank, - element_line, - element_text, - geom_bar, - geom_histogram, - geom_point, - geom_rect, - geom_text, - gggrid, - ggplot, - ggsize, - ggtitle, - labs, - layer_tooltips, - scale_alpha_identity, - scale_color_manual, - scale_fill_manual, - scale_x_continuous, - scale_y_continuous, - theme, - theme_minimal, -) -from lets_plot.export import ggsave - - -LetsPlot.setup_html() - -# Data - Multivariate dataset with numeric and categorical columns -np.random.seed(42) -n = 200 - -# Create 4 clusters with different characteristics -categories = np.repeat(["Cluster A", "Cluster B", "Cluster C", "Cluster D"], n // 4) -cluster_centers_x = {"Cluster A": 25, "Cluster B": 60, "Cluster C": 35, "Cluster D": 70} -cluster_centers_y = {"Cluster A": 70, "Cluster B": 30, "Cluster C": 45, "Cluster D": 65} - -x = np.array([cluster_centers_x[c] + np.random.randn() * 8 for c in categories]) -y = np.array([cluster_centers_y[c] + np.random.randn() * 8 for c in categories]) -value = x * 0.8 + np.random.randn(n) * 10 + 40 - -# Define brush selection region - selects Cluster A and part of Cluster C -brush_xmin, brush_xmax = 15, 45 -brush_ymin, brush_ymax = 55, 85 - -# Determine which points are selected by the brush -selected = (x >= brush_xmin) & (x <= brush_xmax) & (y >= brush_ymin) & (y <= brush_ymax) -point_alpha = np.where(selected, 0.9, 0.2) - -df = pd.DataFrame( - {"x": x, "y": y, "category": categories, "value": value, "selected": selected, "point_alpha": point_alpha} -) - -# Colors - colorblind-friendly palette with distinct hues -colors = ["#AE3030", "#2ABCCD", "#009E73", "#BD8233"] # Orange, Sky Blue, Teal, Pink - -n_selected = selected.sum() -n_total = len(df) - -# Common theme scaled for 4800x2700 output -common_theme = theme_minimal() + theme( - axis_title=element_text(size=18), - axis_text=element_text(size=14), - plot_title=element_text(size=20, face="bold"), - legend_text=element_text(size=14), - legend_title=element_text(size=16), - panel_grid_major=element_line(color="#E5E5E5", size=0.5), - panel_grid_minor=element_blank(), -) - -# Brush rectangle data -brush_df = pd.DataFrame({"xmin": [brush_xmin], "xmax": [brush_xmax], "ymin": [brush_ymin], "ymax": [brush_ymax]}) - -# Tooltips for scatter plot -tooltips = ( - layer_tooltips() - .title("@category") - .line("X: @x") - .line("Y: @y") - .line("Value: @value") - .line("Selected: @selected") - .format("x", ".1f") - .format("y", ".1f") - .format("value", ".1f") -) - -# 1. SCATTER PLOT with brush selection rectangle -scatter = ( - ggplot(df, aes("x", "y")) - # Brush selection rectangle - + geom_rect( - aes(xmin="xmin", xmax="xmax", ymin="ymin", ymax="ymax"), - data=brush_df, - inherit_aes=False, - fill="#3B82F6", - alpha=0.12, - color="#3B82F6", - linetype="dashed", - size=1.5, - ) - # Points with selection-based alpha - + geom_point(aes(color="category", alpha="point_alpha"), size=6, tooltips=tooltips) - + scale_color_manual(values=colors) - + scale_alpha_identity() - + labs(x="Measurement X", y="Measurement Y", title=f"Scatter Plot ({n_selected}/{n_total} selected)") - + common_theme - + theme(legend_position="none") - + ggsize(750, 550) -) - -# 2. HISTOGRAM with linked selection highlighting -# Create separate data for selected and unselected, stacking selected on top -df_hist_unselected = df[~df["selected"]].copy() -df_hist_unselected["selection_state"] = "Unselected" -df_hist_selected = df[df["selected"]].copy() -df_hist_selected["selection_state"] = "Selected" - -# Plot unselected first (gray, low alpha), then selected (colored, high alpha) -histogram = ( - ggplot(df_hist_unselected, aes("value")) - + geom_histogram(bins=20, fill="#CCCCCC", alpha=0.5, color="white", size=0.3) - + geom_histogram(aes("value"), data=df_hist_selected, bins=20, fill="#3B82F6", alpha=0.85, color="white", size=0.3) - + labs(x="Value", y="Count", title="Value Distribution") - + common_theme - + theme(legend_position="none") - + ggsize(750, 550) -) - -# 3. BAR CHART with linked selection highlighting -# Calculate counts per category, split by selection state -category_counts_all = df.groupby("category").size().reset_index(name="total") -category_counts_selected = df[df["selected"]].groupby("category").size().reset_index(name="selected_count") -category_counts = category_counts_all.merge(category_counts_selected, on="category", how="left").fillna(0) -category_counts["unselected_count"] = category_counts["total"] - category_counts["selected_count"] - -# Create stacked bar data -bar_data = [] -for _, row in category_counts.iterrows(): - cat = row["category"] - bar_data.append({"category": cat, "count": row["unselected_count"], "state": "Unselected"}) - bar_data.append({"category": cat, "count": row["selected_count"], "state": "Selected"}) -bar_df = pd.DataFrame(bar_data) - -bar = ( - ggplot(bar_df, aes("category", "count", fill="state")) - + geom_bar(stat="identity", position="stack", alpha=0.85, width=0.7) - + scale_fill_manual(values={"Selected": "#3B82F6", "Unselected": "#CCCCCC"}, name="Selection") - + labs(x="Category", y="Count", title="Category Distribution") - + common_theme - + theme(legend_position="bottom") - + ggsize(750, 550) -) - -# 4. SUMMARY panel with selection information and legend -summary_data = [] -for i, (cat, color) in enumerate(zip(["Cluster A", "Cluster B", "Cluster C", "Cluster D"], colors, strict=True)): - cat_selected = df[(df["category"] == cat) & df["selected"]].shape[0] - cat_total = df[df["category"] == cat].shape[0] - summary_data.append( - {"category": cat, "x": 1, "y": 4 - i, "label": f"{cat}: {cat_selected}/{cat_total}", "color": color} - ) - -summary_df = pd.DataFrame(summary_data) - -summary_panel = ( - ggplot(summary_df, aes("x", "y", color="category")) - + geom_point(size=12) - + geom_text(aes(label="label"), hjust=0, nudge_x=0.12, size=14) - + scale_color_manual(values=colors) - + scale_x_continuous(limits=[0.5, 3]) - + scale_y_continuous(limits=[0, 5]) - + labs(title=f"Selection Summary\n{n_selected} of {n_total} points") - + theme_minimal() - + theme( - plot_title=element_text(size=18, face="bold"), - axis_text=element_blank(), - axis_title=element_blank(), - axis_ticks=element_blank(), - panel_grid=element_blank(), - legend_position="none", - ) - + ggsize(500, 550) -) - -# Combine all plots using gggrid for linked views layout -combined = gggrid([scatter, histogram, bar, summary_panel], ncol=2, align=True) - -# Add overall title -combined = ( - combined - + ggtitle("linked-views-selection · letsplot · pyplots.ai") - + ggsize(1600, 900) - + theme(plot_title=element_text(size=26, face="bold")) -) - -# Save as PNG (scaled 3x for 4800x2700 px) -ggsave(combined, "plot.png", path=".", scale=3) - -# Save as HTML (interactive version) -ggsave(combined, "plot.html", path=".") diff --git a/plots/linked-views-selection/implementations/python/matplotlib.py b/plots/linked-views-selection/implementations/python/matplotlib.py deleted file mode 100644 index ec64ec1166..0000000000 --- a/plots/linked-views-selection/implementations/python/matplotlib.py +++ /dev/null @@ -1,116 +0,0 @@ -""" anyplot.ai -linked-views-selection: Multiple Linked Views with Selection Sync -Library: matplotlib 3.10.9 | Python 3.13.13 -Quality: 96/100 | Created: 2026-05-17 -""" - -import os -from pathlib import Path - -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd -from matplotlib.patches import Patch - - -# 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" - -# Okabe-Ito palette -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233"] -BRAND = IMPRINT[0] - -# Data -np.random.seed(42) -n = 300 -x = np.random.normal(50, 15, n) -y = x * 0.7 + np.random.normal(0, 10, n) -categories = np.random.choice(["Group A", "Group B", "Group C", "Group D"], n) -df = pd.DataFrame({"x": x, "y": y, "category": categories}) - -# Highlight one category to demonstrate linked views concept -highlighted_category = "Group A" -is_selected = df["category"] == highlighted_category - -# Plot -fig = plt.figure(figsize=(16, 9), facecolor=PAGE_BG) -gs = fig.add_gridspec(2, 3, hspace=0.3, wspace=0.3) - -ax_scatter = fig.add_subplot(gs[:, 0:2]) -ax_hist_x = fig.add_subplot(gs[0, 2]) -ax_hist_y = fig.add_subplot(gs[1, 2]) - -# Set background for all axes -ax_scatter.set_facecolor(PAGE_BG) -ax_hist_x.set_facecolor(PAGE_BG) -ax_hist_y.set_facecolor(PAGE_BG) - -# Scatter plot: full data with deemphasized points, selected highlighted -ax_scatter.scatter(df["x"], df["y"], alpha=0.15, s=100, color=INK_SOFT, edgecolors="none") -ax_scatter.scatter( - df[is_selected]["x"], df[is_selected]["y"], alpha=0.8, s=150, color=BRAND, edgecolors=PAGE_BG, linewidth=0.5 -) - -ax_scatter.set_xlabel("X Dimension", fontsize=20, color=INK) -ax_scatter.set_ylabel("Y Dimension", fontsize=20, color=INK) -ax_scatter.set_title("Scatter Plot", fontsize=18, color=INK_SOFT) -ax_scatter.tick_params(axis="both", labelsize=16, colors=INK_SOFT) -ax_scatter.spines["top"].set_visible(False) -ax_scatter.spines["right"].set_visible(False) -for s in ("left", "bottom"): - ax_scatter.spines[s].set_color(INK_SOFT) -ax_scatter.yaxis.grid(True, alpha=0.10, linewidth=0.8, color=INK) - -# Histogram of X dimension -bins = np.linspace(df["x"].min(), df["x"].max(), 20) -ax_hist_x.hist(df["x"], bins=bins, alpha=0.15, color=INK_SOFT, edgecolor="none") -ax_hist_x.hist(df[is_selected]["x"], bins=bins, alpha=0.8, color=BRAND, edgecolor="none") -ax_hist_x.set_xlabel("X", fontsize=16, color=INK) -ax_hist_x.set_ylabel("Count", fontsize=16, color=INK) -ax_hist_x.set_title("X Distribution", fontsize=18, color=INK_SOFT) -ax_hist_x.tick_params(axis="both", labelsize=14, colors=INK_SOFT) -ax_hist_x.spines["top"].set_visible(False) -ax_hist_x.spines["right"].set_visible(False) -for s in ("left", "bottom"): - ax_hist_x.spines[s].set_color(INK_SOFT) - -# Histogram of Y dimension -bins = np.linspace(df["y"].min(), df["y"].max(), 20) -ax_hist_y.hist(df["y"], bins=bins, alpha=0.15, color=INK_SOFT, edgecolor="none") -ax_hist_y.hist(df[is_selected]["y"], bins=bins, alpha=0.8, color=BRAND, edgecolor="none") -ax_hist_y.set_xlabel("Y", fontsize=16, color=INK) -ax_hist_y.set_ylabel("Count", fontsize=16, color=INK) -ax_hist_y.set_title("Y Distribution", fontsize=18, color=INK_SOFT) -ax_hist_y.tick_params(axis="both", labelsize=14, colors=INK_SOFT) -ax_hist_y.spines["top"].set_visible(False) -ax_hist_y.spines["right"].set_visible(False) -for s in ("left", "bottom"): - ax_hist_y.spines[s].set_color(INK_SOFT) - -# Main title -fig.suptitle("linked-views-selection · matplotlib · anyplot.ai", fontsize=24, fontweight="medium", color=INK, y=0.98) - -# Add legend to explain highlighting -legend_elements = [ - Patch(facecolor=INK_SOFT, alpha=0.15, label="Other data"), - Patch(facecolor=BRAND, alpha=0.8, label=f"Selected: {highlighted_category}"), -] -fig.legend( - handles=legend_elements, - loc="lower center", - fontsize=16, - frameon=True, - fancybox=False, - shadow=False, - bbox_to_anchor=(0.5, -0.02), - facecolor=ELEVATED_BG, - edgecolor=INK_SOFT, -) - -script_dir = Path(__file__).parent -output_path = script_dir / f"plot-{THEME}.png" -plt.savefig(output_path, dpi=300, bbox_inches="tight", facecolor=PAGE_BG) diff --git a/plots/linked-views-selection/implementations/python/plotly.py b/plots/linked-views-selection/implementations/python/plotly.py deleted file mode 100644 index bdb8e45f59..0000000000 --- a/plots/linked-views-selection/implementations/python/plotly.py +++ /dev/null @@ -1,281 +0,0 @@ -""" anyplot.ai -linked-views-selection: Multiple Linked Views with Selection Sync -Library: plotly 6.7.0 | Python 3.13.13 -Quality: 76/100 | Updated: 2026-05-17 -""" - -import numpy as np -import plotly.graph_objects as go -from plotly.subplots import make_subplots - - -# Data - Iris-like multivariate dataset with clear clusters -np.random.seed(42) - -# Create 3 distinct groups with different characteristics -n_per_group = 50 -categories = ["Setosa", "Versicolor", "Virginica"] -colors = ["#306998", "#FFD43B", "#E53935"] -deselected_colors = ["rgba(48,105,152,0.2)", "rgba(255,212,59,0.2)", "rgba(229,57,53,0.2)"] - -# Generate clustered data -sepal_length = np.concatenate( - [ - np.random.normal(5.0, 0.35, n_per_group), - np.random.normal(5.9, 0.50, n_per_group), - np.random.normal(6.6, 0.60, n_per_group), - ] -) -sepal_width = np.concatenate( - [ - np.random.normal(3.4, 0.38, n_per_group), - np.random.normal(2.8, 0.30, n_per_group), - np.random.normal(3.0, 0.32, n_per_group), - ] -) -petal_length = np.concatenate( - [ - np.random.normal(1.5, 0.17, n_per_group), - np.random.normal(4.3, 0.45, n_per_group), - np.random.normal(5.5, 0.55, n_per_group), - ] -) -category = np.repeat(categories, n_per_group) -point_indices = np.arange(len(sepal_length)) - -# Create subplots: scatter plot, histogram, and bar chart -fig = make_subplots( - rows=2, - cols=2, - specs=[[{"colspan": 2}, None], [{}, {}]], - subplot_titles=("Sepal Dimensions by Species", "Petal Length Distribution", "Species Count"), - vertical_spacing=0.15, - horizontal_spacing=0.12, -) - -# Add scatter plot for each category (top row, spans both columns) -for i, cat in enumerate(categories): - mask = category == cat - fig.add_trace( - go.Scatter( - x=sepal_length[mask], - y=sepal_width[mask], - mode="markers", - marker={"size": 14, "color": colors[i], "opacity": 0.8, "line": {"width": 1, "color": "white"}}, - name=cat, - legendgroup=cat, - customdata=np.column_stack([point_indices[mask], np.full(mask.sum(), i)]), - hovertemplate=f"{cat}
Sepal Length: %{{x:.2f}} cm
Sepal Width: %{{y:.2f}} cm", - selected={"marker": {"opacity": 1.0, "size": 16}}, - unselected={"marker": {"opacity": 0.2, "size": 10}}, - ), - row=1, - col=1, - ) - -# Add histogram for petal length (bottom left) - use bar for better selection control -for i, cat in enumerate(categories): - mask = category == cat - petal_data = petal_length[mask] - hist, bin_edges = np.histogram(petal_data, bins=15, range=(0, 7)) - bin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2 - fig.add_trace( - go.Bar( - x=bin_centers, - y=hist, - width=bin_edges[1] - bin_edges[0], - name=cat, - marker={"color": colors[i], "opacity": 0.7, "line": {"width": 1, "color": "white"}}, - legendgroup=cat, - showlegend=False, - hovertemplate=f"{cat}
Petal Length: %{{x:.2f}} cm
Count: %{{y}}", - customdata=np.full(len(hist), i), - ), - row=2, - col=1, - ) - -# Add bar chart for species count (bottom right) -counts = [n_per_group] * 3 -fig.add_trace( - go.Bar( - x=categories, - y=counts, - marker={"color": colors, "opacity": 0.85, "line": {"width": 2, "color": "white"}}, - showlegend=False, - hovertemplate="%{x}
Count: %{y}", - customdata=list(range(3)), - ), - row=2, - col=2, -) - -# Update layout for linked selection -fig.update_layout( - title={ - "text": "linked-views-selection · plotly · pyplots.ai", - "font": {"size": 32, "color": "#333333"}, - "x": 0.5, - "xanchor": "center", - }, - template="plotly_white", - font={"size": 18}, - legend={ - "title": {"text": "Species", "font": {"size": 20}}, - "font": {"size": 18}, - "orientation": "h", - "yanchor": "bottom", - "y": 1.02, - "xanchor": "center", - "x": 0.5, - "itemsizing": "constant", - }, - barmode="overlay", - hovermode="closest", - dragmode="select", - margin={"l": 80, "r": 80, "t": 140, "b": 100}, - annotations=[ - { - "text": "Use box/lasso select on scatter plot to highlight species across all views | Double-click to reset", - "xref": "paper", - "yref": "paper", - "x": 0.5, - "y": -0.10, - "showarrow": False, - "font": {"size": 16, "color": "#666666"}, - "xanchor": "center", - } - ], -) - -# Update subplot titles font size -for annotation in fig.layout.annotations: - if annotation.text in ["Sepal Dimensions by Species", "Petal Length Distribution", "Species Count"]: - annotation.font = {"size": 22} - -# Update axes -fig.update_xaxes( - title={"text": "Sepal Length (cm)", "font": {"size": 20}}, - tickfont={"size": 16}, - gridcolor="rgba(0,0,0,0.1)", - row=1, - col=1, -) -fig.update_yaxes( - title={"text": "Sepal Width (cm)", "font": {"size": 20}}, - tickfont={"size": 16}, - gridcolor="rgba(0,0,0,0.1)", - row=1, - col=1, -) -fig.update_xaxes( - title={"text": "Petal Length (cm)", "font": {"size": 20}}, - tickfont={"size": 16}, - gridcolor="rgba(0,0,0,0.1)", - row=2, - col=1, -) -fig.update_yaxes( - title={"text": "Count", "font": {"size": 20}}, tickfont={"size": 16}, gridcolor="rgba(0,0,0,0.1)", row=2, col=1 -) -fig.update_xaxes(title={"text": "Species", "font": {"size": 20}}, tickfont={"size": 16}, row=2, col=2) -fig.update_yaxes( - title={"text": "Count", "font": {"size": 20}}, tickfont={"size": 16}, gridcolor="rgba(0,0,0,0.1)", row=2, col=2 -) - -# Configure select/lasso behavior -fig.update_layout(selectdirection="any", newselection={"line": {"color": "#306998", "width": 2}}) - -# Save PNG -fig.write_image("plot.png", width=1600, height=900, scale=3) - -# JavaScript for true cross-view linked selection -linked_selection_js = """ - -""" - -# Save HTML with linked selection JavaScript -html_content = fig.to_html(include_plotlyjs=True, full_html=True, div_id="plotly-graph") -html_content = html_content.replace("", linked_selection_js + "") - -with open("plot.html", "w", encoding="utf-8") as f: - f.write(html_content) diff --git a/plots/linked-views-selection/implementations/python/plotnine.py b/plots/linked-views-selection/implementations/python/plotnine.py deleted file mode 100644 index 5c3e0c9053..0000000000 --- a/plots/linked-views-selection/implementations/python/plotnine.py +++ /dev/null @@ -1,114 +0,0 @@ -""" anyplot.ai -linked-views-selection: Multiple Linked Views with Selection Sync -Library: plotnine 0.15.4 | Python 3.13.13 -Quality: 84/100 | Created: 2026-05-23 -""" - -import os -import sys - - -# Remove the script's own directory from sys.path so 'plotnine' resolves to -# the installed package, not this file (which shares the same name). -_here = os.path.dirname(os.path.abspath(__file__)) -sys.path = [p for p in sys.path if os.path.abspath(p) != _here] - -import pandas as pd -from plotnine import ( - aes, - element_blank, - element_line, - element_rect, - element_text, - facet_wrap, - geom_point, - ggplot, - guides, - labs, - scale_alpha_manual, - scale_color_manual, - theme, -) -from sklearn.datasets import load_iris - - -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" - -SELECTED_COLOR = "#009E73" -UNSELECTED_COLOR = "#4A4A44" if THEME == "light" else "#B8B7B0" - -# Data — iris dataset with setosa "brushed" / selected across all views -iris = load_iris(as_frame=True) -df = iris.frame -df.columns = ["sepal_length", "sepal_width", "petal_length", "petal_width", "species_id"] -df["selection"] = df["species_id"].apply(lambda s: "Setosa (selected)" if s == 0 else "Other species") - -# Reshape into three coordinated views (different variable pairs per view) -view1 = df[["sepal_length", "sepal_width", "selection"]].rename(columns={"sepal_length": "x", "sepal_width": "y"}) -view1["view"] = "Sepal Length vs Width" - -view2 = df[["petal_length", "petal_width", "selection"]].rename(columns={"petal_length": "x", "petal_width": "y"}) -view2["view"] = "Petal Length vs Width" - -view3 = df[["sepal_length", "petal_length", "selection"]].rename(columns={"sepal_length": "x", "petal_length": "y"}) -view3["view"] = "Sepal vs Petal Length" - -combined = pd.concat([view1, view2, view3], ignore_index=True) -combined["view"] = pd.Categorical( - combined["view"], - categories=["Sepal Length vs Width", "Petal Length vs Width", "Sepal vs Petal Length"], - ordered=True, -) - -# Title with length-aware font scaling -title = "linked-views-selection · python · plotnine · anyplot.ai" -title_fontsize = max(8, round(12 * min(1.0, 67 / len(title)))) - -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -anyplot_theme = theme( - figure_size=(8, 4.5), - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - panel_background=element_rect(fill=PAGE_BG), - panel_grid_major=element_line(color=INK, size=0.3, alpha=0.10), - panel_grid_minor=element_blank(), - panel_border=element_rect(color=INK_SOFT, fill=None, size=0.5), - axis_line=element_blank(), - axis_title=element_text(color=INK, size=9), - axis_text=element_text(color=INK_SOFT, size=7), - plot_title=element_text(color=INK, size=title_fontsize), - plot_subtitle=element_text(color=INK_MUTED, size=8), - legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT), - legend_key=element_rect(fill=PAGE_BG), - legend_text=element_text(color=INK_SOFT, size=7), - legend_title=element_text(color=INK, size=8), - legend_position="right", - strip_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT), - strip_text=element_text(color=INK, size=8), -) - -color_map = {"Setosa (selected)": SELECTED_COLOR, "Other species": UNSELECTED_COLOR} -alpha_map = {"Setosa (selected)": 0.85, "Other species": 0.25} - -n_selected = (df["species_id"] == 0).sum() -n_total = len(df) -subtitle = f"Setosa: {n_selected} selected of {n_total} iris samples · unselected points de-emphasized" - -# Plot — three coordinated scatter views with consistent selection encoding -plot = ( - ggplot(combined, aes("x", "y", color="selection", alpha="selection")) - + geom_point(size=2.5) - + facet_wrap("~view", scales="free", ncol=3) - + scale_color_manual(values=color_map, name="") - + scale_alpha_manual(values=alpha_map) - + guides(alpha=False) - + labs(x="Measurement (cm)", y="Measurement (cm)", title=title, subtitle=subtitle) - + anyplot_theme -) - -# Save -plot.save(f"plot-{THEME}.png", dpi=400, width=8, height=4.5, units="in", verbose=False) diff --git a/plots/linked-views-selection/implementations/python/pygal.py b/plots/linked-views-selection/implementations/python/pygal.py deleted file mode 100644 index 7e9c959b45..0000000000 --- a/plots/linked-views-selection/implementations/python/pygal.py +++ /dev/null @@ -1,262 +0,0 @@ -""" anyplot.ai -linked-views-selection: Multiple Linked Views with Selection Sync -Library: pygal 3.1.0 | Python 3.13.13 -Quality: 85/100 | Updated: 2026-05-24 -""" - -import os -import sys - - -# Prioritize venv packages over the current directory (script is named pygal.py) -venv_path = [p for p in sys.path if ".venv" in p] -sys.path = venv_path + [p for p in sys.path if ".venv" not in p and p != ""] - -import pandas as pd # noqa: E402 -import pygal # noqa: E402 -from PIL import Image # noqa: E402 -from pygal.style import Style # noqa: E402 -from sklearn.datasets import load_iris # noqa: E402 - - -# 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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -IMPRINT = ("#009E73", "#C475FD", "#AE3030", "#4467A3", "#99B314", "#954477", "#BD8233") - -# Canvas layout (3200 × 1800 exact) -CANVAS_W, CANVAS_H = 3200, 1800 -MARGIN, GAP = 40, 20 -TOP_H = 860 -BOT_H = CANVAS_H - 2 * MARGIN - TOP_H - GAP # 840 -HALF_W = (CANVAS_W - 2 * MARGIN - GAP) // 2 # 1550 -FULL_W = CANVAS_W - 2 * MARGIN # 3120 - -# Sub-chart style (sized for partial-canvas charts) -sub_style = Style( - background=PAGE_BG, - plot_background=PAGE_BG, - foreground=INK, - foreground_strong=INK, - foreground_subtle=INK_MUTED, - colors=IMPRINT, - title_font_size=54, - label_font_size=44, - major_label_font_size=36, - legend_font_size=36, - value_font_size=28, - stroke_width=2.5, -) - -# Data: Iris dataset -iris = load_iris() -df = pd.DataFrame(iris.data, columns=["Sepal Length", "Sepal Width", "Petal Length", "Petal Width"]) -df["species"] = [iris.target_names[i] for i in iris.target] -means = df.groupby("species")[["Petal Length", "Sepal Length"]].mean() - -# View 1 (top-left): Scatter — Petal Length vs Petal Width (clearest cluster separation) -scatter = pygal.XY( - width=HALF_W, - height=TOP_H, - title="Petal Length vs Petal Width", - x_title="Petal Length (cm)", - y_title="Petal Width (cm)", - style=sub_style, - show_legend=True, - dots_size=5, - stroke=False, - show_x_guides=True, - show_y_guides=True, -) -for sp in iris.target_names: - sub = df[df["species"] == sp] - scatter.add(sp, list(zip(sub["Petal Length"].round(2), sub["Petal Width"].round(2), strict=True))) -scatter_svg = scatter.render() -scatter.render_to_png(f"scatter-{THEME}.png") - -# View 2 (top-right): Bar — mean Petal Length per species (confirms 1-D separation) -bar = pygal.Bar( - width=HALF_W, - height=TOP_H, - title="Mean Petal Length by Species", - y_title="Petal Length (cm)", - style=sub_style, - show_legend=True, - show_x_guides=False, - show_y_guides=False, -) -bar.x_labels = ["Mean Petal Length"] -for sp in iris.target_names: - bar.add(sp, [round(means.loc[sp, "Petal Length"], 2)]) -bar_svg = bar.render() -bar.render_to_png(f"bar-{THEME}.png") - -# View 3 (bottom, full width): Box — Sepal Length distribution (species overlap visible) -title_main = "linked-views-selection · python · pygal · anyplot.ai" -box = pygal.Box( - width=FULL_W, - height=BOT_H, - title=title_main, - y_title="Sepal Length (cm)", - style=sub_style, - show_legend=True, - box_mode="tukey", - show_x_guides=False, - show_y_guides=False, -) -for sp in iris.target_names: - box.add(sp, df[df["species"] == sp]["Sepal Length"].tolist()) -# Force y-axis ticks from 4–8 so data fills chart (sepal length 4.3–7.9; avoids wasted 0-4 blank) -box.y_labels = [4, 5, 6, 7, 8] -box_svg = box.render() -box.render_to_png(f"box-{THEME}.png") - -# Composite PNG: exactly 3200 × 1800 px -page_bg_rgb = tuple(int(PAGE_BG.lstrip("#")[i : i + 2], 16) for i in (0, 2, 4)) -canvas = Image.new("RGB", (CANVAS_W, CANVAS_H), page_bg_rgb) - -img_scatter = Image.open(f"scatter-{THEME}.png").resize((HALF_W, TOP_H), Image.LANCZOS) -img_bar = Image.open(f"bar-{THEME}.png").resize((HALF_W, TOP_H), Image.LANCZOS) -img_box = Image.open(f"box-{THEME}.png").resize((FULL_W, BOT_H), Image.LANCZOS) - -canvas.paste(img_scatter, (MARGIN, MARGIN)) -canvas.paste(img_bar, (MARGIN + HALF_W + GAP, MARGIN)) -canvas.paste(img_box, (MARGIN, MARGIN + TOP_H + GAP)) -canvas.save(f"plot-{THEME}.png") - -# Interactive HTML with linked species selection -scatter_svg_str = scatter_svg.decode("utf-8") -bar_svg_str = bar_svg.decode("utf-8") -box_svg_str = box_svg.decode("utf-8") - -html_open = ( - "\n\n\n" - ' \n' - " linked-views-selection · pygal · anyplot.ai\n" - " \n\n\n" - "

Multiple Linked Views with Selection Sync

\n" - '

linked-views-selection · python · pygal · anyplot.ai

\n' - '
\n' - " \n" - ' \n' - ' \n' - ' \n' - ' \n' - ' Selected: All species\n' - "
\n" - '
\n' - '
\n' -) - -html_mid1 = '\n
\n
\n' - -html_mid2 = '\n
\n
\n
\n' - -html_close = ( - "\n
\n\n" - " \n" - "\n" -) - -html_content = html_open + scatter_svg_str + html_mid1 + bar_svg_str + html_mid2 + box_svg_str + html_close - -with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f: - f.write(html_content) diff --git a/plots/linked-views-selection/implementations/python/seaborn.py b/plots/linked-views-selection/implementations/python/seaborn.py deleted file mode 100644 index f02c23c7f0..0000000000 --- a/plots/linked-views-selection/implementations/python/seaborn.py +++ /dev/null @@ -1,146 +0,0 @@ -""" anyplot.ai -linked-views-selection: Multiple Linked Views with Selection Sync -Library: seaborn 0.13.2 | Python 3.13.13 -Quality: 82/100 | Created: 2026-05-24 -""" - -import os -import sys - - -# Remove the script's own directory from sys.path so that local files like -# matplotlib.py do not shadow the installed matplotlib package. -_here = os.path.dirname(os.path.abspath(__file__)) -sys.path = [p for p in sys.path if p and os.path.abspath(p) != _here] - -import matplotlib.patches as mpatches -import matplotlib.pyplot as plt -import seaborn as sns - - -# 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" - -IMPRINT = ["#009E73", "#C475FD", "#AE3030", "#4467A3", "#99B314", "#954477", "#BD8233"] - -sns.set_theme( - style="ticks", - rc={ - "figure.facecolor": PAGE_BG, - "axes.facecolor": PAGE_BG, - "axes.edgecolor": INK_SOFT, - "axes.labelcolor": INK, - "text.color": INK, - "xtick.color": INK_SOFT, - "ytick.color": INK_SOFT, - "grid.color": INK, - "grid.alpha": 0.10, - "legend.facecolor": ELEVATED_BG, - "legend.edgecolor": INK_SOFT, - }, -) - -# Data -iris = sns.load_dataset("iris") -species_order = iris["species"].unique() -species_colors = {sp: IMPRINT[i] for i, sp in enumerate(species_order)} - -# Plot -fig, axes = plt.subplots(1, 3, figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG) - -# Panel 1: Scatter — petal length vs petal width -ax1 = axes[0] -sns.scatterplot( - data=iris, - x="petal_length", - y="petal_width", - hue="species", - palette=species_colors, - s=100, - alpha=0.80, - edgecolor=PAGE_BG, - linewidth=0.4, - ax=ax1, - legend=False, -) -ax1.set_title("Scatter", fontsize=9, color=INK_SOFT, pad=4) -ax1.set_xlabel("Petal Length (cm)", fontsize=10, color=INK) -ax1.set_ylabel("Petal Width (cm)", fontsize=10, color=INK) -ax1.tick_params(axis="both", labelsize=8, colors=INK_SOFT) -ax1.spines["top"].set_visible(False) -ax1.spines["right"].set_visible(False) -for spine in ("left", "bottom"): - ax1.spines[spine].set_color(INK_SOFT) -ax1.yaxis.grid(True, alpha=0.10, linewidth=0.8, color=INK) - -# Panel 2: Histogram — sepal length distribution -ax2 = axes[1] -sns.histplot( - data=iris, - x="sepal_length", - hue="species", - palette=species_colors, - multiple="layer", - bins=18, - alpha=0.60, - ax=ax2, - legend=False, -) -ax2.set_title("Distribution", fontsize=9, color=INK_SOFT, pad=4) -ax2.set_xlabel("Sepal Length (cm)", fontsize=10, color=INK) -ax2.set_ylabel("Count", fontsize=10, color=INK) -ax2.tick_params(axis="both", labelsize=8, colors=INK_SOFT) -ax2.spines["top"].set_visible(False) -ax2.spines["right"].set_visible(False) -for spine in ("left", "bottom"): - ax2.spines[spine].set_color(INK_SOFT) -ax2.yaxis.grid(True, alpha=0.10, linewidth=0.8, color=INK) - -# Panel 3: Bar chart — mean petal length per species with std error -ax3 = axes[2] -sns.barplot( - data=iris, - x="species", - y="petal_length", - hue="species", - palette=species_colors, - errorbar="sd", - capsize=0.15, - order=list(species_order), - ax=ax3, - legend=False, - width=0.55, -) -ax3.set_title("Summary", fontsize=9, color=INK_SOFT, pad=4) -ax3.set_xlabel("Species", fontsize=10, color=INK) -ax3.set_ylabel("Mean Petal Length (cm)", fontsize=10, color=INK) -ax3.set_xticks(range(len(species_order))) -ax3.set_xticklabels([sp.capitalize() for sp in species_order], fontsize=8, color=INK_SOFT) -ax3.tick_params(axis="y", labelsize=8, colors=INK_SOFT) -ax3.tick_params(axis="x", length=0) -ax3.spines["top"].set_visible(False) -ax3.spines["right"].set_visible(False) -for spine in ("left", "bottom"): - ax3.spines[spine].set_color(INK_SOFT) -ax3.yaxis.grid(True, alpha=0.10, linewidth=0.8, color=INK) - -# Shared legend — frameon=False keeps layout clean -legend_handles = [mpatches.Patch(color=species_colors[sp], label=sp.capitalize()) for sp in species_order] -fig.legend(handles=legend_handles, loc="lower center", ncol=3, fontsize=8, frameon=False, bbox_to_anchor=(0.5, 0.01)) - -# Title -title = "linked-views-selection · python · seaborn · anyplot.ai" -n = len(title) -ratio = 67 / n if n > 67 else 1.0 -title_fontsize = max(8, round(12 * ratio)) -fig.suptitle(title, fontsize=title_fontsize, fontweight="medium", color=INK, y=0.98) - -fig.subplots_adjust(top=0.86, bottom=0.18, left=0.09, right=0.97, wspace=0.45) - -# Save -plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG) -plt.close() diff --git a/plots/linked-views-selection/implementations/r/ggplot2.R b/plots/linked-views-selection/implementations/r/ggplot2.R deleted file mode 100644 index c545068599..0000000000 --- a/plots/linked-views-selection/implementations/r/ggplot2.R +++ /dev/null @@ -1,99 +0,0 @@ -#' anyplot.ai -#' linked-views-selection: Multiple Linked Views with Selection Sync -#' Library: ggplot2 3.5.1 | R 4.4.1 -#' Quality: 81/100 | Created: 2026-05-17 - -library(ggplot2) -library(dplyr) -library(ragg) - -set.seed(42) - -# --- Theme tokens ----------------------------------------------------------- -THEME <- Sys.getenv("ANYPLOT_THEME", "light") -PAGE_BG <- if (THEME == "light") "#FAF8F1" else "#1A1A17" -ELEVATED_BG <- if (THEME == "light") "#FFFDF6" else "#242420" -INK <- if (THEME == "light") "#1A1A17" else "#F0EFE8" -INK_SOFT <- if (THEME == "light") "#4A4A44" else "#B8B7B0" -GRID <- if (THEME == "light") "#D4D4D0" else "#3A3A36" -IMPRINT <- c("#009E73", "#C475FD", "#4467A3", "#BD8233", - "#AE3030", "#2ABCCD", "#954477") - -# --- Data ------------------------------------------------------------------- -# Create coordinated dataset: multivariate iris with clear groupings -df <- iris %>% - mutate( - id = row_number(), - view = "Data", - size_group = case_when( - Sepal.Length < 5.5 ~ "Compact", - Sepal.Length < 6.5 ~ "Standard", - TRUE ~ "Large" - ) - ) - -# --- Theme settings ---------------------------------------------------------- -anyplot_theme <- theme_minimal(base_size = 14) + - theme( - plot.background = element_rect(fill = PAGE_BG, color = PAGE_BG), - panel.background = element_rect(fill = PAGE_BG, color = NA), - panel.grid.major = element_line(color = GRID, linewidth = 0.2), - panel.grid.minor = element_blank(), - panel.border = element_blank(), - axis.line = element_line(color = INK_SOFT, linewidth = 0.4), - axis.title = element_text(color = INK, size = 20, face = "plain"), - axis.text = element_text(color = INK_SOFT, size = 16), - axis.ticks = element_blank(), - plot.title = element_text(color = INK, size = 28, face = "plain", margin = margin(b = 20)), - strip.text = element_text(color = INK, size = 16, face = "bold"), - legend.background = element_rect(fill = ELEVATED_BG, color = INK_SOFT, linewidth = 0.5), - legend.text = element_text(color = INK_SOFT, size = 16), - legend.title = element_text(color = INK, size = 18, face = "bold"), - legend.position = "bottom", - strip.background = element_rect(fill = ELEVATED_BG, color = INK_SOFT, linewidth = 0.5), - plot.margin = margin(20, 20, 20, 20) - ) - -# --- Create coordinated views as faceted scatter plots --------------------- -# Three perspectives on the same data with consistent coloring -df_views <- rbind( - df %>% mutate( - x_val = Sepal.Length, - y_val = Sepal.Width, - view_name = "Sepal Length vs Width (cm)" - ), - df %>% mutate( - x_val = Sepal.Length, - y_val = Petal.Length, - view_name = "Sepal vs Petal Length (cm)" - ), - df %>% mutate( - x_val = Petal.Length, - y_val = Petal.Width, - view_name = "Petal Length vs Width (cm)" - ) -) - -# --- Main plot: Multiple linked views ---------------------------------------- -p <- ggplot(df_views, aes(x = x_val, y = y_val, color = Species)) + - geom_point(size = 5, alpha = 0.75) + - scale_color_manual(values = IMPRINT[1:3]) + - facet_wrap(~view_name, scales = "free", ncol = 3) + - labs( - title = "linked-views-selection · ggplot2 · anyplot.ai", - x = "Measurement (cm)", - y = "Measurement (cm)", - color = "Species" - ) + - anyplot_theme - -# --- Save ------------------------------------------------------------------- -ggsave( - filename = sprintf("plot-%s.png", THEME), - plot = p, - device = ragg::agg_png, - width = 16, - height = 9, - units = "in", - dpi = 300 -) diff --git a/plots/linked-views-selection/metadata/python/altair.yaml b/plots/linked-views-selection/metadata/python/altair.yaml deleted file mode 100644 index 5c912bc982..0000000000 --- a/plots/linked-views-selection/metadata/python/altair.yaml +++ /dev/null @@ -1,246 +0,0 @@ -library: altair -language: python -specification_id: linked-views-selection -created: '2026-01-08T21:55:13Z' -updated: '2026-05-17T00:35:40Z' -generated_by: claude-haiku -workflow_run: 25976895512 -issue: 3344 -language_version: 3.13.13 -library_version: 6.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/altair/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/altair/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/altair/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/altair/plot-dark.html -quality_score: 93 -review: - strengths: - - Complete spec implementation with 3 coordinated views and full linked-selection - interactivity using native Altair features - - 'Excellent theme adaptability: both light and dark renders fully readable with - no chrome legibility issues' - - 'Perfect palette compliance: Okabe-Ito colors correctly ordered, identical across - both renders, colorblind-safe' - - Idiomatic Altair code using selection, condition, and configuration methods expertly; - declarative and concise - - High visual quality with no text overlap, no element visibility issues, generous - margins, appropriate typography - - Realistic Iris dataset with proper sepal/petal measurements and species distribution - weaknesses: - - 'Design Excellence could be elevated: histogram could use y-axis grid for better - count readability; bar chart could use value labels on bars' - - Data storytelling lacks explicit feedback on selection state (e.g., 'X points - selected from Y total') - - Could showcase more Altair distinctiveness through additional encodings (size, - secondary dimensions) or faceting - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1 target) - correct color, not pure white - Chrome: Title (28px dark ink), axis labels (16px dark ink), tick labels (14px gray), subtitle (16px gray) - all clearly readable against light surface - Data: Three Okabe-Ito colors (green #009E73, orange #D55E00, blue #0072B2) showing species; 150px circle markers with proper opacity hierarchy; grid at 0.10 opacity is subtle - Layout: 3-view coordinated layout (scatter left, histogram top-right, bar chart bottom-right); no overlap, generous margins - Legibility verdict: PASS - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17 target) - correct color, not pure black - Chrome: Title (light #F0EFE8), axis labels (light #F0EFE8), tick labels (gray #B8B7B0), subtitle (gray #B8B7B0) - all readable against dark surface, NO dark-on-dark failures - Data: Identical Okabe-Ito colors to light render (green #009E73 still visible and distinguishable on dark surface), same opacity hierarchy - Legend: Elevated background (#242420) with light text, fully readable - Grid: Subtle at 0.10 opacity, visible but not dominant - Legibility verdict: PASS - both renders fully functional and readable - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All text (title 28px, labels 16px, ticks 14px) explicitly sized and - readable in both light and dark themes - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: Scatter points well-distributed, histogram bars stack cleanly, bar - chart species separated, axes clear, legend positioned away - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: 150px markers clearly visible, histogram heights proportional, bar - chart heights adequate, unselected opacity 0.15 provides clear de-emphasis - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette colorblind-safe, adequate contrast, no red-green - as sole signal - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Efficient 3-view landscape layout, balanced proportions, nothing - cut off, margins appropriate - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title format correct, axes labeled with units, view subtitles descriptive - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73, Okabe-Ito positions 1-3, backgrounds correct, - both renders theme-correct' - design_excellence: - score: 14 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 6 - max: 8 - passed: true - comment: Professional polish with clean layout, intentional hierarchy, Okabe-Ito - applied correctly, subtle grid, no chartjunk - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Spines appropriate, grid subtle (0.10 opacity), legend has elevated - background, theme-aware axis colors, generous margins - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Clear focal point (scatter for selection), hierarchy guides viewer, - species colors tell consistent story; could enhance with selection feedback - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct linked-views-selection with 3 coordinated views - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: 3 views, linked selection via brush, consistent colors, unselected - de-emphasis, selection mechanism, tooltips, native Altair features - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X/Y/color correctly mapped, all data visible, axes show full ranges - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title format correct, legend labels match data, legend properly styled - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'All plot aspects demonstrated: scatter, histogram, bar chart, multiple - measurements, categorical dimension' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Iris-like measurements, realistic species-specific distributions, - neutral and scientifically plausible - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: All ranges realistic (sepal 4-7cm, width 2-4.2cm, petal 0.5-6.5cm), - 150 points appropriate for interactivity - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: No functions or classes, straightforward data generation and encoding - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) ensures deterministic state - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports (os, sys, altair, numpy, pandas) are used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Uses real Altair interactivity, not fake/simulated UI - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png and .html with scale_factor=3.0 - library_mastery: - score: 9 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: High-level declarative API, alt.condition(), alt.selection_interval(), - .add_params(), .hconcat()/.vconcat(), configure_* methods - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: alt.condition() for responsive encoding, selection_interval() for - linked views, .resolve_scale(color='shared'), theme adaptation; could explore - secondary encodings - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - hover-tooltips - - html-export - patterns: - - data-generation - - iteration-over-groups - dataprep: [] - styling: [] diff --git a/plots/linked-views-selection/metadata/python/bokeh.yaml b/plots/linked-views-selection/metadata/python/bokeh.yaml deleted file mode 100644 index dc1395b4e5..0000000000 --- a/plots/linked-views-selection/metadata/python/bokeh.yaml +++ /dev/null @@ -1,255 +0,0 @@ -library: bokeh -language: python -specification_id: linked-views-selection -created: '2026-01-08T21:54:54Z' -updated: '2026-05-17T00:32:33Z' -generated_by: claude-haiku -workflow_run: 25976859294 -issue: 3344 -language_version: 3.13.13 -library_version: 3.9.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/bokeh/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/bokeh/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/bokeh/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/bokeh/plot-dark.html -quality_score: 90 -review: - strengths: - - Perfect visual quality across all criteria—text legible in both themes, proper - palette compliance, excellent layout proportions - - Correct linked-selection implementation using Bokeh's ColumnDataSource and CustomJS - callbacks for dynamic histogram/bar chart updates - - Professional theme-adaptive styling with consistent use of INK/INK_SOFT/PAGE_BG - tokens throughout all four plots - - Clean, reproducible code with deterministic seed and idiomatic Bokeh patterns - - 'Comprehensive specification compliance: 2 scatter plots + histogram + bar chart - with selection sync, clear button, and de-emphasis on unselected' - weaknesses: - - Design excellence is competent but standard—no exceptional aesthetic touches beyond - required theme tokens; could emphasize the linkage mechanism more visually - - Clear button uses default warning styling; consider more integration with overall - design palette - - Histogram and bar chart are separate ColumnDataSources manually updated via CustomJS - rather than derived views—works correctly but architectural alternative exists - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 with subtle grain, correctly applied - Chrome: Title "linked-views-selection · bokeh · anyplot.ai" in dark text (#1A1A17), axis labels (Sepal Length, Sepal Width, Value, Category, Count) clearly readable, tick labels in softer dark (#4A4A44), grid lines at 10% opacity - Data: Two scatter plots show three colored categories (green #009E73, orange #D55E00, blue #0072B2) at consistent opacity 0.8; histogram and bar chart use same palette; all data colors match Okabe-Ito specification - Layout: 2×2 grid layout with clear button centered at top; all elements properly spaced with generous margins - Legibility verdict: PASS — All text readable, no collisions, high contrast between foreground and background - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17, correctly applied (not pure black) - Chrome: Title and all axis/tick labels rendered in light text (#F0EFE8 and #B8B7B0), all readable against dark background with no dark-on-dark failures - Data: All data colors identical to light render—green, orange, blue scatter points and histogram/bar colors preserved; only chrome (background, text, grid) flips between themes as specified - Layout: Identical 2×2 grid structure maintained; spacing and proportions consistent with light render - Legibility verdict: PASS — All text readable, brand green #009E73 remains visible and distinct, proper theme-adapted contrast throughout - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All text (titles, axis labels, tick labels) explicitly sized (28pt, - 22pt, 18pt) and readable at full resolution in both light (#FAF8F1) and - dark (#1A1A17) themes - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: Elements well-separated in 2×2 grid; no text collisions or overlapping - markers - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Scatter markers (size=15) clearly visible; histogram bars and categorical - bars well-distinguished - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: 'Okabe-Ito palette (green #009E73, orange #D55E00, blue #0072B2) - is CVD-safe; no red-green as sole signal' - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 2×2 grid proportions balanced; nothing cut off; generous margins - around plots - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: 'Descriptive labels with units: Sepal Length (cm), Sepal Width (cm), - Value (units); title format correct' - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series green #009E73; multi-series follows Okabe-Ito order - (1→3); backgrounds #FAF8F1 (light) and #1A1A17 (dark); chrome correctly - theme-adapted in both renders' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: false - comment: Professional styling with intentional hierarchy and theme token consistency, - but uses standard Bokeh patterns without exceptional design flourishes - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: false - comment: Clean styling with proper font hierarchy and subtle grid (10% opacity), - but minimal additional refinement beyond defaults - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: false - comment: Four complementary views guide exploratory analysis (scatter → histogram, - bar chart), but linkage mechanism could be more visually emphasized - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: 'Correct plot types: two scatter plots (primary interaction), histogram - (continuous distribution), bar chart (categorical summary)' - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: 'All spec requirements: 3 coordinated views, selection sync via CustomJS - callback, de-emphasis (nonselection_alpha=0.15), selection mechanism (box_select, - lasso_select), clear button, consistent color encoding' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X/Y axes correct; all 150 data points shown; axes scale appropriately - to data ranges - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title: ''linked-views-selection · bokeh · anyplot.ai'' (correct - format); implicit legend via color encoding in scatter plots' - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Demonstrates multiple dimensions (x, y, category, value), brushing/linking - interaction, and categorical/continuous distributions - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Iris-like data with realistic labels (Sepal Length/Width, Species - A–C); 150 points across 3 categories is plausible - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: 150 points (50 per category) appropriate for linked-view interaction; - value ranges (10–25 units) and sepal dimensions (2–8) realistic - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Simple, straightforward script; no unnecessary functions or classes - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Seed set to 42 for deterministic data generation - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only necessary imports; no unused dependencies - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Appropriate complexity; uses idiomatic Bokeh patterns (ColumnDataSource, - CustomJS callbacks) without fake UI - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Generates plot-light.png, plot-dark.png, plot-light.html, plot-dark.html; - no bare plot.png - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: 'Uses Bokeh''s recommended patterns: ColumnDataSource for data binding, - figure() API, CustomJS for callbacks, proper theme token application' - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: 'Leverages Bokeh''s distinctive capabilities: ColumnDataSource linking, - selection glyphs (selection_color, nonselection_alpha), CustomJS for dynamic - updates; Selenium screenshot pattern correct per library rules' - verdict: APPROVED -impl_tags: - dependencies: - - selenium - techniques: - - html-export - patterns: - - data-generation - - columndatasource - dataprep: - - binning - styling: - - grid-styling - - alpha-blending diff --git a/plots/linked-views-selection/metadata/python/letsplot.yaml b/plots/linked-views-selection/metadata/python/letsplot.yaml deleted file mode 100644 index 21294cbacd..0000000000 --- a/plots/linked-views-selection/metadata/python/letsplot.yaml +++ /dev/null @@ -1,254 +0,0 @@ -library: letsplot -language: python -specification_id: linked-views-selection -created: '2026-01-08T22:05:55Z' -updated: '2026-05-17T00:50:57Z' -generated_by: claude-haiku -workflow_run: 25977051014 -issue: 3344 -language_version: 3.13.13 -library_version: 4.9.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/letsplot/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/letsplot/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/letsplot/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/letsplot/plot-dark.html -quality_score: 81 -review: - strengths: - - Clean multi-panel layout effectively communicates linked-view concept with brush - selection and summary - - Excellent use of opacity to de-emphasize unselected points while preserving color - identity across themes - - Text legibility excellent in both light and dark renders; all font sizes explicitly - set - - Well-chosen four-cluster synthetic data effectively demonstrates linked-view relationships - and selection - - Proper use of gggrid for coordinated multi-view layout - weaknesses: - - 'Title domain error: ''pyplots.ai'' should be ''anyplot.ai''' - - 'Palette array does not follow Okabe-Ito canonical order; first color should be - #009E73 (brand green), not #E69F00' - - Output filenames use 'plot.png' instead of dynamic 'plot-{THEME}.png'; code does - not read ANYPLOT_THEME environment variable - - 'Grid color uses hardcoded #E5E5E5 instead of theme-adaptive INK_SOFT token for - proper light/dark contrast' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) - correct theme color - Chrome: Title, axis labels (Measurement X, Measurement Y, Value, Category), tick labels all dark and clearly readable - Data: Four-cluster scatter plot with colors appearing in sequence (orange, sky blue, teal, pink); histogram with blue selected bars and gray unselected; stacked bar chart; summary legend with colored dots. Brush rectangle clearly visible in dashed blue outline on scatter plot. - Legibility verdict: PASS - all text elements readable, no contrast failures, grid subtle and visible - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) - correct dark theme color - Chrome: Title, axis labels, tick labels all light-colored and readable against dark background; no dark-on-dark text failures - Data: Data colors identical to light render (same cluster colors preserved). Selection state visualized through same opacity values. Summary legend colors match light render. - Legibility verdict: PASS - all text readable in light color, data colors preserved across themes, grid visible with appropriate contrast - criteria_checklist: - visual_quality: - score: 27 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: 'All font sizes explicitly set: 18pt axis titles, 20pt plot title, - 14pt axis text. All readable in both light and dark renders.' - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text elements across four plots. Category labels, - axis ticks, and legend text well-spaced. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Marker sizes (6pt) and alpha blending (0.2-0.9) well-adapted for - 200 points. Clear visual distinction between selected and unselected. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette is colorblind-safe. Selection encoded through both - opacity and color. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Four-plot grid fills ~70% of canvas. Balanced margins, legend integrated, - excellent space utilization. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: 'Descriptive labels: ''Measurement X'', ''Measurement Y'', ''Value'', - ''Category'' provide clear context.' - - id: VQ-07 - name: Palette Compliance - score: 1 - max: 2 - passed: false - comment: 'Partial compliance: First color is #E69F00 (orange) instead of #009E73 - (brand green). Okabe-Ito order violated.' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: false - comment: Well-configured default styling with multi-panel layout intentionality. - Professional appearance but palette order is generic rather than brand-first. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: false - comment: Grid present and readable with generous whitespace. Summary panel - shows custom layout. Could refine grid opacity/styling further. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: false - comment: 'Visual hierarchy present: brush rectangle prominent, opacity contrast - emphasizes selection, summary provides quantitative insight.' - spec_compliance: - score: 13 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct implementation of linked views with scatter plot, histogram, - bar chart, and summary panel. - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: 'All features present: brush selection rectangle, opacity-based de-emphasis, - color encoding across views, selection counts, summary statistics.' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X and Y correctly mapped in scatter plot. Histogram maps value dimension. - Bar chart aggregates by category. - - id: SC-04 - name: Title & Legend - score: 1 - max: 3 - passed: false - comment: 'Title format incorrect: ''pyplots.ai'' should be ''anyplot.ai''. - Legend labels are correct (Cluster A-D with counts).' - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'Shows all aspects: scatter with brush, histogram distribution, category - breakdown, selection summary. Four-cluster data demonstrates linked-view - value.' - - id: DQ-02 - name: Realistic Context - score: 4 - max: 5 - passed: false - comment: Plausible multivariate scenario with cluster-based synthetic data. - Generic labels acceptable for demonstration purposes. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: 'Values sensible: x/y in range [0-100], value in [40-140], cluster - distributions realistic.' - code_quality: - score: 8 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear structure: imports → data → plot → save. No unnecessary functions - or classes.' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Seed set to 42 ensures deterministic output. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only used imports from lets_plot, pandas, and numpy. - - id: CQ-04 - name: Code Elegance - score: 1 - max: 2 - passed: false - comment: 'Missing theme adaptation: uses hardcoded color=''#E5E5E5'' for grid - instead of reading ANYPLOT_THEME and using theme-adaptive tokens.' - - id: CQ-05 - name: Output & API - score: 0 - max: 1 - passed: false - comment: 'Incorrect filename: saves to ''plot.png'' instead of ''plot-{THEME}.png''. - Should read ANYPLOT_THEME environment variable.' - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: false - comment: Good ggplot grammar with aes(), geom_*(), scale_manual(), gggrid(). - Missing theme-adaptive pattern per letsplot.md guide. - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: false - comment: Uses gggrid() for layout and layer_tooltips() for interactivity. - Could leverage more of letsplot's distinctive capabilities. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - subplots - - layer-composition - - hover-tooltips - - html-export - patterns: - - data-generation - - groupby-aggregation - dataprep: - - binning - styling: - - alpha-blending - - publication-ready diff --git a/plots/linked-views-selection/metadata/python/matplotlib.yaml b/plots/linked-views-selection/metadata/python/matplotlib.yaml deleted file mode 100644 index 7f3d3b81c1..0000000000 --- a/plots/linked-views-selection/metadata/python/matplotlib.yaml +++ /dev/null @@ -1,239 +0,0 @@ -library: matplotlib -language: python -specification_id: linked-views-selection -created: '2026-05-17T00:24:04Z' -updated: '2026-05-17T00:27:42Z' -generated_by: claude-haiku -workflow_run: 25976747408 -issue: 3344 -language_version: 3.13.13 -library_version: 3.10.9 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/matplotlib/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/matplotlib/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 96 -review: - strengths: - - Perfect visual quality with all text explicitly sized and readable in both themes - - 'Excellent spec compliance: all required linked-views features demonstrated effectively' - - Sophisticated use of theme adaptation with environment variables and proper color - tokens - - Clean, idiomatic matplotlib code with effective use of GridSpec for multi-panel - layout - - Strong visual hierarchy using opacity and size to emphasize selected data points - - Professional design with intentional color choices and refined grid/spine styling - weaknesses: - - LM-02 could be enhanced with additional distinctive matplotlib features beyond - GridSpec and custom legend - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) - correct theme surface - Chrome: Title "linked-views-selection · matplotlib · anyplot.ai" is clearly visible in dark text (INK). Axis labels "X Dimension" and "Y Dimension" are readable at fontsize=20. Subplot titles "Scatter Plot", "X Distribution", "Y Distribution" at fontsize=18 are clearly visible. All tick labels (0, 20, 40, 60, 80, 100 for X; numeric for Y) are readable at fontsize=16. Grid is subtle and visible (alpha=0.10). Legend at bottom shows "Other data" and "Selected: Group A" with proper styling. - Data: Selected points (Group A) rendered in brand green (#009E73) with s=150 and alpha=0.8, standing out clearly. Unselected points in light gray (INK_SOFT with alpha=0.15) provide context without overwhelming. Histogram bars show the same color pattern - unselected in light gray, selected in brand green. Data colors are consistent across all three panels (scatter, X distribution, Y distribution). - Legibility verdict: PASS - All elements are clearly readable against the light background. No text-on-text issues detected. - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) - correct dark theme surface - Chrome: Title renders in light text (should be INK which flips to #F0EFE8 in dark mode) and is clearly visible against dark background. Axis labels are visible in light text color. Subplot titles are readable. All tick labels are legible against the dark background - NO dark-on-dark failures detected. Grid remains visible and subtle. Legend text is light-colored and readable against the dark legend frame. - Data: Selected points (Group A) remain in identical brand green (#009E73) - confirming data colors are theme-independent. Unselected points in gray show proper adaptation. Histogram bars display consistent coloring between themes. The visual contrast and emphasis are maintained between light and dark renders. - Legibility verdict: PASS - All text is readable against the dark background. Tick labels and all other chrome elements are properly light-colored for the dark theme. No dark-on-dark text issues. The brand green data color remains visible and consistent with the light render, confirming proper theme adaptation. - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: 'All font sizes explicitly set: Title 24pt, Labels 20pt, Ticks 16pt, - all perfectly readable in both themes' - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping elements, all text fully readable - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Markers perfectly adapted to data density with appropriate sizing - (s=150/100) and alpha (0.8/0.15) - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Good contrast, Okabe-Ito CVD-safe palette, adequate alpha for overlapping - elements - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Perfect layout with 16:9 aspect ratio, balanced spacing (hspace=0.3, - wspace=0.3), nothing cut off - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: 'Descriptive labels: ''X Dimension'', ''Y Dimension'', proper main - title format' - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series is #009E73, backgrounds #FAF8F1 (light) and #1A1A17 - (dark), theme-adaptive chrome perfect in both renders' - design_excellence: - score: 17 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 7 - max: 8 - passed: true - comment: Professional polish with intentional hierarchy, thoughtful color - use, properly styled spines/grid - - id: DE-02 - name: Visual Refinement - score: 5 - max: 6 - passed: true - comment: 'Refined presentation: spines removed top/right, subtle grid (alpha=0.10), - generous whitespace' - - id: DE-03 - name: Data Storytelling - score: 5 - max: 6 - passed: true - comment: 'Clear narrative: shows linked-views concept through selection highlighting - with visual hierarchy via opacity and size' - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: 'Correct chart type: scatter plot + histograms demonstrating linked - selection' - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: 'All spec features present: 3 coordinated views, selection highlighting, - consistent color, de-emphasized unselected, legend explains selection' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X/Y correctly mapped, axes show all data, appropriate ranges - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title format correct, legend labels match and explain selection - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'Shows all aspects: scatter plot, X distribution, Y distribution, - multi-category data, linked selection' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Plausible synthetic data with correlation, neutral context, clear - domain - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: 'Sensible values: X ~0-100 (mean 50), Y correlated, 300 points appropriate - for linked selection demo' - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Simple, direct: imports → data → plot → save, no unnecessary functions/classes' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Deterministic with np.random.seed(42) - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only necessary imports used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: No fake functionality, appropriate complexity - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: 'Correct output naming: plot-{THEME}.png' - library_mastery: - score: 9 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: 'Expert matplotlib: GridSpec for layout, Axes methods, proper figure - pattern, theme tokens' - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: Uses GridSpec, custom legend with Patch, theme adaptation, alpha - blending - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - subplots - - custom-legend - - alpha-blending - patterns: - - data-generation - - explicit-figure - dataprep: [] - styling: - - alpha-blending - - grid-styling diff --git a/plots/linked-views-selection/metadata/python/plotly.yaml b/plots/linked-views-selection/metadata/python/plotly.yaml deleted file mode 100644 index 9faf5dc86d..0000000000 --- a/plots/linked-views-selection/metadata/python/plotly.yaml +++ /dev/null @@ -1,231 +0,0 @@ -library: plotly -language: python -specification_id: linked-views-selection -created: '2026-01-08T21:54:59Z' -updated: '2026-05-17T00:48:56Z' -generated_by: claude-haiku -workflow_run: 25976874632 -issue: 3344 -language_version: 3.13.13 -library_version: 6.7.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/plotly/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/plotly/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/plotly/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/plotly/plot-dark.html -quality_score: 76 -review: - strengths: - - Excellent data quality with realistic iris-like dataset and clear clustering - - Strong layout and composition with three well-balanced coordinated views - - All text readable in both light and dark renders with proper contrast - - Correct plot types and core spec compliance - - Good marker sizing and opacity handling for scatter overlaps - - Seed-based reproducibility with deterministic data - weaknesses: - - 'VQ-07 CRITICAL: Code uses wrong palette (#306998, #FFD43B, #E53935) instead of - Okabe-Ito (#009E73, #D55E00, #0072B2). First series must be brand green.' - - 'CQ-05 CRITICAL: Output filenames wrong. Code saves plot.png/plot.html but should - save plot-light.png/plot-dark.png with theme variants.' - - 'VQ-01: Title uses hardcoded color #333333 instead of theme-adaptive INK token. - Code doesn''t read ANYPLOT_THEME environment variable.' - - 'LM-01: Doesn''t follow plotly.md template for theme-adaptive chrome (no INK/INK_SOFT/GRID - tokens, no PAGE_BG background switching).' - - 'SC-04: Title says ''pyplots.ai'' instead of ''anyplot.ai''.' - - 'DE-01/DE-02: Generic styling with library defaults, no visual refinement or design - sophistication.' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) - PASS - Chrome: Title "linked-views-selection · plotly · anyplot.ai" in dark text, axis labels "Sepal Length (cm)", "Sepal Width (cm)", "Petal Length (cm)", "Count", "Species" all with units, tick labels visible - PASS - Data: Three species in teal, orange, and blue (colors appear visually correct for Okabe-Ito positions 1-3 in render, though code uses wrong hex values), white-edged markers for definition, histogram bins with matching colors, bar chart with matching species colors - PASS - Legibility verdict: PASS - All elements readable, good contrast, no overlaps - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) - PASS - Chrome: Title in light text, axis labels and tick labels all light-colored and readable against dark background - PASS - Data: Teal, orange, blue colors IDENTICAL to light render (only chrome flipped, not data colors) - CORRECT. White-edged markers maintain definition on dark background. Histogram and bar colors match - PASS - Legibility verdict: PASS - All text fully readable, no dark-on-dark failures, proper theme contrast - criteria_checklist: - visual_quality: - score: 26 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 6 - max: 8 - passed: true - comment: Readable but not explicitly theme-aware - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text elements - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Markers well-sized and visible - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: CVD-safe, good contrast - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Excellent three-subplot layout - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: All labels have units - - id: VQ-07 - name: Palette Compliance - score: 0 - max: 2 - passed: false - comment: 'Code uses #306998/#FFD43B/#E53935 instead of Okabe-Ito #009E73/#D55E00/#0072B2' - design_excellence: - score: 8 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: false - comment: Library defaults, no exceptional design - - id: DE-02 - name: Visual Refinement - score: 2 - max: 6 - passed: false - comment: Minimal customization beyond defaults - - id: DE-03 - name: Data Storytelling - score: 2 - max: 6 - passed: false - comment: Data displayed without visual hierarchy - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct three-view setup - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Most features present; PNG is static - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Correct X/Y/categorical assignments - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title correct (but typo: pyplots.ai not anyplot.ai)' - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Shows all iris aspects with three distinct clusters - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Real, plausible, neutral scientific scenario - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Realistic iris proportions - code_quality: - score: 8 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Linear code, no over-engineering - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Seed set to 42 - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only used imports - - id: CQ-04 - name: Code Elegance - score: 1 - max: 2 - passed: false - comment: Readable but lacks theme logic; hard-coded trace indices - - id: CQ-05 - name: Output & API - score: 0 - max: 1 - passed: false - comment: 'Wrong filenames: plot.png not plot-light.png/plot-dark.png' - library_mastery: - score: 5 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 3 - max: 5 - passed: false - comment: Correct but doesn't follow plotly.md theme patterns - - id: LM-02 - name: Distinctive Features - score: 2 - max: 5 - passed: false - comment: Uses make_subplots and JavaScript selection but implementation is - manual - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - subplots - - html-export - patterns: - - data-generation - - iteration-over-groups - dataprep: - - binning - styling: - - alpha-blending - - edge-highlighting diff --git a/plots/linked-views-selection/metadata/python/plotnine.yaml b/plots/linked-views-selection/metadata/python/plotnine.yaml deleted file mode 100644 index dd935f4926..0000000000 --- a/plots/linked-views-selection/metadata/python/plotnine.yaml +++ /dev/null @@ -1,247 +0,0 @@ -library: plotnine -language: python -specification_id: linked-views-selection -created: '2026-05-23T21:57:05Z' -updated: '2026-05-23T22:15:16Z' -generated_by: claude-sonnet -workflow_run: 26344347590 -issue: 3344 -language_version: 3.13.13 -library_version: 0.15.4 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/plotnine/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/plotnine/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 84 -review: - strengths: - - Three coordinated scatter views with consistent selection encoding via facet_wrap - — best possible static approximation of linked views for a static library - - 'Dual encoding (color + alpha) clearly communicates selection state: selected - Setosa visually pops, unselected recedes without disappearing' - - Selection count displayed in subtitle satisfies the 'summary statistics' requirement - from the spec - - Perfect canvas dimensions (8x4.5 @ dpi=400 = 3200x1800 px); both themes render - with correct backgrounds and readable chrome - - 'Clean idiomatic plotnine code: scale_alpha_manual + guides(alpha=False) combo - is idiomatic; pd.Categorical for ordered facets is correct' - weaknesses: - - Axes both labeled 'Measurement (cm)' — generic labels due to data reshaping; per-panel - specific labels (e.g. 'Sepal Length (cm)' / 'Sepal Width (cm)') are not achievable - with facet_wrap on reshaped x/y data - - All three coordinated views are scatter plots; the spec suggests including a histogram - or bar chart view for categorical distribution — adding geom_histogram with fill='selection' - would broaden feature coverage - - Panel borders use 4-sided box frames per facet rather than L-shaped spines — acceptable - for faceted layout but slightly heavier than the minimal style guide default - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct light surface - Chrome: Title "linked-views-selection · python · plotnine · anyplot.ai" in dark ink, clearly readable; subtitle in muted gray (#6B6A63) readable; axis labels "Measurement (cm)" on both axes at 9pt readable; tick labels at 7pt small but legible; facet strip labels ("Sepal Length vs Width", "Petal Length vs Width", "Sepal vs Petal Length") in dark ink on elevated background; legend text readable - Data: Selected Setosa points in brand green (#009E73) at alpha=0.85 — prominent; unselected Other species in muted gray (#4A4A44) at alpha=0.25 — intentionally de-emphasized; both categories clearly distinguishable; three panels show different variable pairs; ~50 points per panel with size=2.5 appropriate for density - Legibility verdict: PASS - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct dark surface - Chrome: Title in light ink (#F0EFE8) clearly visible; subtitle in tertiary dark ink token readable; axis labels, tick labels, strip labels all in light tokens and readable; legend text in #B8B7B0 readable; no dark-on-dark failures; panel borders in INK_SOFT; legend and strip backgrounds use elevated dark (#242420) - Data: Brand green (#009E73) for Setosa — identical to light render, clearly visible on dark background; unselected gray (#B8B7B0 at alpha=0.25) visible as subtle de-emphasized dots — same colors as light render, only chrome flips - Legibility verdict: PASS - criteria_checklist: - visual_quality: - score: 27 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: All elements explicitly sized; 7pt tick labels proportional to dense - 3-panel layout; readable in both themes - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: No text collisions; point overlap in first panel is expected scatter - behavior handled by alpha encoding - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: size=2.5 appropriate for ~50 points/panel; green selected prominent; - gray unselected intentionally de-emphasized but visible - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Green vs muted gray is CVD-safe; no red-green-only encoding - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 3200x1800 canvas confirmed; 3-panel landscape layout well-proportioned; - no overflow or clipping - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Units included; correct title format; informative subtitle with selection - count - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First/selected series = #009E73; unselected = INK_SOFT neutral; - backgrounds #FAF8F1/#1A1A17 correct' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Intentional dual encoding color+alpha; custom theme; subtitle integrates - data context; above generic but no exceptional hierarchy - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Subtle grid alpha=0.10; minor grid removed; axis_line blanked; 4-sided - panel borders acceptable for faceted layout - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: 'Clear visual narrative: Setosa consistently stands out across three - dimensional perspectives; subtitle reinforces count; coherent focal point' - spec_compliance: - score: 13 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 4 - max: 5 - passed: true - comment: Three coordinated scatter plots with consistent selection encoding; - best static approximation for plotnine; interactive linking not possible - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: 3 views, consistent encoding, unselected de-emphasized, selection - count shown; interactive mechanism and reset button not possible in static - library - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Different variable pairs per panel; all 150 points displayed; species - used for selection grouping - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Correct title format; legend shows both selection states clearly - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: Three variable-pair views with dual encoding; all scatter plots — - adding histogram would improve coverage per spec suggestion - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Real iris dataset; biologically plausible; neutral scientific context - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Sepal/petal measurements in cm with correct biological ranges - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: No functions or classes; linear script; clean pandas reshaping - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Real deterministic dataset; no random seed needed - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports used; sklearn only for iris dataset loading - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clear data pipeline; pd.Categorical for ordered facets; no fake UI - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png correctly - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Full ggplot grammar; scale_alpha_manual + scale_color_manual dual - encoding; guides(alpha=False) to suppress redundant guide; facet_wrap with - free scales - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: facet_wrap(scales='free') is distinctive plotnine/ggplot2 feature; - pd.Categorical for panel ordering; scale_alpha_manual for simultaneous alpha+color - control - verdict: APPROVED -impl_tags: - dependencies: - - sklearn - techniques: - - faceting - patterns: - - dataset-loading - - wide-to-long - dataprep: [] - styling: - - alpha-blending diff --git a/plots/linked-views-selection/metadata/python/pygal.yaml b/plots/linked-views-selection/metadata/python/pygal.yaml deleted file mode 100644 index 38f3263d3c..0000000000 --- a/plots/linked-views-selection/metadata/python/pygal.yaml +++ /dev/null @@ -1,263 +0,0 @@ -library: pygal -language: python -specification_id: linked-views-selection -created: '2026-05-17T00:37:37Z' -updated: '2026-05-24T07:50:41Z' -generated_by: claude-sonnet -workflow_run: 26354894419 -issue: 3344 -language_version: 3.13.13 -library_version: 3.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/pygal/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/pygal/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/pygal/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/pygal/plot-dark.html -quality_score: 85 -review: - strengths: - - 'Perfect spec compliance: 3 coordinated views (scatter + bar + box) with real - HTML/JS linked selection implementing all spec requirements' - - 'Consistent Imprint palette across all three views with correct theme adaptation - (light #FAF8F1 / dark #1A1A17)' - - 'Excellent data choice: Iris dataset revealing different structural aspects — - cluster separation (scatter), magnitude differences (bar), distribution overlap - (box)' - - Real interactive linked selection in HTML output with species filter buttons, - reset functionality, click handlers on SVG chart elements, and selection count - display - - 'Idiomatic pygal usage: box_mode=tukey for Tukey whiskers, selective grid control - (scatter-only), Style object for full theme token propagation' - weaknesses: - - Sub-chart font sizes (title=54, label=44, major_label=36, legend=36) are slightly - below the pygal style guide defaults (66/56/44/44) — at final composite scale - the tick labels read as slightly small; consider increasing to guide-recommended - values for sub-charts - - Bar chart uses a single x-category ('Mean Petal Length') with three bars side-by-side, - leaving horizontal axis space underutilized — x-labels for each species (setosa, - versicolor, virginica) as separate bar groups would better communicate the comparison - - 'DE-01/DE-02: Design is well-configured but not publication-ready — could be elevated - with annotation callouts highlighting key insights (e.g. ''Setosa cleanly separates - by petal dims'') or more refined grid-free chart area in sub-charts' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1 confirmed) — correct anyplot light surface, not pure white - Layout: Three sub-charts composited: scatter (top-left 1550×860), bar (top-right 1550×860), box plot (bottom 3120×840) - Chrome: Sub-chart titles ("Petal Length vs Petal Width", "Mean Petal Length by Species") in dark ink, readable. Axis labels with units ("Petal Length (cm)", "Petal Width (cm)", "Sepal Length (cm)") visible. Tick labels (numeric values) are small but legible. Main anyplot title in box plot: "linked-views-selection · python · pygal · anyplot.ai" readable. - Data: Scatter shows three species clusters: setosa (green #009E73) tight cluster at low petal values, versicolor (purple #9418DB) mid-range, virginica (red #B71D27) upper-right. Bar chart shows three bars with correctly ordered heights (setosa ~1.4cm, versicolor ~4.2cm, virginica ~5.6cm). Box plot shows Tukey-style boxes with y-axis range 4–8, one outlier visible for virginica. - Legibility verdict: PASS — all text readable against the light background, no light-on-light failures - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17 confirmed) — correct anyplot dark surface, not pure black - Chrome: Sub-chart titles and axis labels render in light/cream text against the dark surface — all clearly readable. Tick labels legible. Main title visible. - Data: Data colors are IDENTICAL to light render — setosa=green, versicolor=purple, virginica=red — confirming only chrome (background, text) flipped, not data palette. - Legibility verdict: PASS — no dark-on-dark failures observed; all text elements use theme-adaptive light ink on dark surface - criteria_checklist: - visual_quality: - score: 27 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: Font sizes explicitly set (title=54, label=44, major_label=36, legend=36); - readable in both themes. Slightly below style guide defaults (66/56/44) - — tick labels appear somewhat small in the composited sub-charts - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping elements across any of the three views - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: Scatter dots (dots_size=5) visible but slightly small for 150 points; - bars and boxes are prominent - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Imprint palette is CVD-safe; three species clearly distinguishable - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: Multi-chart layout fills canvas well; bar chart uses single x-category - leaving some horizontal space underutilized - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: All axis labels include units (cm). Title format correct. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Imprint palette correctly applied; backgrounds #FAF8F1/#1A1A17; - theme-adaptive chrome in both renders; data colors identical across themes' - design_excellence: - score: 11 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: true - comment: Multi-chart layout with consistent theming above defaults; not reaching - publication-quality due to pygal default chart chrome - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: true - comment: Selective grid (scatter-only), theme-adaptive backgrounds, custom - font sizes; limited by pygal's refinement options (no spine removal) - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: 'Intentional chart selection: scatter for clustering, bar for magnitude, - box for distribution — each view reveals different structure. Consistent - color coding aids cross-view comparison.' - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: 'Three coordinated views: scatter (XY), bar (Bar), and box plot (Box)' - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Linked selection via HTML/JS buttons + click handlers; reset button; - unselected opacity=0.08; selection count label; consistent color encoding - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: 'Correct mappings: scatter (PetalLength×PetalWidth), bar (species→MeanPetalLength), - box (species→SepalLength)' - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title 'linked-views-selection · python · pygal · anyplot.ai' correct - in box chart. Species legend labels (setosa/versicolor/virginica) match - data. - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'Three views collectively show all aspects: cluster separation, magnitude - differences, distribution overlap, outliers (Tukey whiskers reveal one virginica - outlier)' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: 'Iris dataset: canonical real-world scientific dataset, neutral and - comprehensible' - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Biologically accurate Iris measurements (petal 1–7cm, sepal 4–8cm); - y-axis range 4–8 avoids wasted 0–4 blank space - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear structure: imports → data → three sub-charts → composite - PNG → HTML output. No functions or classes.' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Iris dataset from sklearn is fully deterministic - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports (os, sys, pandas, pygal, PIL, Style, load_iris) are used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Well-structured; HTML string concatenation is verbose but appropriate - for the complex multi-chart interactive output. No fake functionality. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html correctly using current - pygal API - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: 'Idiomatic pygal: XY for scatter, Box with box_mode=tukey, Bar, Style - object for theme tokens, show_x/y_guides for selective grid, render() for - SVG bytes' - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: 'Distinctive: SVG output embedded in multi-chart HTML dashboard with - JS linking (pygal-specific SVG class names .serie-N used for selection), - box_mode=tukey (pygal-specific Tukey whiskers), PIL compositing of pygal - sub-charts' - verdict: APPROVED -impl_tags: - dependencies: - - sklearn - - pillow - techniques: - - subplots - - html-export - - hover-tooltips - - manual-ticks - patterns: - - dataset-loading - - groupby-aggregation - - iteration-over-groups - dataprep: [] - styling: [] diff --git a/plots/linked-views-selection/metadata/python/seaborn.yaml b/plots/linked-views-selection/metadata/python/seaborn.yaml deleted file mode 100644 index 112b03951d..0000000000 --- a/plots/linked-views-selection/metadata/python/seaborn.yaml +++ /dev/null @@ -1,252 +0,0 @@ -library: seaborn -language: python -specification_id: linked-views-selection -created: '2026-05-24T07:23:00Z' -updated: '2026-05-24T07:41:10Z' -generated_by: claude-sonnet -workflow_run: 26354883422 -issue: 3344 -language_version: 3.13.13 -library_version: 0.13.2 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/seaborn/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/python/seaborn/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 82 -review: - strengths: - - Full theme adaptation in both light and dark renders with all chrome tokens correctly - applied - - Consistent Imprint palette across all three coordinated panels creates immediate - visual coherence - - Correct canvas dimensions (3200x1800) with no bbox_inches drift; subplots_adjust - used properly - - frameon=False shared legend avoids box clutter across three panels - - Bar chart with standard deviation error bars adds genuine statistical depth - weaknesses: - - Interactive selection/brushing (the primary spec feature) is entirely absent — - seaborn is static so this is a fundamental constraint, leaving the implementation - as a multi-panel display rather than a true linked-views system - - histplot multiple="layer" creates overlapping bars that are harder to read than - stack for three-species distributions — switch to multiple="stack" for unambiguous - species contributions - - FacetGrid or PairGrid would be more idiomatic seaborn pattern for coordinated - multi-panel plots - - Panel subtitles at 9pt are slightly small for a 3200x1800 canvas; 10-11pt would - improve glanceability - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct anyplot light surface - Chrome: Title "linked-views-selection · python · seaborn · anyplot.ai" in dark ink (#1A1A17); axis labels in INK (#1A1A17) at 10pt; tick labels in INK_SOFT (#4A4A44) at 8pt; panel subtitles ("Scatter", "Distribution", "Summary") in INK_SOFT at 9pt — all readable - Data: Three panels — scatter (Setosa=#009E73, Versicolor=#9418DB, Virginica=#B71D27), layered histogram, bar chart with error bars — correct Imprint palette order - Legibility verdict: PASS - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct anyplot dark surface - Chrome: Title in light ink (#F0EFE8); axis labels in INK (#F0EFE8); tick labels in INK_SOFT (#B8B7B0); panel subtitles in INK_SOFT — all readable against dark background; no dark-on-dark failures - Data: Colors identical to light render (Setosa=#009E73, Versicolor=#9418DB, Virginica=#B71D27) — only chrome flips - Legibility verdict: PASS - criteria_checklist: - visual_quality: - score: 28 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: All text readable in both themes; panel subtitles at 9pt slightly - small for 3200x1800 - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: No text-to-text or text-to-data collisions; histogram bar overlap - is by design (multiple=layer) - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: s=100 markers appropriate for 150 Iris points; alpha=0.80/0.60 correct - for density - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Imprint palette is colorblind-safe; three-species encoding is unambiguous - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Canvas gate passed; 3200x1800 px; no bbox_inches=tight; subplots_adjust - well-balanced; no clipping - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: All panels have descriptive labels with units (cm); mandated title - format correct - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73; canonical palette order; correct bg colors; - full chrome adaptation' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Clean professional multi-panel layout with custom palette; slightly - above default - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Top/right spines removed; y-only grid at alpha=0.10; frameon=False - legend; good whitespace - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Three complementary panels tell coherent story; consistent color - makes cross-panel tracking easy - spec_compliance: - score: 11 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 3 - max: 5 - passed: true - comment: Correctly implements 3 coordinated panel views; interactive selection - absent (seaborn static constraint) - - id: SC-02 - name: Required Features - score: 2 - max: 4 - passed: true - comment: Consistent color encoding and summary stats present; selection, de-emphasis, - reset all absent (interactivity required) - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: All 150 Iris points mapped correctly; species encoding consistent - across all three panels - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title matches mandated format exactly; shared legend shows capitalized - species names - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: Three panel types (scatter, histogram, bar+errorbar) cover complementary - aspects; one more type would be ideal - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Iris dataset is a real-world botanical dataset; neutral and non-controversial - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: 150 data points at realistic measurement scale (1-8 cm); error bars - reflect true standard deviation - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Flat linear structure; no functions or classes; clear panel-by-panel - organization - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Iris dataset is deterministic; no stochastic generation requiring - seed - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports actively used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean, well-organized; no fake UI elements - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png; no bbox_inches=tight; current seaborn - 0.13 API - library_mastery: - score: 6 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Consistent axes-level API; hue+palette+legend=False pattern; sns.set_theme() - with rc params - - id: LM-02 - name: Distinctive Features - score: 2 - max: 5 - passed: false - comment: Uses histplot multiple=layer, barplot errorbar=sd with capsize; misses - FacetGrid/PairGrid opportunity - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - subplots - - custom-legend - - manual-ticks - patterns: - - dataset-loading - - explicit-figure - dataprep: [] - styling: - - alpha-blending - - edge-highlighting - - grid-styling diff --git a/plots/linked-views-selection/metadata/r/ggplot2.yaml b/plots/linked-views-selection/metadata/r/ggplot2.yaml deleted file mode 100644 index 0e7233c3ee..0000000000 --- a/plots/linked-views-selection/metadata/r/ggplot2.yaml +++ /dev/null @@ -1,261 +0,0 @@ -library: ggplot2 -language: r -specification_id: linked-views-selection -created: '2026-05-17T00:44:35Z' -updated: '2026-05-17T01:05:06Z' -generated_by: claude-haiku -workflow_run: 25977124451 -issue: 3344 -language_version: 4.4.1 -library_version: 3.5.1 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/r/ggplot2/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/linked-views-selection/r/ggplot2/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 81 -review: - strengths: - - Perfect theme-adaptive chrome in both renders with correct color adaptation - - 'Excellent palette compliance with Okabe-Ito order and brand green #009E73' - - Clean code structure with proper reproducibility (set.seed(42)) - - All text explicitly sized (title 28pt, labels 20pt, ticks 16pt) and readable at - full resolution - - No overlap or legibility issues in either light or dark theme - - Good canvas utilization (70-80%) and balanced three-panel facet layout - - Iris dataset is canonical scientific choice showing realistic relationships across - dimensions - - Correct title format and legend labels with proper positioning - weaknesses: - - Unused tidyr library import - remove from code for clean imports - - Design is generic library default (DE-01=4) - could add custom visual sophistication - through refined styling choices - - Limited visual refinement (DE-02=4) - grid lines could be subtler (reduce to y-axis - only); spines already removed correctly - - No visual storytelling (DE-03=2) - three views displayed but no emphasis, hierarchy, - or focal points to guide reader interpretation - - Limited Library Mastery (LM-02=2) - facet_wrap is standard; could leverage distinctive - ggplot2 features like stat layers or custom scales - - 'Spec limitation: interactive linked selection is impossible in static ggplot2; - implementation provides best static alternative (consistent colors) but cannot - show selection highlighting or de-emphasis of unselected points' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) - exactly correct per style guide - Chrome: Title "linked-views-selection · ggplot2 · anyplot.ai" in dark text (28pt), axis labels and tick labels in soft gray (INK_SOFT, 16pt), legend titled "Species" positioned below plot with colored facet strip backgrounds - Data: Three iris species color-encoded with Okabe-Ito palette - setosa #009E73 (bluish green), versicolor #D55E00 (vermillion), virginica #0072B2 (blue); approximately 50 points per panel with size=5 and alpha=0.75 for good density visualization - Grid: Subtle grid lines visible at GRID color (#D4D4D0), not competing with data - Layout: Three faceted scatter plots arranged horizontally showing (1) Sepal Length vs Width, (2) Sepal vs Petal Length, (3) Petal Length vs Width. Facet borders visible with headers. Colored strip backgrounds improve visual separation. - Axis labels: "Measurement (cm)" present with units; facet titles provide specific dimension descriptions - Legibility verdict: PASS - All text clearly readable with strong contrast against light background. No light-on-light failures. Spines have been removed for clean L-shaped frames. - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) - exactly correct per style guide - Chrome: Title and axis labels in light color (INK=#F0EFE8, 28pt for title, 16-20pt for labels), tick labels in lighter gray (INK_SOFT=#B8B7B0), legend text light-colored. Facet strip backgrounds darker (ELEVATED_BG = #242420). - Data: Three iris species colors IDENTICAL to light render (#009E73, #D55E00, #0072B2) - perfect color consistency across themes. Only chrome (background, text, grid) has adapted to dark theme, data colors remain constant. - Grid: Subtle light gray lines maintaining consistency with light render visual weight - Layout: Identical three-panel faceted structure. Facet borders clearly visible and readable against dark background. - Axis labels: Same "Measurement (cm)" labels present and readable - Legibility verdict: PASS - All text clearly readable against dark background. No dark-on-dark failures detected - text is light-colored against dark background. Brand green (#009E73) for setosa is distinctly visible and identifiable in dark theme. - Theme consistency: PERFECT - Data colors constant across both renders, chrome correctly adapts to theme, exactly per style guide requirements - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: 'All text explicitly sized: title 28pt, labels 20pt, ticks 16pt. - Perfect readability in both light and dark themes.' - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: Zero overlapping text elements. Three panels well-spaced, legend - centered below, no text collisions. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: 50 points per panel with size=5, alpha=0.75. Optimal density adaptation - for visibility and overlap handling. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette is CVD-safe. Strong contrast between species. No - red-green only coding. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Plot fills 70-80% of canvas. Balanced margins. Three-panel layout - uses space efficiently. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title format correct. Axis labels with units present; facet titles - provide dimension descriptions. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series setosa=#009E73 (brand green). versicolor=#D55E00, virginica=#0072B2 - correct Okabe-Ito order. Background #FAF8F1 light / #1A1A17 dark correct. - Theme chrome flips perfectly.' - design_excellence: - score: 10 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: false - comment: Clean, well-configured library defaults with correct color palette. - Professional but lacks custom visual hierarchy or design choices beyond - defaults. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: false - comment: Colored facet backgrounds and margins visible. Grid could be subtler; - consider y-axis only for cleaner appearance. - - id: DE-03 - name: Data Storytelling - score: 2 - max: 6 - passed: false - comment: Three perspectives displayed with consistent colors but no visual - hierarchy or emphasis to guide interpretation. - spec_compliance: - score: 12 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 3 - max: 5 - passed: false - comment: Three coordinated scatter plots show correct base structure. Spec's - PRIMARY feature (interactive linked selection) impossible in static ggplot2. - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: false - comment: 'PRESENT: Multiple views, consistent color encoding. MISSING: Interactive - selection mechanism, de-emphasis of unselected points, reset button, selection - count.' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X/Y correctly assigned. All data visible. Axes ranges appropriate. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title format correct. Legend labels match data. Properly positioned. - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Iris dataset shows all three species with distinct distributions - and natural correlations. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Iris is canonical, peer-reviewed scientific dataset. Neutral and - real-world-plausible. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: All values from actual iris measurements. Proportions and scales - factually correct. - code_quality: - score: 9 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Clear structure: imports → theme → data → plot → save. No unnecessary - functions.' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: set.seed(42) ensures reproducible data. All outputs deterministic. - - id: CQ-03 - name: Clean Imports - score: 1 - max: 2 - passed: false - comment: tidyr imported but NEVER used. Remove this import. ggplot2, dplyr, - ragg actively used. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean, straightforward, appropriate complexity. No over-engineering - or fake UI. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png correctly with ragg device, proper dimensions. - library_mastery: - score: 5 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 3 - max: 5 - passed: false - comment: Uses ggplot2's high-level API correctly. However, doesn't leverage - advanced features. - - id: LM-02 - name: Distinctive Features - score: 2 - max: 5 - passed: false - comment: facet_wrap is standard ggplot2. No stat transformations, custom scales, - or advanced features leveraged. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - faceting - patterns: - - dataset-loading - dataprep: [] - styling: - - alpha-blending diff --git a/plots/linked-views-selection/specification.md b/plots/linked-views-selection/specification.md deleted file mode 100644 index 9fed0eacce..0000000000 --- a/plots/linked-views-selection/specification.md +++ /dev/null @@ -1,32 +0,0 @@ -# linked-views-selection: Multiple Linked Views with Selection Sync - -## Description - -Multiple coordinated plots where selecting data in one view automatically highlights or filters corresponding data points in all other views. This brushing-and-linking interaction pattern is fundamental for exploratory data analysis, allowing users to discover relationships across different visual representations of the same dataset. By selecting a cluster in a scatter plot, for example, users can instantly see how those points distribute in histograms, how they appear in parallel coordinates, or which categories they belong to. - -## Applications - -- Exploratory data analysis dashboards where analysts investigate multivariate datasets by brushing across scatter plots, histograms, and categorical views -- Customer segmentation tools where selecting a group in one dimension reveals their characteristics across demographics, behavior, and transactions -- Scientific data exploration comparing different measurements or experimental conditions with synchronized highlighting -- Financial portfolio analysis linking asset allocation views with performance metrics and risk indicators - -## Data - -- `x` (numeric) - Values for scatter plot horizontal axis -- `y` (numeric) - Values for scatter plot vertical axis -- `category` (categorical) - Group labels for bar chart or color encoding -- `value` (numeric) - Additional numeric dimension for histogram or secondary visualizations -- Size: 100-1000 points (interactivity works best with moderate dataset sizes) -- Example: Iris dataset or simulated multivariate data with numeric and categorical columns - -## Notes - -- Implement at least 2-3 coordinated views (e.g., scatter plot + histogram + bar chart) -- Selection in any view should highlight corresponding points in all other views -- Use consistent color encoding across all views for the same data points -- Unselected points should be visually de-emphasized (reduced opacity or gray) -- Include a clear selection mechanism (brush, click, or lasso) -- Provide a reset/clear selection button to restore full view -- Consider showing selection count or summary statistics -- Libraries with native linked selection support (Altair, Bokeh, Plotly) should leverage those features diff --git a/plots/linked-views-selection/specification.yaml b/plots/linked-views-selection/specification.yaml deleted file mode 100644 index 7e9c01db23..0000000000 --- a/plots/linked-views-selection/specification.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Specification-level metadata for linked-views-selection -# Auto-synced to PostgreSQL on push to main - -spec_id: linked-views-selection -title: Multiple Linked Views with Selection Sync - -# Specification tracking -created: 2026-01-08T21:41:58Z -updated: null -issue: 3344 -suggested: MarkusNeusinger - -# Classification tags (applies to all library implementations) -# See docs/reference/tagging-system.md for detailed guidelines -tags: - plot_type: - - scatter - - histogram - - bar - - multi - data_type: - - numeric - - categorical - domain: - - general - - statistics - - business - features: - - interactive - - multi - - coordinated - - brushing diff --git a/plots/map-animated-temporal/implementations/julia/makie.jl b/plots/map-animated-temporal/implementations/julia/makie.jl deleted file mode 100644 index 337edc9abf..0000000000 --- a/plots/map-animated-temporal/implementations/julia/makie.jl +++ /dev/null @@ -1,154 +0,0 @@ -# anyplot.ai -# map-animated-temporal: Animated Map over Time -# Library: makie 0.22.10 | Julia 1.11.9 -# Quality: 86/100 | Created: 2026-05-27 - -using CairoMakie -using Colors -using Random - -Random.seed!(42) - -# Theme tokens -const THEME = get(ENV, "ANYPLOT_THEME", "light") -const PAGE_BG = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17" -const INK = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8" -const INK_SOFT = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0" - -# Grid color as explicit RGBAf (avoids N0f8 promotion ambiguity) -const GRID_COLOR = THEME == "light" ? - RGBAf(26f0/255f0, 26f0/255f0, 23f0/255f0, 0.10f0) : - RGBAf(240f0/255f0, 239f0/255f0, 232f0/255f0, 0.10f0) - -# Sequential colormap: anyplot brand green → blue (single-polarity continuous) -const ANYPLOT_SEQ = cgrad([colorant"#009E73", colorant"#4467A3"]) - -# Earthquake aftershock sequence (synthetic, central Japan) -const N_STEPS = 12 -const MAIN_LAT = 34.5f0 -const MAIN_LON = 135.8f0 -const N_ROWS = 3 -const N_COLS = 4 - -event_lats = Float32[MAIN_LAT] -event_lons = Float32[MAIN_LON] -event_mags = Float32[7.2f0] -event_days = Int[1] - -for day in 1:N_STEPS - n_shocks = max(3, round(Int, 55 / day)) - spread = 0.25f0 + 0.07f0 * sqrt(Float32(day)) - for _ in 1:n_shocks - push!(event_lats, MAIN_LAT + Float32(randn()) * spread) - push!(event_lons, MAIN_LON + Float32(randn()) * spread * 1.3f0) - push!(event_mags, 2.5f0 + Float32(rand()) * 3.2f0) - push!(event_days, day) - end -end - -const LAT_LO = MAIN_LAT - 2.0f0 -const LAT_HI = MAIN_LAT + 2.0f0 -const LON_LO = MAIN_LON - 2.5f0 -const LON_HI = MAIN_LON + 2.5f0 - -# Figure: landscape 1600×900 → 3200×1800 at px_per_unit=2 -fig = Figure( - size = (1600, 900), - fontsize = 11, - backgroundcolor = PAGE_BG, -) - -Label(fig[0, 1:N_COLS], - "map-animated-temporal · julia · makie · anyplot.ai", - fontsize = 20, - color = INK, - font = :bold, - padding = (0, 0, 8, 2), - tellwidth = false, - tellheight = true, -) - -# Small multiples: 12 daily cumulative snapshots in 3×4 grid -for i in 1:N_STEPS - row = div(i - 1, N_COLS) + 1 - col = mod(i - 1, N_COLS) + 1 - - ax = Axis( - fig[row, col]; - title = "Day $i", - titlesize = 11, - titlecolor = INK, - backgroundcolor = PAGE_BG, - topspinevisible = false, - rightspinevisible = false, - leftspinecolor = INK_SOFT, - bottomspinecolor = INK_SOFT, - xticklabelsize = 8, - yticklabelsize = 8, - xticklabelcolor = INK_SOFT, - yticklabelcolor = INK_SOFT, - xtickcolor = INK_SOFT, - ytickcolor = INK_SOFT, - xgridcolor = GRID_COLOR, - ygridcolor = GRID_COLOR, - xticklabelsvisible = (row == N_ROWS), - yticklabelsvisible = (col == 1), - xlabel = "Longitude", - ylabel = "Latitude", - xlabelcolor = INK_SOFT, - ylabelcolor = INK_SOFT, - xlabelsize = 8, - ylabelsize = 8, - xlabelvisible = (row == N_ROWS), - ylabelvisible = (col == 1), - ) - limits!(ax, LON_LO, LON_HI, LAT_LO, LAT_HI) - - # Cumulative events up to day i - mask = event_days .<= i - lons_cum = event_lons[mask] - lats_cum = event_lats[mask] - mags_cum = event_mags[mask] - days_cum = Float32.(event_days[mask]) - time_frac = (days_cum .- 1f0) ./ Float32(N_STEPS - 1) - sizes_pt = clamp.((mags_cum .- 2.5f0) .* 2.5f0 .+ 3f0, 2f0, 11f0) - - # Color encodes age: green = early (day 1), blue = late (day 12) - scatter!(ax, lons_cum, lats_cum; - color = time_frac, - colormap = ANYPLOT_SEQ, - markersize = sizes_pt, - strokewidth = 0, - alpha = 0.7, - ) - - # Mainshock star marker (M7.2 epicentre) - scatter!(ax, [MAIN_LON], [MAIN_LAT]; - color = colorant"#AE3030", - markersize = 12, - marker = :star5, - strokewidth = 0.8f0, - strokecolor = INK, - ) -end - -# Shared colorbar -Colorbar(fig[N_ROWS + 1, 1:N_COLS]; - colormap = ANYPLOT_SEQ, - limits = (1f0, Float32(N_STEPS)), - label = "Day of sequence (green = early | blue = late)", - labelcolor = INK, - tickcolor = INK_SOFT, - ticklabelcolor = INK_SOFT, - ticklabelsize = 10, - labelsize = 10, - vertical = false, - height = 14, -) - -rowsize!(fig.layout, 0, Fixed(50)) -rowsize!(fig.layout, N_ROWS + 1, Fixed(60)) -colgap!(fig.layout, 6) -rowgap!(fig.layout, 6) - -save("plot-$(THEME).png", fig; px_per_unit = 2) diff --git a/plots/map-animated-temporal/implementations/python/altair.py b/plots/map-animated-temporal/implementations/python/altair.py deleted file mode 100644 index 244dd72c0c..0000000000 --- a/plots/map-animated-temporal/implementations/python/altair.py +++ /dev/null @@ -1,177 +0,0 @@ -""" anyplot.ai -map-animated-temporal: Animated Map over Time -Library: altair 6.1.0 | Python 3.13.13 -Quality: 85/100 | Created: 2026-05-27 -""" - -import json -import os -import sys - - -# This file is named altair.py; remove its directory from sys.path before -# importing the installed altair package to avoid shadowing it. -_here = os.path.dirname(os.path.abspath(__file__)) -sys.path = [p for p in sys.path if os.path.abspath(p) != _here] - -import altair as alt -import numpy as np -import pandas as pd -from PIL import Image - - -sys.path.insert(0, _here) # restore for relative-path file saves - -# 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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" -LAND_FILL = "#CCC4B0" if THEME == "light" else "#2D2D29" -OCEAN_FILL = "#E0D8C8" if THEME == "light" else "#1E1E1C" - -# Data: synthetic earthquake aftershock sequence (Omori's Law decay) -np.random.seed(42) -lat_center, lon_center = 38.3, 142.4 # Offshore Tohoku -n_days = 30 - -records = [] -for day in range(n_days): - n_quakes = max(2, int(20 * np.exp(-0.09 * day) + np.random.randint(-2, 3))) - for _ in range(n_quakes): - records.append( - { - "day": day, - "day_label": f"Day {day + 1:02d}", - "lat": lat_center + np.random.normal(0, 0.9), - "lon": lon_center + np.random.normal(0, 1.3), - "magnitude": round(np.clip(np.random.exponential(0.9) + 2.0, 2.0, 6.8), 1), - } - ) - -df = pd.DataFrame(records) -df["label"] = df.apply(lambda r: f"M{r.magnitude:.1f} · Day {r.day + 1}", axis=1) - -# World country polygons (Vega CDN — standard topo used by all Vega/Altair examples) -world = alt.topo_feature("https://vega.github.io/vega-datasets/data/world-110m.json", "countries") - -# Title (scaled for length) -title_str = "Earthquake Aftershock Sequence · map-animated-temporal · python · altair · anyplot.ai" -n = len(title_str) -title_fontsize = max(11, round(16 * (67 / n if n > 67 else 1.0))) - -# Play/pause checkbox param -play_param = alt.param(name="playing", value=False, bind=alt.binding_checkbox(name="Play / Pause ")) - -# Time slider — default at mid-sequence so PNG shows meaningful data -time_param = alt.param( - name="selected_day", value=15, bind=alt.binding_range(min=0, max=n_days - 1, step=1, name="Day: ") -) - -# Base map layer (land + borders) -base_map = alt.Chart(world).mark_geoshape(fill=LAND_FILL, stroke=INK_MUTED, strokeWidth=0.5) - -# Earthquake circles — cumulative up to selected_day -points = ( - alt.Chart(df) - .mark_circle(opacity=0.72, stroke=PAGE_BG, strokeWidth=0.6) - .encode( - longitude="lon:Q", - latitude="lat:Q", - size=alt.Size( - "magnitude:Q", - scale=alt.Scale(range=[30, 480], domain=[2.0, 7.0]), - legend=alt.Legend(title="Magnitude", orient="bottom-right", symbolOpacity=0.75), - ), - color=alt.Color("magnitude:Q", scale=alt.Scale(range=["#009E73", "#4467A3"]), legend=None), - tooltip=[ - alt.Tooltip("label:N", title="Event"), - alt.Tooltip("magnitude:Q", title="Magnitude", format=".1f"), - alt.Tooltip("lat:Q", title="Latitude", format=".2f"), - alt.Tooltip("lon:Q", title="Longitude", format=".2f"), - ], - ) - .transform_filter("datum.day <= selected_day") -) - -# Dynamic timestamp overlay — positioned lower-left of the study area -day_text_layer = ( - alt.Chart(pd.DataFrame([{"lon": 136.5, "lat": 33.5}])) - .mark_text(align="left", baseline="middle", fontWeight="bold", fontSize=13, color=INK, opacity=0.9) - .transform_calculate(day_str='"Day " + (selected_day + 1) + " / 30"') - .encode(longitude="lon:Q", latitude="lat:Q", text="day_str:N") -) - -# Compose layers with mercator projection centered on study area -chart = ( - alt.layer(base_map, points, day_text_layer) - .project(type="mercator", center=[lon_center, lat_center], scale=1400) - .properties( - width=600, - height=310, - background=PAGE_BG, - title=alt.TitleParams( - title_str, - subtitle="Use slider or Play / Pause to animate cumulative aftershocks", - subtitleColor=INK_SOFT, - subtitleFontSize=10, - fontSize=title_fontsize, - color=INK, - anchor="start", - offset=8, - ), - ) - .add_params(time_param, play_param) - .configure_view(fill=OCEAN_FILL, strokeWidth=0) - .configure_legend( - fillColor=ELEVATED_BG, - strokeColor=INK_SOFT, - labelColor=INK_SOFT, - titleColor=INK, - labelFontSize=10, - titleFontSize=10, - ) -) - -# Save PNG (static render — timer param not needed for static snapshot) -TW, TH = 3200, 1800 -chart.save(f"plot-{THEME}.png", scale_factor=4.0) - -_img = Image.open(f"plot-{THEME}.png").convert("RGB") -_w, _h = _img.size -if _w > TW or _h > TH: - raise SystemExit( - f"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. " - f"Shrink chart .properties(width=, height=) values and re-render." - ) -if _w < TW or _h < TH: - _canvas = Image.new("RGB", (TW, TH), PAGE_BG) - _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2)) - _canvas.save(f"plot-{THEME}.png") - -# Inject Vega-Lite timer signal for auto-play in the interactive HTML output -chart_dict = chart.to_dict() -for _param in chart_dict.get("params", []): - if _param.get("name") == "selected_day": - _param["on"] = [ - { - "events": {"type": "timer", "throttle": 800}, - "update": f"playing ? (selected_day + 1) % {n_days} : selected_day", - } - ] - break - -_spec = json.dumps(chart_dict) -_html = ( - "" - "" - "" - "" - "
" - f"" - "" -) -with open(f"plot-{THEME}.html", "w") as _f: - _f.write(_html) diff --git a/plots/map-animated-temporal/implementations/python/bokeh.py b/plots/map-animated-temporal/implementations/python/bokeh.py deleted file mode 100644 index 2bd6e927b0..0000000000 --- a/plots/map-animated-temporal/implementations/python/bokeh.py +++ /dev/null @@ -1,268 +0,0 @@ -""" anyplot.ai -map-animated-temporal: Animated Map over Time -Library: bokeh 3.9.0 | Python 3.13.13 -Quality: 92/100 | Updated: 2026-05-27 -""" - -import os -import sys -import time -from pathlib import Path - - -# Change to script directory for consistent file saving -script_dir = Path(__file__).parent.absolute() -os.chdir(script_dir) - -# Remove current directory from sys.path to avoid shadowing the bokeh package -sys.path = [p for p in sys.path if p != str(script_dir) and p != ""] -if __name__ in sys.modules: - del sys.modules[__name__] - -import numpy as np -import pandas as pd -from bokeh.io import output_file, save -from bokeh.layouts import column, row -from bokeh.models import Button, ColumnDataSource, CustomJS, HoverTool, Label, Slider -from bokeh.plotting import figure -from selenium import webdriver -from selenium.webdriver.chrome.options import Options - - -# Theme -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" - -BRAND = "#009E73" # Imprint palette position 1 — always first series -EPICENTER_COLOR = "#AE3030" # matte red — semantic anchor for hazard - -# Data — earthquake aftershock sequence, Japan region, 20-day simulation -np.random.seed(42) - -n_days = 20 -points_per_day = 25 -epicenter_lat = 35.0 -epicenter_lon = 140.0 - -timestamps, latitudes, longitudes, magnitudes = [], [], [], [] - -for day in range(n_days): - spread = 0.5 + day * 0.15 - base_mag = 5.0 - day * 0.15 - for _ in range(points_per_day): - angle = np.random.uniform(0, 2 * np.pi) - dist = np.random.exponential(spread) - lat = epicenter_lat + dist * np.sin(angle) - lon = epicenter_lon + dist * np.cos(angle) - mag = max(2.0, base_mag + np.random.normal(0, 0.5)) - timestamps.append(day) - latitudes.append(lat) - longitudes.append(lon) - magnitudes.append(mag) - -df = pd.DataFrame({"day": timestamps, "lat": latitudes, "lon": longitudes, "magnitude": magnitudes}) - -# Cumulative frames for JS animation -all_frames = {} -for day in range(n_days): - day_data = df[df["day"] <= day] - all_frames[day] = { - "lat": day_data["lat"].tolist(), - "lon": day_data["lon"].tolist(), - "magnitude": day_data["magnitude"].tolist(), - "size": (day_data["magnitude"] * 4).tolist(), - "alpha": [0.35 + 0.65 * (1.0 - (day - d) / n_days) for d in day_data["day"]], - } - -# Title with scaled fontsize -title_str = "Earthquake Aftershocks · map-animated-temporal · python · bokeh · anyplot.ai" -n_chars = len(title_str) -title_pt = max(34, round(50 * 67 / n_chars)) if n_chars > 67 else 50 -title_fontsize = f"{title_pt}pt" - -# Initial data source (day 0) -source = ColumnDataSource( - data={ - "lat": all_frames[0]["lat"], - "lon": all_frames[0]["lon"], - "magnitude": all_frames[0]["magnitude"], - "size": all_frames[0]["size"], - "alpha": all_frames[0]["alpha"], - } -) -all_data_source = ColumnDataSource(data={"frames": [all_frames]}) - -# Figure — 3200×1800 landscape canvas -p = figure( - width=3200, - height=1800, - x_range=(130, 150), - y_range=(28, 42), - x_axis_label="Longitude (°E)", - y_axis_label="Latitude (°N)", - title=title_str, - toolbar_location=None, - min_border_bottom=160, - min_border_left=180, - min_border_top=110, - min_border_right=50, -) - -# Font sizing -p.title.text_font_size = title_fontsize -p.xaxis.axis_label_text_font_size = "42pt" -p.yaxis.axis_label_text_font_size = "42pt" -p.xaxis.major_label_text_font_size = "34pt" -p.yaxis.major_label_text_font_size = "34pt" - -# Theme-adaptive chrome -p.background_fill_color = PAGE_BG -p.border_fill_color = PAGE_BG -p.outline_line_color = INK_SOFT -p.title.text_color = INK -p.xaxis.axis_label_text_color = INK -p.yaxis.axis_label_text_color = INK -p.xaxis.major_label_text_color = INK_SOFT -p.yaxis.major_label_text_color = INK_SOFT -p.xaxis.axis_line_color = INK_SOFT -p.yaxis.axis_line_color = INK_SOFT -p.xaxis.major_tick_line_color = INK_SOFT -p.yaxis.major_tick_line_color = INK_SOFT -p.xgrid.grid_line_color = INK -p.ygrid.grid_line_color = INK -p.xgrid.grid_line_alpha = 0.15 -p.ygrid.grid_line_alpha = 0.15 - -# Coastline approximation (Japan region) -coast_lon = [130, 131, 132, 134, 135, 136, 137, 139, 140, 141, 142, 144, 146, 148, 150] -coast_lat = [33, 34, 34.5, 35, 35.5, 36, 37, 38, 40, 41, 42, 42, 41, 40, 39] -p.line(coast_lon, coast_lat, line_width=4, line_color=INK_SOFT, alpha=0.45) - -# Aftershocks (drawn first so epicenter renders on top) -aftershocks = p.scatter( - x="lon", - y="lat", - size="size", - fill_color=BRAND, - fill_alpha="alpha", - line_color=PAGE_BG, - line_width=1, - source=source, - legend_label="Aftershocks", -) - -# Epicenter marker (drawn on top of aftershocks) -p.scatter( - [epicenter_lon], - [epicenter_lat], - size=55, - color=EPICENTER_COLOR, - marker="star", - line_color=INK, - line_width=2, - legend_label="Epicenter (M7.2)", -) - -hover = HoverTool( - tooltips=[("Position", "(@lon{0.2f}°E, @lat{0.2f}°N)"), ("Magnitude", "@magnitude{0.1f}")], renderers=[aftershocks] -) -p.add_tools(hover) - -# Day label overlay -time_label = Label( - x=131, - y=40.2, - text="Day 0", - text_font_size="38pt", - text_color=INK, - text_font_style="bold", - background_fill_color=ELEVATED_BG, - background_fill_alpha=0.85, -) -p.add_layout(time_label) - -# Legend -p.legend.location = "bottom_right" -p.legend.label_text_font_size = "34pt" -p.legend.background_fill_color = ELEVATED_BG -p.legend.border_line_color = INK_SOFT -p.legend.label_text_color = INK_SOFT -p.legend.glyph_height = 40 -p.legend.glyph_width = 40 -p.legend.padding = 20 -p.legend.spacing = 15 -p.legend.margin = 20 - -# Slider and play button -slider = Slider(start=0, end=n_days - 1, value=0, step=1, title="Day", width=1000) -play_button = Button(label="▶ Play", button_type="success", width=200) - -slider_callback = CustomJS( - args={"source": source, "all_data": all_data_source, "time_label": time_label, "n_days": n_days}, - code=""" - const day = cb_obj.value; - const frames = all_data.data['frames'][0]; - const frame = frames[day]; - - source.data['lon'] = frame['lon']; - source.data['lat'] = frame['lat']; - source.data['magnitude'] = frame['magnitude']; - source.data['size'] = frame['size']; - source.data['alpha'] = frame['alpha']; - source.change.emit(); - - time_label.text = 'Day ' + day; - """, -) -slider.js_on_change("value", slider_callback) - -play_callback = CustomJS( - args={"slider": slider, "button": play_button, "n_days": n_days}, - code=""" - if (button.label.includes('Play')) { - button.label = '⏸ Pause'; - button.button_type = 'warning'; - window.animation_interval = setInterval(function() { - const cur = slider.value; - slider.value = cur < n_days - 1 ? cur + 1 : 0; - }, 500); - } else { - button.label = '▶ Play'; - button.button_type = 'success'; - clearInterval(window.animation_interval); - } - """, -) -play_button.js_on_click(play_callback) - -controls = row(play_button, slider) -layout = column(p, controls) - -# Save interactive HTML — figure (1800 px) is at the top; controls sit below the fold -output_file(f"plot-{THEME}.html", title="Earthquake Aftershock Animation") -save(layout) - -# Screenshot: CDP override locks viewport to exact figure dimensions, -# so save_screenshot() captures only the 3200×1800 figure area -W, H = 3200, 1800 -opts = Options() -for arg in ( - "--headless=new", - "--no-sandbox", - "--disable-dev-shm-usage", - "--disable-gpu", - f"--window-size={W},{H}", - "--hide-scrollbars", -): - opts.add_argument(arg) -driver = webdriver.Chrome(options=opts) -driver.execute_cdp_cmd( - "Emulation.setDeviceMetricsOverride", {"width": W, "height": H, "deviceScaleFactor": 1, "mobile": False} -) -driver.get(f"file://{Path(f'plot-{THEME}.html').resolve()}") -time.sleep(3) -driver.save_screenshot(f"plot-{THEME}.png") -driver.quit() diff --git a/plots/map-animated-temporal/implementations/python/letsplot.py b/plots/map-animated-temporal/implementations/python/letsplot.py deleted file mode 100644 index 4da4a82560..0000000000 --- a/plots/map-animated-temporal/implementations/python/letsplot.py +++ /dev/null @@ -1,189 +0,0 @@ -""" anyplot.ai -map-animated-temporal: Animated Map over Time -Library: letsplot 4.10.1 | Python 3.13.13 -Quality: 84/100 | Updated: 2026-05-27 -""" - -import os - -import numpy as np -import pandas as pd -from lets_plot import ( - LetsPlot, - aes, - element_rect, - element_text, - facet_wrap, - geom_livemap, - geom_point, - geom_polygon, - geom_text, - ggplot, - ggsave, - ggsize, - guide_legend, - guides, - labs, - scale_color_gradient, - scale_size, - theme, - theme_void, - tilesets, - xlim, - ylim, -) - - -LetsPlot.setup_html() - -# 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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" -MAP_FILL = "#E0DDD5" if THEME == "light" else "#2A2A26" -MAP_BORDER = "#B0ADA5" if THEME == "light" else "#4A4A44" - -# Data: earthquake aftershock sequence spreading outward over 6 weeks -np.random.seed(42) - -epicenter_lon, epicenter_lat = -119.5, 36.0 -events_per_week = [20, 45, 50, 40, 30, 15] - -data_rows = [] -for week in range(6): - n_week = events_per_week[week] - max_radius = 0.5 + week * 0.4 - for _ in range(n_week): - angle = np.random.uniform(0, 2 * np.pi) - distance = min(np.random.exponential(max_radius * 0.4), max_radius * 1.5) - lon = epicenter_lon + distance * np.cos(angle) - lat = epicenter_lat + distance * np.sin(angle) * 0.8 - magnitude = max(2.0, 4.5 - week * 0.3 + np.random.normal(0, 0.8)) - data_rows.append({"week": week + 1, "lon": lon, "lat": lat, "magnitude": round(magnitude, 1)}) - -df = pd.DataFrame(data_rows) - -# Build cumulative snapshots for 4 key time steps -snapshot_weeks = [1, 2, 4, 6] -df_snapshots = [] -for week in snapshot_weeks: - week_data = df[df["week"] <= week].copy() - week_data["week_label"] = f"Week {week}" - df_snapshots.append(week_data) - -df_facet = pd.concat(df_snapshots, ignore_index=True) - -# Epicenter marker shown across all panels -epicenter_df = pd.DataFrame([{"lon": epicenter_lon, "lat": epicenter_lat, "label": "Epicenter"}]) - -# Simplified California outline basemap -ca_coords = [ - (-124.4, 42.0), - (-124.2, 40.0), - (-123.8, 38.5), - (-122.5, 37.8), - (-122.4, 37.0), - (-121.8, 36.5), - (-121.0, 36.0), - (-120.5, 35.0), - (-120.0, 34.5), - (-119.0, 34.0), - (-118.5, 34.0), - (-117.5, 33.0), - (-117.1, 32.5), - (-116.0, 32.5), - (-115.5, 32.8), - (-114.6, 33.0), - (-114.5, 34.0), - (-114.1, 34.3), - (-114.4, 35.0), - (-114.6, 36.0), - (-117.0, 37.0), - (-118.0, 37.5), - (-118.5, 38.0), - (-119.5, 38.5), - (-120.0, 39.0), - (-120.0, 39.5), - (-121.0, 40.0), - (-122.0, 41.0), - (-124.0, 41.5), - (-124.4, 42.0), -] -df_ca = pd.DataFrame(ca_coords, columns=["x", "y"]) -df_ca["group"] = 0 - -title = "Seismic Activity Spread · map-animated-temporal · python · letsplot · anyplot.ai" -n_chars = len(title) -title_fontsize = max(11, round(16 * (67 / n_chars if n_chars > 67 else 1.0))) - -mag_name = "Magnitude (M)" -# Use matching breaks on both scales to produce a single merged legend — no limits to avoid -# a lets-plot rendering failure when xlim clips a data point outside scale limits -mag_breaks = [2, 3, 4, 5, 6] -caption = "Cumulative aftershock sequence radiating from epicenter (×) | Central California region" - -# PNG: polygon basemap renders cleanly via SVG export -plot_png = ( - ggplot(data=df_facet, mapping=aes(x="lon", y="lat")) - + geom_polygon(data=df_ca, mapping=aes(x="x", y="y", group="group"), fill=MAP_FILL, color=MAP_BORDER, size=0.5) - + geom_point(aes(size="magnitude", color="magnitude"), alpha=0.75) - + geom_point(data=epicenter_df, mapping=aes(x="lon", y="lat"), shape=4, size=7, color=INK, stroke=2) - + geom_text(data=epicenter_df, mapping=aes(x="lon", y="lat", label="label"), nudge_y=0.22, size=3.5, color=INK) - + scale_color_gradient(low="#009E73", high="#4467A3", name=mag_name, breaks=mag_breaks) - + scale_size(range=[2, 9], name=mag_name, breaks=mag_breaks) - + guides(color=guide_legend(nrow=5), size=guide_legend(nrow=5)) - + facet_wrap("week_label", ncol=2) - + xlim(-122.5, -116.5) - + ylim(33.5, 38.5) - + labs(x="Longitude", y="Latitude", title=title, caption=caption) - + theme_void() - + theme( - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - panel_background=element_rect(fill=PAGE_BG), - panel_border=element_rect(color=INK_SOFT, size=0.3), - plot_title=element_text(size=title_fontsize, color=INK, face="bold", hjust=0.5), - plot_caption=element_text(size=9, color=INK_MUTED, hjust=0.5), - strip_text=element_text(size=13, color=INK, face="bold"), - legend_text=element_text(size=10, color=INK_SOFT), - legend_title=element_text(size=11, color=INK), - legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT), - legend_position="right", - axis_title=element_text(size=10, color=INK_SOFT), - axis_text=element_text(size=8, color=INK_MUTED), - ) - + ggsize(800, 450) -) - -# HTML: geom_livemap tile basemap — letsplot's distinctive interactive mapping feature -tiles = tilesets.CARTO_POSITRON if THEME == "light" else tilesets.CARTO_MIDNIGHT_COMMANDER -plot_html = ( - ggplot(data=df_facet, mapping=aes(x="lon", y="lat")) - + geom_livemap(tiles=tiles) - + geom_point(aes(size="magnitude", color="magnitude"), alpha=0.75) - + geom_point(data=epicenter_df, mapping=aes(x="lon", y="lat"), shape=4, size=7, color=INK, stroke=2) - + scale_color_gradient(low="#009E73", high="#4467A3", name=mag_name, breaks=mag_breaks) - + scale_size(range=[2, 9], name=mag_name, breaks=mag_breaks) - + guides(color=guide_legend(nrow=5), size=guide_legend(nrow=5)) - + facet_wrap("week_label", ncol=2) - + xlim(-122.5, -116.5) - + ylim(33.5, 38.5) - + labs(x="Longitude", y="Latitude", title=title, caption=caption) - + theme( - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - plot_title=element_text(size=title_fontsize, color=INK, face="bold", hjust=0.5), - plot_caption=element_text(size=9, color=INK_MUTED, hjust=0.5), - strip_text=element_text(size=13, color=INK, face="bold"), - legend_text=element_text(size=10, color=INK_SOFT), - legend_title=element_text(size=11, color=INK), - legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT), - legend_position="right", - ) - + ggsize(800, 450) -) - -# Save — PNG uses polygon basemap (SVG-compatible), HTML uses geom_livemap (interactive tiles) -ggsave(plot_png, f"plot-{THEME}.png", path=".", scale=4) -ggsave(plot_html, f"plot-{THEME}.html", path=".") diff --git a/plots/map-animated-temporal/implementations/python/matplotlib.py b/plots/map-animated-temporal/implementations/python/matplotlib.py deleted file mode 100644 index bb083d8e0e..0000000000 --- a/plots/map-animated-temporal/implementations/python/matplotlib.py +++ /dev/null @@ -1,265 +0,0 @@ -""" anyplot.ai -map-animated-temporal: Animated Map over Time -Library: matplotlib 3.10.9 | Python 3.13.13 -Quality: 86/100 | Updated: 2026-05-27 -""" - -import os - -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd -from matplotlib.cm import ScalarMappable -from matplotlib.colors import LinearSegmentedColormap, Normalize -from matplotlib.patches import Polygon - - -# 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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -OCEAN_BG = "#C8DDF0" if THEME == "light" else "#1E3A4A" -LAND_COLOR = "#E8E4D8" if THEME == "light" else "#2E2E28" - -# anyplot diverging cmap: blue (cold) → neutral → red (warm) -# Intentionally reversed from canonical imprint_div (red→neutral→blue) so cold anomalies -# appear blue and warm anomalies appear red, matching standard climate science convention. -midpoint = "#FAF8F1" if THEME == "light" else "#1A1A17" -imprint_div = LinearSegmentedColormap.from_list("imprint_div", ["#4467A3", midpoint, "#AE3030"]) - -# Data: European weather station temperature anomalies over 12 months -np.random.seed(42) - -station_coords = [ - (48.86, 2.35), - (51.51, -0.13), - (52.52, 13.40), - (41.90, 12.50), - (40.42, -3.70), - (59.33, 18.07), - (60.17, 24.94), - (55.68, 12.57), - (52.37, 4.90), - (50.85, 4.35), - (48.21, 16.37), - (47.50, 19.04), - (50.08, 14.44), - (52.23, 21.01), - (38.72, -9.14), - (45.46, 9.19), - (43.30, 5.37), - (53.55, 9.99), - (48.14, 11.58), - (45.44, 12.32), - (41.39, 2.17), - (37.98, 23.73), - (44.43, 26.10), - (42.70, 23.32), - (46.95, 7.45), -] -station_names = [ - "Paris", - "London", - "Berlin", - "Rome", - "Madrid", - "Stockholm", - "Helsinki", - "Copenhagen", - "Amsterdam", - "Brussels", - "Vienna", - "Budapest", - "Prague", - "Warsaw", - "Lisbon", - "Milan", - "Marseille", - "Hamburg", - "Munich", - "Venice", - "Barcelona", - "Athens", - "Bucharest", - "Sofia", - "Bern", -] - -n_months = 12 -months = pd.date_range("2025-01-01", periods=n_months, freq="ME") -month_names = [m.strftime("%b %Y") for m in months] - -lats = np.array([s[0] for s in station_coords]) -lons = np.array([s[1] for s in station_coords]) -n_stations = len(station_coords) - -# Warming pattern: base anomaly grows over time, spreading north from southern stations -anomalies = np.zeros((n_months, n_stations)) -for t in range(n_months): - base = 0.5 + t * 0.15 - lat_effect = (50 - lats) * 0.03 - lag = np.maximum(0, (t - (lats - 35) / 5)) * 0.1 - anomalies[t] = base + lat_effect + lag + np.random.normal(0, 0.3, n_stations) - -# Simplified Europe coastline as (lon, lat) polygon vertices -europe_coastline = [ - [(-10, 36), (-6, 37), (-2, 36), (3, 43), (0, 44), (-2, 43), (-8, 44), (-9, 42), (-10, 36)], - [(-6, 50), (-5, 54), (-4, 58), (-8, 58), (-6, 55), (-6, 50)], - [ - (-5, 43), - (0, 43), - (3, 43), - (6, 44), - (8, 44), - (12, 46), - (14, 45), - (14, 41), - (16, 40), - (20, 40), - (24, 37), - (26, 38), - (28, 41), - (30, 42), - (32, 42), - (28, 56), - (24, 55), - (22, 56), - (19, 55), - (14, 54), - (12, 56), - (10, 56), - (10, 54), - (5, 54), - (3, 51), - (4, 51), - (5, 49), - (8, 48), - (10, 47), - (12, 44), - (10, 47), - (8, 48), - (5, 49), - (4, 51), - (3, 51), - (2, 50), - (-2, 50), - (-5, 48), - (-5, 43), - ], - [ - (5, 58), - (10, 62), - (12, 65), - (20, 70), - (28, 70), - (30, 60), - (24, 55), - (22, 56), - (19, 55), - (14, 54), - (12, 56), - (10, 56), - (5, 58), - ], - [(8, 44), (12, 46), (14, 45), (16, 38), (15, 37), (12, 38), (10, 42), (8, 44)], - [(20, 40), (24, 37), (26, 38), (28, 41), (24, 40), (20, 40)], -] - -vmin, vmax = -1.0, 3.5 -# Key snapshots: every other month — Jan, Mar, May, Jul, Sep, Nov -snapshot_indices = [0, 2, 4, 6, 8, 10] - -# Plot: 2×3 small-multiples grid of key monthly snapshots -title = "map-animated-temporal · python · matplotlib · anyplot.ai" -fig, axes = plt.subplots(2, 3, figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG) -fig.suptitle(title, fontsize=11, fontweight="medium", color=INK, y=0.995) - -for ax, month_idx in zip(axes.flat, snapshot_indices, strict=False): - ax.set_facecolor(OCEAN_BG) - - for coastline in europe_coastline: - poly = Polygon(coastline, facecolor=LAND_COLOR, edgecolor=INK_SOFT, linewidth=0.3, zorder=1) - ax.add_patch(poly) - - # Graticule lines — more visible than minimal dotted hairlines - for lat in range(35, 75, 10): - ax.axhline(y=lat, color=INK_SOFT, linewidth=0.5, linestyle=":", alpha=0.55, zorder=0) - for lon in range(-10, 35, 10): - ax.axvline(x=lon, color=INK_SOFT, linewidth=0.5, linestyle=":", alpha=0.55, zorder=0) - - ax.set_xlim(-15, 35) - ax.set_ylim(33, 72) - - current = anomalies[month_idx] - sizes = 60 + np.abs(current) * 50 - - ax.scatter( - lons, - lats, - c=current, - s=sizes, - cmap=imprint_div, - vmin=vmin, - vmax=vmax, - alpha=0.9, - edgecolors=INK, - linewidths=0.6, - zorder=5, - ) - - ax.text( - 0.04, - 0.96, - month_names[month_idx], - transform=ax.transAxes, - fontsize=7, - fontweight="bold", - ha="left", - va="top", - color=INK, - bbox={"facecolor": ELEVATED_BG, "edgecolor": "none", "alpha": 0.85, "pad": 1.5}, - ) - - if month_idx == snapshot_indices[-1]: # Nov: annotate peak anomaly station - peak_idx = np.argmax(current) - ax.annotate( - f"+{current[peak_idx]:.1f}°C\n{station_names[peak_idx]}", - xy=(lons[peak_idx], lats[peak_idx]), - xytext=(0.95, 0.12), - textcoords="axes fraction", - fontsize=5.5, - color=INK, - ha="right", - va="bottom", - arrowprops={"arrowstyle": "->", "color": INK_MUTED, "lw": 0.6, "shrinkA": 2, "shrinkB": 3}, - bbox={"facecolor": ELEVATED_BG, "edgecolor": INK_SOFT, "alpha": 0.9, "pad": 1.5, "linewidth": 0.3}, - ) - - ax.tick_params(axis="both", labelsize=6, colors=INK_SOFT) - for spine in ax.spines.values(): - spine.set_color(INK_SOFT) - spine.set_linewidth(0.4) - -# Axis labels on left column and bottom row only -for row in range(2): - axes[row, 0].set_ylabel("Latitude (°)", fontsize=7, color=INK) -for col in range(3): - axes[1, col].set_xlabel("Longitude (°)", fontsize=7, color=INK) - -# Layout and shared colorbar -fig.subplots_adjust(left=0.07, right=0.87, top=0.94, bottom=0.07, hspace=0.18, wspace=0.14) -cbar_ax = fig.add_axes([0.895, 0.12, 0.016, 0.76]) -sm = ScalarMappable(cmap=imprint_div, norm=Normalize(vmin=vmin, vmax=vmax)) -sm.set_array([]) -cbar = fig.colorbar(sm, cax=cbar_ax) -cbar.set_label("Temp. Anomaly (°C)", fontsize=7, color=INK) -cbar.ax.tick_params(labelsize=6, colors=INK_SOFT) -cbar.outline.set_edgecolor(INK_SOFT) - -# Save -plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG) -plt.close() diff --git a/plots/map-animated-temporal/implementations/python/plotly.py b/plots/map-animated-temporal/implementations/python/plotly.py deleted file mode 100644 index 7b2bb218dc..0000000000 --- a/plots/map-animated-temporal/implementations/python/plotly.py +++ /dev/null @@ -1,229 +0,0 @@ -""" anyplot.ai -map-animated-temporal: Animated Map over Time -Library: plotly 6.7.0 | Python 3.13.13 -Quality: 90/100 | Updated: 2026-05-27 -""" - -import os - -import numpy as np -import plotly.graph_objects as go - - -# 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" -GRID = "rgba(26,26,23,0.15)" if THEME == "light" else "rgba(240,239,232,0.15)" -LAND_COLOR = "#E0DDD5" if THEME == "light" else "#252520" -OCEAN_COLOR = "#C5D5E5" if THEME == "light" else "#1E2832" -BORDER_COLOR = "#888888" if THEME == "light" else "#555555" - -# Continuous colorscale (imprint_seq — sequential, single-polarity) -imprint_seq = [[0.0, "#009E73"], [1.0, "#4467A3"]] - -# Data: Simulated earthquake aftershock sequence (off Japan coast) -np.random.seed(42) -n_days = 15 -epicenter_lat, epicenter_lon = 38.3, 142.4 - -days_data = {} -for day in range(n_days): - n_points = 30 + np.random.randint(-10, 15) - spread = 0.5 + day * 0.3 - intensity_decay = 1.0 - day * 0.05 - lats, lons, mags = [], [], [] - for _ in range(n_points): - angle = np.random.uniform(0, 2 * np.pi) - distance = np.abs(np.random.normal(0, spread)) - lats.append(epicenter_lat + distance * np.cos(angle)) - lons.append(epicenter_lon + distance * np.sin(angle)) - mags.append(min(7.5, max(2.0, np.random.exponential(2.5) * intensity_decay))) - days_data[day] = {"lat": lats, "lon": lons, "mag": mags} - -# Plot: base figure initialised with Day 1 data -cd0 = days_data[0] -fig = go.Figure() - -fig.add_trace( - go.Scattergeo( # trail layer — ghost points from previous 2 days - lat=[], - lon=[], - mode="markers", - marker=dict(size=4, color="#009E73", opacity=0.2), - showlegend=False, - hoverinfo="skip", - ) -) -fig.add_trace( - go.Scattergeo( # current-day layer - lat=cd0["lat"], - lon=cd0["lon"], - mode="markers", - marker=dict( - size=[max(4, m * 3.5) for m in cd0["mag"]], - color=cd0["mag"], - coloraxis="coloraxis", - opacity=0.75, - line=dict(width=0.5, color=INK_SOFT), - ), - hovertemplate="Lat: %{lat:.2f}
Lon: %{lon:.2f}", - showlegend=False, - ) -) - -# Build animation frames with trailing ghost points -frames = [] -for day in range(n_days): - trail_lats, trail_lons = [], [] - for d in range(max(0, day - 2), day): - trail_lats.extend(days_data[d]["lat"]) - trail_lons.extend(days_data[d]["lon"]) - - cd = days_data[day] - frames.append( - go.Frame( - data=[ - go.Scattergeo( - lat=trail_lats, - lon=trail_lons, - mode="markers", - marker=dict(size=4, color="#009E73", opacity=0.2), - hoverinfo="skip", - ), - go.Scattergeo( - lat=cd["lat"], - lon=cd["lon"], - mode="markers", - marker=dict( - size=[max(4, m * 3.5) for m in cd["mag"]], - color=cd["mag"], - coloraxis="coloraxis", - opacity=0.75, - line=dict(width=0.5, color=INK_SOFT), - ), - hovertemplate="Lat: %{lat:.2f}
Lon: %{lon:.2f}", - ), - ], - name=f"Day {day + 1:02d}", - ) - ) - -fig.frames = frames - -# Title with adaptive fontsize (longer title shrinks proportionally) -title_text = "Earthquake Aftershock Sequence · map-animated-temporal · python · plotly · anyplot.ai" -n = len(title_text) -title_fontsize = max(11, round(16 * min(1.0, 67 / n))) - -slider_steps = [ - dict( - args=[ - [f"Day {i + 1:02d}"], - {"frame": {"duration": 300, "redraw": True}, "mode": "immediate", "transition": {"duration": 300}}, - ], - label=f"Day {i + 1}", - method="animate", - ) - for i in range(n_days) -] - -# Style -fig.update_layout( - autosize=False, - paper_bgcolor=PAGE_BG, - font=dict(color=INK), - title=dict(text=title_text, font=dict(size=title_fontsize, color=INK), x=0.5, xanchor="center"), - geo=dict( - scope="asia", - center=dict(lat=epicenter_lat, lon=epicenter_lon), - projection_scale=4, - showland=True, - landcolor=LAND_COLOR, - showocean=True, - oceancolor=OCEAN_COLOR, - showcountries=True, - countrycolor=BORDER_COLOR, - countrywidth=1, - showcoastlines=True, - coastlinecolor=BORDER_COLOR, - coastlinewidth=1.5, - showlakes=True, - lakecolor=OCEAN_COLOR, - lataxis=dict(showgrid=True, gridcolor=GRID), - lonaxis=dict(showgrid=True, gridcolor=GRID), - bgcolor=PAGE_BG, - ), - coloraxis=dict( - colorscale=imprint_seq, - cmin=2, - cmax=7.5, - colorbar=dict( - title=dict(text="Magnitude", font=dict(size=12, color=INK)), - tickfont=dict(size=10, color=INK_SOFT), - bgcolor=ELEVATED_BG, - bordercolor=INK_SOFT, - borderwidth=1, - len=0.6, - thickness=15, - ), - ), - margin=dict(l=20, r=20, t=70, b=120), - updatemenus=[ - dict( - type="buttons", - showactive=True, - y=0.08, - x=0.05, - xanchor="left", - buttons=[ - dict( - label="▶ Play", - method="animate", - args=[ - None, - { - "frame": {"duration": 800, "redraw": True}, - "fromcurrent": True, - "transition": {"duration": 300, "easing": "cubic-in-out"}, - }, - ], - ), - dict( - label="⏸ Pause", - method="animate", - args=[ - [None], - {"frame": {"duration": 0, "redraw": False}, "mode": "immediate", "transition": {"duration": 0}}, - ], - ), - ], - font=dict(size=12, color=INK), - bgcolor=ELEVATED_BG, - bordercolor=INK_SOFT, - ) - ], - sliders=[ - dict( - active=0, - yanchor="top", - xanchor="left", - currentvalue=dict(font=dict(size=12, color=INK), prefix="Time: ", visible=True, xanchor="center"), - transition=dict(duration=300, easing="cubic-in-out"), - pad=dict(b=10, t=50), - len=0.9, - x=0.05, - y=0, - steps=slider_steps, - font=dict(color=INK_SOFT), - bgcolor=ELEVATED_BG, - bordercolor=INK_SOFT, - ) - ], -) - -# Save -fig.write_image(f"plot-{THEME}.png", width=800, height=450, scale=4) -fig.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn", full_html=True) diff --git a/plots/map-animated-temporal/implementations/python/plotnine.py b/plots/map-animated-temporal/implementations/python/plotnine.py deleted file mode 100644 index 6a2713d3d4..0000000000 --- a/plots/map-animated-temporal/implementations/python/plotnine.py +++ /dev/null @@ -1,174 +0,0 @@ -""" anyplot.ai -map-animated-temporal: Animated Map over Time -Library: plotnine 0.15.4 | Python 3.13.13 -Quality: 81/100 | Updated: 2026-05-27 -""" - -import os -import sys - - -_here = os.path.dirname(os.path.abspath(__file__)) -sys.path = [p for p in sys.path if os.path.abspath(p or os.getcwd()) != _here] - -import numpy as np -import pandas as pd -from plotnine import ( - aes, - coord_fixed, - element_blank, - element_line, - element_rect, - element_text, - facet_wrap, - geom_path, - geom_point, - ggplot, - labs, - scale_color_gradient, - theme, - theme_minimal, -) - - -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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -# Data - Simulated earthquake aftershock sequence across California region -np.random.seed(42) - -n_timesteps = 12 -timestamps = pd.date_range("2024-03-01", periods=n_timesteps, freq="D") - -california_coast = pd.DataFrame( - { - "lon": [ - -124.4, - -124.2, - -123.8, - -122.4, - -122.0, - -121.8, - -121.5, - -120.6, - -120.1, - -119.5, - -118.5, - -117.9, - -117.2, - -117.1, - -117.3, - -114.6, - -114.6, - -117.1, - -119.0, - -120.0, - -121.0, - -122.4, - -123.2, - -124.2, - -124.4, - ], - "lat": [ - 40.0, - 41.0, - 41.8, - 42.0, - 41.0, - 39.5, - 38.5, - 37.5, - 36.5, - 35.0, - 34.0, - 33.5, - 33.0, - 32.5, - 32.6, - 32.7, - 34.8, - 34.8, - 36.5, - 38.0, - 39.0, - 40.0, - 41.0, - 41.5, - 40.0, - ], - } -) - -epicenter_lat, epicenter_lon = 36.0, -118.5 -epicenter_df = pd.DataFrame({"lon": [epicenter_lon], "lat": [epicenter_lat]}) - -data_rows = [] - -for i, ts in enumerate(timestamps): - n_points = int(30 + 20 * np.sin(np.pi * i / (n_timesteps - 1))) - spread = 0.5 + i * 0.15 - for _ in range(n_points): - lat = epicenter_lat + np.random.normal(0, spread) - lon = epicenter_lon + np.random.normal(0, spread * 1.2) - magnitude = max(1.0, 5.5 - i * 0.15 + np.random.exponential(0.8)) - data_rows.append( - { - "lat": lat, - "lon": lon, - "timestamp": ts, - "magnitude": magnitude, - "day": f"Day {i + 1}: {ts.strftime('%b %d')}", - } - ) - -df = pd.DataFrame(data_rows) - -day_order = [f"Day {i + 1}: {ts.strftime('%b %d')}" for i, ts in enumerate(timestamps)] -df["day"] = pd.Categorical(df["day"], categories=day_order, ordered=True) - -# Title fontsize scaled for length -title = "Earthquake Aftershocks · map-animated-temporal · python · plotnine · anyplot.ai" -n = len(title) -title_fontsize = max(8, round(12 * 67 / n)) if n > 67 else 12 - -# Plot -plot = ( - ggplot(df, aes(x="lon", y="lat")) - + geom_path(data=california_coast, mapping=aes(x="lon", y="lat"), color=INK_MUTED, size=0.6, inherit_aes=False) - + geom_point(aes(color="magnitude"), size=2.5, alpha=0.75) - + geom_point(data=epicenter_df, mapping=aes(x="lon", y="lat"), color=INK, shape="*", size=5, inherit_aes=False) - + facet_wrap("~day", ncol=4) - + scale_color_gradient(low="#009E73", high="#4467A3", name="Magnitude") - + coord_fixed(ratio=1.0) - + labs( - title=title, - subtitle="Spatial spread of aftershocks over 12 days following M5.5 main event (★ = epicenter)", - x="Longitude (°)", - y="Latitude (°)", - ) - + theme_minimal() - + theme( - figure_size=(8, 4.5), - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - panel_background=element_rect(fill=PAGE_BG), - panel_grid_major=element_line(color=INK, size=0.3, alpha=0.15), - panel_grid_minor=element_line(color=INK, size=0.2, alpha=0.05), - panel_border=element_blank(), - axis_line=element_line(color=INK_SOFT, size=0.3), - axis_title=element_text(color=INK, size=10), - axis_text=element_text(color=INK_SOFT, size=8), - strip_text=element_text(color=INK, size=6, weight="bold"), - plot_title=element_text(color=INK, size=title_fontsize, weight="bold"), - plot_subtitle=element_text(color=INK_SOFT, size=7), - legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT), - legend_text=element_text(color=INK_SOFT, size=7), - legend_title=element_text(color=INK, size=8), - ) -) - -# Save -plot.save(f"plot-{THEME}.png", dpi=400, width=8, height=4.5, units="in", verbose=False) diff --git a/plots/map-animated-temporal/implementations/python/pygal.py b/plots/map-animated-temporal/implementations/python/pygal.py deleted file mode 100644 index 27de3e34b5..0000000000 --- a/plots/map-animated-temporal/implementations/python/pygal.py +++ /dev/null @@ -1,329 +0,0 @@ -""" anyplot.ai -map-animated-temporal: Animated Map over Time -Library: pygal 3.1.0 | Python 3.13.13 -Quality: 87/100 | Updated: 2026-05-27 -""" - -import importlib.util -import json -import os -import sys -from io import BytesIO - -from PIL import Image, ImageDraw, ImageFont - - -# Prevent this file (pygal.py) from shadowing the installed pygal package -_pygal_spec = importlib.util.find_spec("pygal") -if _pygal_spec and _pygal_spec.origin != __file__: - from pygal.style import Style -else: - _cwd = os.getcwd() - sys.path = [p for p in sys.path if os.path.abspath(p) != _cwd] - try: - from pygal.style import Style - finally: - sys.path.insert(0, _cwd) - -from pygal_maps_world.maps import World - - -# Theme tokens -THEME = os.getenv("ANYPLOT_THEME", "light") -PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" -INK = "#1A1A17" if THEME == "light" else "#F0EFE8" -INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -# imprint_seq: #009E73 → #4467A3, 6 stops for 6 intensity bins -n_bins = 6 -seq_colors = [] -for i in range(n_bins): - t = i / (n_bins - 1) - r = int(round(0x00 + (0x44 - 0x00) * t)) - g = int(round(0x9E + (0x67 - 0x9E) * t)) - b = int(round(0x73 + (0xA3 - 0x73) * t)) - seq_colors.append(f"#{r:02X}{g:02X}{b:02X}") -seq_colors = tuple(seq_colors) - -# Data: Seismic activity spreading across Pacific Ring of Fire over 6 time steps -time_periods = ["T1: Initial", "T2: +6h", "T3: +12h", "T4: +24h", "T5: +48h", "T6: +72h"] - -activity_by_time = { - 0: {"jp": 95, "kr": 15, "ph": 25, "id": 38}, - 1: {"jp": 85, "kr": 55, "ph": 40, "id": 22, "nz": 15}, - 2: {"jp": 75, "kr": 70, "ph": 65, "id": 50, "tw": 45, "my": 18}, - 3: {"jp": 60, "kr": 55, "ph": 75, "id": 80, "tw": 50, "my": 35, "nz": 40, "au": 22}, - 4: {"jp": 45, "kr": 40, "ph": 60, "id": 70, "tw": 40, "my": 50, "nz": 65, "au": 35, "cl": 30, "pe": 18}, - 5: { - "jp": 35, - "kr": 30, - "ph": 45, - "id": 55, - "tw": 30, - "my": 45, - "nz": 55, - "au": 50, - "cl": 60, - "pe": 40, - "mx": 35, - "ec": 25, - }, -} - -activity_bins = [ - ("Minimal (<20)", 0, 20), - ("Low (20-35)", 20, 35), - ("Moderate (35-50)", 35, 50), - ("High (50-65)", 50, 65), - ("Severe (65-80)", 65, 80), - ("Extreme (80+)", 80, 101), -] - -# Pre-compute binned data once — shared by both PNG and HTML render passes -binned_by_time = [] -for time_idx in range(6): - data = activity_by_time[time_idx] - binned = {label: {} for label, _, _ in activity_bins} - for country, value in data.items(): - for label, low, high in activity_bins: - if low <= value < high: - binned[label][country] = value - break - binned_by_time.append(binned) - -# Canvas layout: 3200×1800 (landscape) -CANVAS_W = 3200 -CANVAS_H = 1800 -TITLE_H = 110 -SUBTITLE_H = 60 -LEGEND_H = 130 -GRID_H = CANVAS_H - TITLE_H - SUBTITLE_H - LEGEND_H # 1500 -GRID_ROWS = 2 -GRID_COLS = 3 -CELL_W = CANVAS_W // GRID_COLS # 1066 -CELL_H = GRID_H // GRID_ROWS # 750 - -# Pygal style for individual map panels (no per-panel legend) -map_style = Style( - background=PAGE_BG, - plot_background=PAGE_BG, - foreground=INK, - foreground_strong=INK, - foreground_subtle=INK_MUTED, - colors=seq_colors, - title_font_size=66, - label_font_size=44, - major_label_font_size=36, - legend_font_size=36, - value_font_size=28, -) - -# Render 6 world map panels as PNG bytes -map_images = [] -for time_idx in range(6): - binned = binned_by_time[time_idx] - worldmap = World(style=map_style, width=CELL_W, height=CELL_H, title=time_periods[time_idx], show_legend=False) - for label, _, _ in activity_bins: - if binned[label]: - worldmap.add(label, binned[label]) - png_bytes = worldmap.render_to_png() - map_images.append(Image.open(BytesIO(png_bytes))) - -# Compose final 3200×1800 image -combined = Image.new("RGB", (CANVAS_W, CANVAS_H), PAGE_BG) -draw = ImageDraw.Draw(combined) - -# Load fonts (fall back to PIL default if DejaVu not available) -try: - font_title = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 64) - font_subtitle = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 42) - font_legend = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 38) -except OSError: - font_title = ImageFont.load_default() - font_subtitle = ImageFont.load_default() - font_legend = ImageFont.load_default() - -# Draw centered title -title_str = "Seismic Activity · map-animated-temporal · python · pygal · anyplot.ai" -tbbox = draw.textbbox((0, 0), title_str, font=font_title) -text_w = tbbox[2] - tbbox[0] -text_h = tbbox[3] - tbbox[1] -title_x = max(20, (CANVAS_W - text_w) // 2) -title_y = (TITLE_H - text_h) // 2 -draw.text((title_x, title_y), title_str, fill=INK, font=font_title) - -# Draw subtitle (data-storytelling context, muted tone beneath the title) -subtitle_str = "Pacific Ring of Fire — 72-hour seismic spread from epicenter in Japan" -sbbox = draw.textbbox((0, 0), subtitle_str, font=font_subtitle) -sub_w = sbbox[2] - sbbox[0] -sub_h = sbbox[3] - sbbox[1] -sub_x = max(20, (CANVAS_W - sub_w) // 2) -sub_y = TITLE_H + (SUBTITLE_H - sub_h) // 2 -draw.text((sub_x, sub_y), subtitle_str, fill=INK_SOFT, font=font_subtitle) - -# Paste resized map panels into grid -for idx, img in enumerate(map_images): - row = idx // GRID_COLS - col = idx % GRID_COLS - img_resized = img.resize((CELL_W, CELL_H), Image.Resampling.LANCZOS) - x = col * CELL_W - y = TITLE_H + SUBTITLE_H + row * CELL_H - combined.paste(img_resized, (x, y)) - -# Draw single shared legend at bottom (replaces per-panel legends) -box_sz = 32 -gap = 14 -padding = 40 - -total_leg_w = 0 -for lbl, _, _ in activity_bins: - lbbox = draw.textbbox((0, 0), lbl, font=font_legend) - total_leg_w += box_sz + gap + (lbbox[2] - lbbox[0]) + padding - -leg_x = max(20, (CANVAS_W - total_leg_w) // 2) -leg_y_center = TITLE_H + SUBTITLE_H + GRID_H + LEGEND_H // 2 -leg_y = leg_y_center - box_sz // 2 - -for (lbl, _, _), color in zip(activity_bins, seq_colors, strict=True): - draw.rectangle([leg_x, leg_y, leg_x + box_sz, leg_y + box_sz], fill=color, outline=INK_MUTED) - lbbox = draw.textbbox((0, 0), lbl, font=font_legend) - lbl_h = lbbox[3] - lbbox[1] - lbl_w = lbbox[2] - lbbox[0] - draw.text((leg_x + box_sz + gap, leg_y + (box_sz - lbl_h) // 2), lbl, fill=INK_SOFT, font=font_legend) - leg_x += box_sz + gap + lbl_w + padding - -combined.save(f"plot-{THEME}.png") - -# Interactive HTML: all 6 time steps with tab navigation + auto-play + fade transitions -html_style = Style( - background=PAGE_BG, - plot_background=PAGE_BG, - foreground=INK, - foreground_strong=INK, - foreground_subtle=INK_MUTED, - colors=seq_colors, - title_font_size=66, - label_font_size=56, - major_label_font_size=44, - legend_font_size=44, - value_font_size=36, -) - -# Render all 6 time steps as embeddable SVG strings -svg_frames = [] -for time_idx in range(6): - binned = binned_by_time[time_idx] - html_map = World( - style=html_style, - width=1600, - height=900, - title=f"Seismic Activity · {time_periods[time_idx]} · pygal · anyplot.ai", - show_legend=True, - legend_at_bottom=True, - legend_at_bottom_columns=6, - ) - for label, _, _ in activity_bins: - if binned[label]: - html_map.add(label, binned[label]) - svg_frames.append(html_map.render().decode("utf-8")) - -# Build tabbed HTML: frames use CSS opacity transitions; Play/Pause auto-cycles at 1.2s -tab_buttons = "" -svg_divs = "" -for i, (period, svg) in enumerate(zip(time_periods, svg_frames, strict=True)): - active_btn = " active" if i == 0 else "" - if i == 0: - frame_attrs = ' class="frame active" style="opacity:1"' - else: - frame_attrs = ' class="frame" style="opacity:0"' - tab_buttons += f'\n' - svg_divs += f'{svg}\n' - -html_content = f""" - - - -Seismic Activity · map-animated-temporal · pygal · anyplot.ai - - - -

Seismic Activity · map-animated-temporal · python · pygal · anyplot.ai

-

Pacific Ring of Fire — 72-hour seismic spread from epicenter in Japan

-
-{tab_buttons} -
-
- - Step: - - {time_periods[0]} -
-
-{svg_divs} -
- - -""" - -with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f: - f.write(html_content) diff --git a/plots/map-animated-temporal/implementations/python/seaborn.py b/plots/map-animated-temporal/implementations/python/seaborn.py deleted file mode 100644 index 801de24d83..0000000000 --- a/plots/map-animated-temporal/implementations/python/seaborn.py +++ /dev/null @@ -1,198 +0,0 @@ -""" anyplot.ai -map-animated-temporal: Animated Map over Time -Library: seaborn 0.13.2 | Python 3.13.13 -Quality: 88/100 | Updated: 2026-05-27 -""" - -import os - -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd -import seaborn as sns -from matplotlib.colors import LinearSegmentedColormap -from matplotlib.lines import Line2D - - -# 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" - -IMPRINT_PALETTE = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477", "#99B314"] - -# Sequential colormap for magnitude (single-polarity continuous) -imprint_seq = LinearSegmentedColormap.from_list("imprint_seq", ["#009E73", "#4467A3"]) - -# Ocean tint for geographic context -OCEAN_TINT = "#D4E8F0" if THEME == "light" else "#192830" - -sns.set_theme( - style="ticks", - rc={ - "figure.facecolor": PAGE_BG, - "axes.facecolor": PAGE_BG, - "axes.edgecolor": INK_SOFT, - "axes.labelcolor": INK, - "text.color": INK, - "xtick.color": INK_SOFT, - "ytick.color": INK_SOFT, - "grid.color": INK, - "grid.alpha": 0.15, - "legend.facecolor": ELEVATED_BG, - "legend.edgecolor": INK_SOFT, - }, -) - -# Synthetic aftershock sequence — Tohoku region, Japan (off Miyagi coast) -np.random.seed(42) - -epicenter_lat, epicenter_lon = 38.1, 142.8 -n_periods = 6 -mag_min, mag_max = 2.5, 5.8 - -data_records = [] -for period in range(n_periods): - spread = 0.4 + period * 0.25 - n_pts = 12 + period * 4 - lats = epicenter_lat + np.random.randn(n_pts) * spread - lons = epicenter_lon + np.random.randn(n_pts) * spread - upper = max(mag_min + 0.5, mag_max - period * 0.4) - mags = np.random.uniform(mag_min, upper, n_pts) - for i in range(n_pts): - data_records.append( - {"lat": float(lats[i]), "lon": float(lons[i]), "magnitude": float(mags[i]), "period": f"Day {period + 1}"} - ) - -df = pd.DataFrame(data_records) - -# Simplified Tohoku coastline (Sanriku coast, Miyagi–Iwate prefectures) -coast_lons = [141.2, 141.3, 141.5, 141.4, 141.1, 141.0, 141.1, 141.4, 141.6, 141.9] -coast_lats = [40.5, 40.1, 39.5, 39.0, 38.5, 38.0, 37.5, 37.0, 36.8, 36.5] - -# Small multiples grid — 2 rows × 3 cols -fig, axes = plt.subplots(2, 3, figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG) -axes = axes.flatten() - -for idx in range(n_periods): - ax = axes[idx] - ax.set_facecolor(PAGE_BG) - period_label = f"Day {idx + 1}" - period_data = df[df["period"] == period_label].copy() - cumulative_data = df[df["period"].isin([f"Day {i + 1}" for i in range(idx + 1)])].copy() - - # Ocean tint east of coastline - ax.axvspan(141.5, epicenter_lon + 3.5, color=OCEAN_TINT, alpha=0.35, zorder=0) - - # Simplified coastline for geographic context - ax.plot(coast_lons, coast_lats, color=INK_SOFT, linewidth=0.8, alpha=0.65, zorder=2) - - # Cumulative KDE density contours (seaborn-distinctive): show where aftershocks cluster - if idx >= 1: - sns.kdeplot( - data=cumulative_data, - x="lon", - y="lat", - ax=ax, - color=INK_SOFT, - alpha=0.30, - fill=False, - levels=3, - linewidths=0.7, - zorder=1, - ) - - # Ghost trail: previous periods' scatter points (temporal history) - if idx > 0: - trail_data = df[df["period"].isin([f"Day {i + 1}" for i in range(idx)])].copy() - ax.scatter(trail_data["lon"], trail_data["lat"], s=3, c=INK_SOFT, alpha=0.15, zorder=2, linewidths=0) - - # Aftershock scatter with dual encoding: hue + size for magnitude - sns.scatterplot( - data=period_data, - x="lon", - y="lat", - size="magnitude", - sizes=(10, 110), - hue="magnitude", - palette=imprint_seq, - hue_norm=(mag_min, mag_max), - alpha=0.80, - edgecolor=PAGE_BG, - linewidth=0.3, - ax=ax, - legend=False, - ) - - # Epicenter marker (matte red — danger semantic anchor) - ax.scatter( - epicenter_lon, epicenter_lat, marker="*", s=90, c=IMPRINT_PALETTE[4], edgecolors=INK, linewidths=0.5, zorder=10 - ) - - ax.set_xlim(epicenter_lon - 3.5, epicenter_lon + 3.5) - ax.set_ylim(epicenter_lat - 3.5, epicenter_lat + 3.5) - ax.set_title(period_label, fontsize=9, fontweight="bold", color=INK) - ax.set_xlabel("Lon (°E)" if idx >= 3 else "", fontsize=8, color=INK) - ax.set_ylabel("Lat (°N)" if idx % 3 == 0 else "", fontsize=8, color=INK) - ax.tick_params(axis="both", labelsize=6.5, colors=INK_SOFT) - ax.grid(True, alpha=0.12, linewidth=0.5, color=INK) - ax.spines["top"].set_visible(False) - ax.spines["right"].set_visible(False) - - # Point count annotation per panel - ax.text( - 0.97, - 0.04, - f"n={len(period_data)}", - transform=ax.transAxes, - ha="right", - va="bottom", - fontsize=6.5, - color=INK_SOFT, - style="italic", - ) - -# Overall title -title = "map-animated-temporal · python · seaborn · anyplot.ai" -fig.suptitle(title, fontsize=12, fontweight="medium", color=INK, y=0.98) - -# Shared colorbar for magnitude scale -norm = plt.Normalize(vmin=mag_min, vmax=mag_max) -sm = plt.cm.ScalarMappable(cmap=imprint_seq, norm=norm) -sm.set_array([]) -cbar_ax = fig.add_axes([0.93, 0.20, 0.013, 0.58]) -cbar = fig.colorbar(sm, cax=cbar_ax) -cbar.set_label("Magnitude", fontsize=7.5, color=INK) -cbar.ax.tick_params(labelsize=6.5, colors=INK_SOFT) -cbar.outline.set_edgecolor(INK_SOFT) - -# Legend for map context markers -epicenter_handle = Line2D( - [0], - [0], - marker="*", - color="w", - markerfacecolor=IMPRINT_PALETTE[4], - markeredgecolor=INK, - markersize=7, - linestyle="None", - label="Epicenter", -) -coast_handle = Line2D([0], [0], color=INK_SOFT, linewidth=1.2, label="Coastline") - -fig.legend( - handles=[epicenter_handle, coast_handle], - loc="lower center", - bbox_to_anchor=(0.46, 0.02), - fontsize=7, - framealpha=0.92, - facecolor=ELEVATED_BG, - edgecolor=INK_SOFT, - ncol=2, - handlelength=1.2, -) - -fig.subplots_adjust(left=0.08, right=0.91, top=0.88, bottom=0.14, wspace=0.38, hspace=0.50) -plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG) diff --git a/plots/map-animated-temporal/implementations/r/ggplot2.R b/plots/map-animated-temporal/implementations/r/ggplot2.R deleted file mode 100644 index 8a281583f3..0000000000 --- a/plots/map-animated-temporal/implementations/r/ggplot2.R +++ /dev/null @@ -1,115 +0,0 @@ -#' anyplot.ai -#' map-animated-temporal: Animated Map over Time -#' Library: ggplot2 3.5.1 | R 4.4.1 -#' Quality: 90/100 | Created: 2026-05-27 - -library(ggplot2) -library(ragg) - -set.seed(42) - -# --- Theme tokens ----------------------------------------------------------- -THEME <- Sys.getenv("ANYPLOT_THEME", "light") -PAGE_BG <- if (THEME == "light") "#FAF8F1" else "#1A1A17" -ELEVATED_BG <- if (THEME == "light") "#FFFDF6" else "#242420" -INK <- if (THEME == "light") "#1A1A17" else "#F0EFE8" -INK_SOFT <- if (THEME == "light") "#4A4A44" else "#B8B7B0" - -# --- Data ------------------------------------------------------------------- -# Earthquake aftershock sequence - Tohoku region, Japan -# Temporal decay follows Omori law: N(t) ~ 1 / (t + c)^p -n_days <- 9 -lat_epi <- 38.3 -lon_epi <- 142.4 -events_per_day <- pmax(round(55 / (1:n_days + 0.3)^0.8), 4) - -quakes <- do.call(rbind, lapply(seq_len(n_days), function(d) { - n <- events_per_day[d] - sp <- 0.45 + 0.07 * d # spatial spread grows as fault zone relaxes - data.frame( - day = d, - lat = lat_epi + rnorm(n, 0, sp), - lon = lon_epi + rnorm(n, 0, sp * 1.2), - magnitude = pmin(2.5 + rexp(n, rate = 1.4), 7.5) - ) -})) -quakes$day_label <- factor( - paste0("Day ", quakes$day), - levels = paste0("Day ", seq_len(n_days)) -) - -# Epicenter reference point -epicenter <- data.frame(lat = lat_epi, lon = lon_epi) - -# Event counts per panel for strip labels -day_counts <- as.integer(table(quakes$day)) -day_labels_full <- paste0("Day ", seq_len(n_days), " (n=", day_counts, ")") -levels(quakes$day_label) <- day_labels_full - -# --- Plot ------------------------------------------------------------------- -title_text <- "map-animated-temporal · r · ggplot2 · anyplot.ai" -subtitle_text <- "Tohoku aftershock sequence: aftershock frequency decays via Omori law as spatial spread grows" - -p <- ggplot(quakes, aes(x = lon, y = lat)) + - geom_point( - aes(size = magnitude, color = magnitude), - alpha = 0.75, stroke = 0 - ) + - geom_point( - data = epicenter, - aes(x = lon, y = lat), - shape = 3, size = 2.5, color = INK, stroke = 0.8 - ) + - scale_color_gradient( - low = "#009E73", high = "#4467A3", - name = "Mag", limits = c(2.5, 7.5) - ) + - scale_size_continuous(range = c(0.8, 4.5), guide = "none") + - scale_x_continuous( - limits = c(139.5, 145.5), - breaks = c(140, 142, 144), - labels = c("140°E", "142°E", "144°E") - ) + - scale_y_continuous( - limits = c(35.5, 41.5), - breaks = c(36, 38, 40), - labels = c("36°N", "38°N", "40°N") - ) + - facet_wrap(~ day_label, nrow = 3) + - labs( - title = title_text, - subtitle = subtitle_text, - x = "Longitude", y = "Latitude" - ) + - theme_minimal(base_size = 7) + - theme( - plot.background = element_rect(fill = PAGE_BG, color = PAGE_BG), - panel.background = element_rect(fill = PAGE_BG, color = NA), - panel.grid.major = element_line(color = INK_SOFT, linewidth = 0.15), - panel.grid.minor = element_blank(), - panel.border = element_rect(color = INK_SOFT, fill = NA, linewidth = 0.25), - axis.title = element_text(color = INK, size = 9), - axis.text = element_text(color = INK_SOFT, size = 8), - axis.text.x = element_text(angle = 45, hjust = 1), - plot.title = element_text(color = INK, size = 12, hjust = 0.5, - margin = margin(b = 4)), - plot.subtitle = element_text(color = INK_SOFT, size = 8, hjust = 0.5, - margin = margin(b = 6)), - legend.background = element_rect(fill = ELEVATED_BG, color = INK_SOFT, - linewidth = 0.25), - legend.text = element_text(color = INK_SOFT, size = 8), - legend.title = element_text(color = INK, size = 9), - legend.key.height = unit(0.5, "cm"), - strip.background = element_rect(fill = ELEVATED_BG, color = NA), - strip.text = element_text(color = INK, size = 8, face = "bold"), - plot.margin = margin(8, 8, 8, 8) - ) - -# --- Save ------------------------------------------------------------------- -ggsave( - filename = sprintf("plot-%s.png", THEME), - plot = p, - device = ragg::agg_png, - width = 8, height = 4.5, - units = "in", dpi = 400 -) diff --git a/plots/map-animated-temporal/metadata/julia/makie.yaml b/plots/map-animated-temporal/metadata/julia/makie.yaml deleted file mode 100644 index be2c256c04..0000000000 --- a/plots/map-animated-temporal/metadata/julia/makie.yaml +++ /dev/null @@ -1,259 +0,0 @@ -library: makie -language: julia -specification_id: map-animated-temporal -created: '2026-05-27T09:50:34Z' -updated: '2026-05-27T10:09:10Z' -generated_by: claude-sonnet -workflow_run: 26502994944 -issue: 3769 -language_version: 1.11.9 -library_version: 0.22.10 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/julia/makie/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/julia/makie/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 86 -review: - strengths: - - Perfect code quality and data quality — both score maximum points - - Dual encoding (color=event age, size=magnitude) adds meaningful information density - to each panel - - Cumulative rendering approach correctly shows aftershock growth dynamics across - the temporal sequence - - Fully theme-adaptive chrome with all tokens correctly applied to both renders - — dark render passes all legibility checks with no dark-on-dark failures - - Shared colorbar with descriptive label cleanly replaces per-panel legends - - Geophysically realistic synthetic data (Omori-law-like decay, realistic magnitudes - and spatial spread) - - Edge-only tick/label visibility idiomatically avoids clutter in a 12-panel grid - weaknesses: - - 'Missing basemap: the spec explicitly requires a basemap with geographic context - (country boundaries, coastlines, or terrain). The implementation shows only bare - lat/lon coordinate axes. Use GeoMakie''s GeoAxis for geographic projection and - coastlines, or add a simplified Japan/region outline using polygon data.' - - 'Minor colorbar/scatter data range mismatch: scatter color uses normalized 0-1 - fractions (time_frac) while the colorbar limits are set to (1, 12). Fix by passing - days_cum directly as the scatter color values (1-12 range) so colorbar tick labels - precisely match the data colors.' - - 'Small text in dense grid: axis label sizes of 8pt (effective 16pt) and panel - titles of 11pt (effective 22pt) are borderline small for the 12-panel layout, - especially when scaled to mobile width.' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct - Chrome: Bold centered title "map-animated-temporal · julia · makie · anyplot.ai" in dark ink clearly visible. Subplot titles "Day N" in small dark text readable. Axis labels "Longitude" and "Latitude" on bottom row / left column respectively. Colorbar label and tick labels in dark ink readable. - Data: Green-to-blue sequential gradient (ANYPLOT_SEQ: #009E73 -> #4467A3) encoding event age. Point size encodes magnitude (2-11pt). Red star (#AE3030) marks mainshock epicenter in each panel. 12 cumulative panels showing aftershock growth. - Legibility verdict: PASS — all text clearly readable against warm off-white background - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct - Chrome: Title and all labels flip to light text (#F0EFE8 / #B8B7B0). Grid lines faintly visible in light tone. All tick labels, axis labels, subplot titles, and colorbar labels are clearly readable in light text on dark background. No dark-on-dark failures observed. - Data: Data colors are identical to light render — same green-to-blue gradient with same magnitude sizing. Red star epicenter with light stroke remains visible. No color drift between themes. - Legibility verdict: PASS — all text clearly readable against warm near-black background; theme adaptation complete - criteria_checklist: - visual_quality: - score: 28 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: All font sizes explicitly set; proportional and readable in both - themes; axis-level text at 8pt (16pt effective) borderline small for dense - 12-panel grid but legible - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No text overlaps; edge-only tick/label visibility prevents crowding - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: Points visible with alpha=0.7 and magnitude-scaled sizes; dense later - panels adequately handled but markers could be slightly larger in sparse - early panels - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: CVD-safe sequential colormap; redundant encoding via shape and size - for mainshock - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Canvas gate passed 3200x1800; balanced layout with title, grid, colorbar; - proper gaps - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title correct format; Longitude/Latitude self-describing; colorbar - has descriptive label - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'ANYPLOT_SEQ correctly used as imprint_seq; #AE3030 for mainshock - is semantic red; backgrounds #FAF8F1/#1A1A17; full theme-adaptive chrome' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Above configured-default level; dual encoding with custom sequential - colormap and size; star marker as focal anchor; not publication-ready due - to missing geographic context - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Top/right spines removed; 10% opacity grid; edge-only tick labels; - shared colorbar; explicit panel gaps - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Cumulative approach creates compelling temporal narrative; dual encoding - adds depth; persistent mainshock star provides reference anchor - spec_compliance: - score: 13 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Small multiples is correct fallback for static CairoMakie; 12-panel - grid well-implemented - - id: SC-02 - name: Required Features - score: 2 - max: 4 - passed: false - comment: Day labels satisfy timestamp display; cumulative temporal data present; - but spec-required basemap with geographic context (coastlines/country boundaries) - is completely absent - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X=Longitude, Y=Latitude correctly assigned; color=time, size=magnitude; - all data visible - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title exactly matches required format; colorbar serves as descriptive - legend - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Shows spatial distribution, temporal accumulation, magnitude variation, - temporal progression within panels, mainshock vs aftershocks - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Earthquake aftershock sequence in central Japan; M7.2 mainshock realistic; - non-controversial science domain - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Magnitudes 2.5-5.7 realistic; Omori-law-like decay; plausible geographic - spread - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear flow: imports → constants → data → figure → save; no functions - or classes' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Random.seed!(42) at top - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: CairoMakie, Colors, Random — all used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean idiomatic Julia; broadcasting used appropriately; no fake functionality - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-$(THEME).png with px_per_unit=2; current CairoMakie - API - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Good use of Makie layout grammar; fig[row, col] positioning; limits!; - rowsize!/colgap!/rowgap!; edge-only tick visibility idiomatic - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Makie composable layout (colon spanning), Fixed row sizes, shared - colorbar spanning columns, cgrad() for custom colormap, RGBAf for alpha-composited - grid colors — distinctively Makie - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - colorbar - - faceting - patterns: - - data-generation - - iteration-over-groups - dataprep: [] - styling: - - custom-colormap - - alpha-blending diff --git a/plots/map-animated-temporal/metadata/python/altair.yaml b/plots/map-animated-temporal/metadata/python/altair.yaml deleted file mode 100644 index 5ae37dd475..0000000000 --- a/plots/map-animated-temporal/metadata/python/altair.yaml +++ /dev/null @@ -1,271 +0,0 @@ -library: altair -language: python -specification_id: map-animated-temporal -created: '2026-05-27T09:35:04Z' -updated: '2026-05-27T10:00:12Z' -generated_by: claude-sonnet -workflow_run: 26502411073 -issue: 3769 -language_version: 3.13.13 -library_version: 6.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/altair/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/altair/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/altair/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/altair/plot-dark.html -quality_score: 85 -review: - strengths: - - Thoughtful theme-adaptive map styling (LAND_FILL and OCEAN_FILL tokens adapt correctly - to both themes) - - 'Correct continuous colormap usage: imprint_seq equivalent (green→blue range) - for magnitude — dual encoding with size reinforces CVD safety' - - 'Excellent Altair idiom: alt.topo_feature, transform_filter, transform_calculate, - geographic projection, multi-binding parameters all used correctly' - - 'Realistic and scientifically grounded data: Omori Law decay aftershock sequence - centered on Tohoku — neutral, plausible, and pedagogically strong' - - Creative timer signal injection via JSON spec manipulation to enable true animation - in HTML output - - 'Perfect spec compliance: play/pause checkbox, day slider, Day N/30 overlay, basemap, - cumulative filter all implemented' - - KISS code structure with no functions or classes; clean flat script with all imports - used - weaknesses: - - Day 16/30 timestamp overlay is fontsize=13, which is somewhat small at 3200px - canvas resolution — spec requires the timestamp to be clearly displayed; consider - bumping to fontsize=15-16 for better prominence - - 'Design Excellence is moderate: the map lacks visual storytelling emphasis — consider - adding a subtle directional annotation or color-coded magnitude thresholds (e.g., - M5+ in a distinct color) to create a focal point' - - The geographic zoom level (scale=1400) is tight on the study area but the earthquake - cluster itself occupies only ~25% of the visual canvas — the projection could - be widened slightly to show more regional context or the scale tuned to make the - data cluster more prominent - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct anyplot light surface, no pure white. - Chrome: Title "Earthquake Aftershock Sequence · map-animated-temporal · python · altair · anyplot.ai" rendered in dark INK (#1A1A17), clearly legible. Subtitle "Use slider or Play / Pause to animate cumulative aftershocks" in smaller INK_SOFT. Day 16/30 overlay text in lower-left of map area, readable but slightly small at fontsize=13. Magnitude legend (bottom-right) shows size-graded circles labeled 2-7 with fillColor=ELEVATED_BG frame — all readable. - Data: Earthquake circles in green-to-blue imprint_seq gradient (range=["#009E73", "#4467A3"]), size-encoded by magnitude (range [30, 480]), opacity=0.72. Dense cluster offshore Tohoku, with cumulative buildup through Day 16 of 30 well represented. Land in warm tan (#CCC4B0), ocean in cream (#E0D8C8) — clean contrast for circle visibility. - Legibility verdict: PASS — all text visible and readable against warm off-white background. - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct anyplot dark surface. - Chrome: Title and subtitle rendered in light INK (#F0EFE8 equivalent) — clearly visible against dark background. Day 16/30 overlay text still visible in light color. Legend frame adapted to ELEVATED_BG (#242420) with light label text. No dark-on-dark failures detected. - Data: Earthquake circle colors are identical to the light render (same imprint_seq green→blue gradient, same opacity). Land adapts to dark gray (#2D2D29), ocean to near-black (#1E1E1C) — correct theme flip. Brand green #009E73 clearly visible on dark background. - Legibility verdict: PASS — theme chrome correctly flips; only data colors (positions 1–8) stay constant as required. - criteria_checklist: - visual_quality: - score: 27 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: All font sizes explicitly set; title fontsize correctly scaled for - 85-char string. Day 16/30 overlay at fontsize=13 is slightly small for 3200px - canvas but readable in both themes. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No text overlap; Day overlay is in clear geographic area, legend - is well-separated from data cluster. - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: Circles well-sized (range 30-480 by magnitude), opacity=0.72 appropriate - for cumulative ~200-point density. Smallest magnitude-2 circles may be partially - obscured under larger ones in the dense cluster. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Dual encoding (size + color) for magnitude is excellent CVD accommodation; - imprint_seq (green→blue) is perceptually uniform and CVD-safe. - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: Ocean fill covers the full padded 3200x1800 canvas. Map area is well-utilized, - though the earthquake cluster itself occupies roughly 25% of visual area - due to the Mercator zoom level. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: No traditional axis labels needed for a map. Descriptive title with - spec-id, subtitle explains interactivity. Magnitude legend is labeled. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Continuous data uses imprint_seq (green→blue, range=["#009E73", - "#4467A3"]) — correct. Backgrounds #FAF8F1 / #1A1A17 correct. Chrome fully - theme-adaptive in both renders.' - design_excellence: - score: 11 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Theme-adaptive map colors (LAND_FILL/OCEAN_FILL) create a polished - warm/cool duality. Dual magnitude encoding is thoughtful. Above the default-4 - baseline but not publication-ready — could add more focal point emphasis. - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: true - comment: No traditional spines (appropriate for map), strokeWidth=0 on view - border is clean, legend styling is subtle. Configure_legend with elevated - background and soft colors is nicely refined. - - id: DE-03 - name: Data Storytelling - score: 3 - max: 6 - passed: true - comment: Cumulative filter approach (show all quakes up to selected day) is - a strong narrative choice — viewers see the aftershock sequence accumulate. - Omori Law decay makes the temporal story scientifically authentic. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: 'Correct: animated geographic map with point data driven by time - parameter.' - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Play/pause checkbox, day range slider, Day N/30 timestamp overlay, - basemap with country borders, cumulative animation, 800ms default speed - — all present. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: lon→longitude, lat→latitude, magnitude→size+color, day→filter parameter - — all correct. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title format: ''Earthquake Aftershock Sequence · map-animated-temporal - · python · altair · anyplot.ai'' — correct {Descriptive} · {spec-id} · {lang} - · {lib} · anyplot.ai pattern. Magnitude legend present and labeled.' - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'Shows all aspects: lat/lon point distribution, temporal animation, - magnitude color+size encoding, geographic clustering, temporal decay pattern.' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Offshore Tohoku aftershock sequence — scientifically grounded, neutral, - comprehensible. Omori Law decay for aftershock rates is accurate. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: 38.3°N 142.4°E is accurate for 2011 Tohoku offshore. Magnitude 2.0-6.8 - range is realistic for aftershock sequences. 30-day window with ~200 total - events matches real aftershock statistics. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'No functions or classes. Flat: imports → data generation → chart - layers → compose → save PNG → pad → save HTML.' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set at top. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All 7 imports (json, os, sys, altair, numpy, pandas, PIL) are actually - used. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Well-organized, Pythonic, no fake UI. JSON manipulation for timer - signal injection is a documented creative workaround, not a fake UI element. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html. Uses Altair 6.x API. - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: 'Excellent Altair idioms: alt.topo_feature, transform_filter, transform_calculate, - .project(), alt.layer, alt.TitleParams, binding_range + binding_checkbox. - Above the default-3 baseline.' - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Vega-Lite geographic projection, topojson data integration, reactive - transform_filter for animation, timer signal injection via JSON spec manipulation - — distinctly Altair/Vega-Lite capabilities. - verdict: APPROVED -impl_tags: - dependencies: - - pillow - techniques: - - hover-tooltips - - html-export - - layer-composition - patterns: - - data-generation - dataprep: [] - styling: - - alpha-blending - - custom-colormap diff --git a/plots/map-animated-temporal/metadata/python/bokeh.yaml b/plots/map-animated-temporal/metadata/python/bokeh.yaml deleted file mode 100644 index a6b20a5a43..0000000000 --- a/plots/map-animated-temporal/metadata/python/bokeh.yaml +++ /dev/null @@ -1,257 +0,0 @@ -library: bokeh -language: python -specification_id: map-animated-temporal -created: '2026-01-20T19:45:19Z' -updated: '2026-05-27T09:38:23Z' -generated_by: claude-sonnet -workflow_run: 26502313087 -issue: 3769 -language_version: 3.13.13 -library_version: 3.9.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/bokeh/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/bokeh/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/bokeh/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/bokeh/plot-dark.html -quality_score: 92 -review: - strengths: - - Correct Imprint palette with semantic matte-red epicenter marker (#AE3030) — semantically - appropriate for hazard/focal point - - 'Full spec compliance: play/pause button, time slider, Day-label overlay, tooltip, - and cumulative alpha-faded trail all present and working' - - Bokeh-idiomatic CustomJS callbacks enable true client-side animation without a - Python server — all frames pre-embedded in ColumnDataSource - - Earthquake aftershock scenario is realistic, neutral, and geo-accurate (lat 35, - lon 140 is a plausible Japan epicenter) - - Proper theme-adaptive chrome with ELEVATED_BG callout boxes for legend and day - label; both renders pass legibility check - - Title fontsize scaled with length formula — no overflow - - Code is clean KISS structure with seed set, all imports used - weaknesses: - - Static Day-0 PNG shows only 25 sparse aftershock points; at size=magnitude×4 (~20 - px for M5) markers are small on the 3200 px canvas — for sparse data (<30 points), - markers should be ~40–50 px minimum to be prominently visible at scale - - Coastline is a 15-segment manual approximation rather than actual geographic line - data; leaves most of the 130–150 lon, 28–42 lat region featureless, limiting geographic - context - - Aftershocks are clustered in a small area around lon 140 while the map extends - to 130–150 (20° range), creating a large empty ocean area to the left — a tighter - geographic extent (e.g. 136–146) would make the data more prominent - image_description: |- - Light render (plot-light.png): - Background: Warm off-white, confirmed #FAF8F1 — correct anyplot light surface. - Chrome: Title "Earthquake Aftershocks · map-animated-temporal · python · bokeh · anyplot.ai" in dark INK, occupies ~78% of plot width (expected for this long title with optional prefix). Axis labels "Longitude (°E)" and "Latitude (°N)" in dark INK, clearly readable. Tick labels (28–42 lat, 130–150 lon) in INK_SOFT, readable. "Day 0" callout box with elevated background (#FFFDF6) visible in upper-left. - Data: 25 aftershock scatter points in brand green (#009E73) clustered around lon 140, lat 35. Large red star (matte red #AE3030) marks the epicenter at (140, 35), clearly prominent. Gray coastline approximation runs diagonally from lower-left to upper-right. Legend in bottom-right shows "Aftershocks" (green dot) and "Epicenter (M7.2)" (red star) with elevated background. - Legibility verdict: PASS — all text readable, no light-on-light issues. - - Dark render (plot-dark.png): - Background: Warm near-black, confirmed #1A1A17 — correct anyplot dark surface. - Chrome: Title in light INK (#F0EFE8), clearly readable against near-black. Axis labels in light INK, tick labels in INK_SOFT (#B8B7B0) — both readable, no dark-on-dark failure. "Day 0" callout box with elevated dark background (#242420). Grid lines subtle (alpha 0.15) against dark surface. - Data: Aftershock points are identical brand green (#009E73) — data colors unchanged from light render. Epicenter star in matte red with white outline (line_color=INK → #F0EFE8 on dark) — clearly visible. Legend with dark elevated background (#242420) and light text. - Legibility verdict: PASS — all text readable against dark background; no dark-on-dark failures detected. - criteria_checklist: - visual_quality: - score: 29 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All font sizes explicitly set (title scaled via formula, 42pt axis - labels, 34pt ticks/legend). Both renders fully legible, no overflow. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: 'No overlap: Day-0 label is in empty upper-left, legend in empty - lower-right, data clustered around epicenter.' - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: Markers visible but on the small side for 25-point sparse data (size=magnitude×4 - gives ~20 px for M5); could be larger (40–50 px) for sparse scatter. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Green vs red with distinct circle/star shapes — CVD-safe with redundant - shape encoding. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Canvas gate passed. Geographic extent is intentional; min_border - reservations ensure labels are not clipped. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Longitude (°E) and Latitude (°N) — both have units. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73, epicenter uses semantic matte-red #AE3030, - backgrounds correct in both themes.' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Above well-configured default: semantic epicenter color, alpha-fading - trail, magnitude-to-size encoding, elevated callout boxes. Not FiveThirtyEight-level - polish.' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Subtle grid (alpha=0.15), styled legend/label callouts, INK_SOFT - spines. All four spines present (appropriate for a geographic map). - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Clear focal point (epicenter star), alpha fading shows recency, magnitude-size - encoding adds depth. Visual hierarchy guides viewer to epicenter. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Animated geographic map with point data, play controls, and time - slider — full spec type. - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Play/pause button, time slider, Day-label overlay, hover tooltip, - basemap coastline, configurable speed, cumulative point trail all present. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X=longitude, Y=latitude, color=category (aftershock/epicenter), size=magnitude. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title follows {Descriptive Title} · {spec-id} · python · bokeh · - anyplot.ai format. Legend labels correct. - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Shows temporal spread, magnitude variation, geographic distribution, - and recency through alpha. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Earthquake aftershock sequence in Japan region — real-world, neutral, - comprehensible. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Epicenter at lat 35, lon 140 (Pacific coast near central Honshu) - is a plausible seismic location. M7.2 main shock and M2–5 aftershocks declining - over 20 days are factually consistent. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Imports → Data → Plot → Save, no functions or classes. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports used. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean, appropriate complexity. CustomJS callbacks are the correct - Bokeh idiom, not over-engineered. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.html + plot-{THEME}.png. Current Bokeh API used. - library_mastery: - score: 10 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: ColumnDataSource, column/row layouts, HoverTool, output_file/save - — all idiomatic Bokeh patterns. - - id: LM-02 - name: Distinctive Features - score: 5 - max: 5 - passed: true - comment: CustomJS callbacks with setInterval animation and ColumnDataSource - dynamic updates are distinctively Bokeh — client-side animation without - a Python server. - verdict: APPROVED -impl_tags: - dependencies: - - selenium - techniques: - - hover-tooltips - - html-export - - annotations - patterns: - - data-generation - - columndatasource - - iteration-over-groups - dataprep: [] - styling: - - alpha-blending - - edge-highlighting diff --git a/plots/map-animated-temporal/metadata/python/letsplot.yaml b/plots/map-animated-temporal/metadata/python/letsplot.yaml deleted file mode 100644 index e05ad47d88..0000000000 --- a/plots/map-animated-temporal/metadata/python/letsplot.yaml +++ /dev/null @@ -1,263 +0,0 @@ -library: letsplot -language: python -specification_id: map-animated-temporal -created: '2026-01-20T19:45:13Z' -updated: '2026-05-27T10:15:35Z' -generated_by: claude-sonnet -workflow_run: 26502800915 -issue: 3769 -language_version: 3.13.13 -library_version: 4.10.1 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/letsplot/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/letsplot/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/letsplot/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/letsplot/plot-dark.html -quality_score: 84 -review: - strengths: - - Custom California polygon basemap provides clean geographic context without external - dependencies - - 'Correct imprint_seq gradient (green #009E73 → blue #4467A3) for continuous magnitude - data' - - 'Dual output: PNG as small-multiples facet grid + HTML with geom_livemap tile - basemap (CARTO_POSITRON/MIDNIGHT_COMMANDER) — distinctively letsplot' - - Cumulative data approach across 4 panels naturally shows the spreading aftershock - sequence - - Merged color+size legend with shared breaks (mag_breaks=[2,3,4,5,6]) avoids redundant - legend entries - - 'Full theme-adaptive chrome: all text, backgrounds, and borders adapt correctly - to both light (#FAF8F1) and dark (#1A1A17) themes' - - Dynamic title fontsize scaling for the long mandated title string - - 'Realistic seismic dataset: Central California epicenter, exponential spread, - plausible magnitude decay' - weaknesses: - - '''Epicenter'' text label overlaps data points in dense Week 4 and Week 6 panels - — increase nudge_y from 0.22 to ~0.35 degrees lat, or add a semi-transparent background - to the label' - - Small multiples show only 4 snapshots (Week 1/2/4/6) — the HTML geom_livemap version - still uses facet_wrap rather than any interactive time control; a tooltip showing - timestamp/magnitude on hover would improve the HTML experience - - 'DE-01: Design is above defaults but not publication-ready — the polygon basemap - could be styled with a more subtle fill, and panel spacing could be more generous' - - 'DE-03: The story of spreading aftershocks is clear from the cumulative data, - but no annotation highlights the maximum spread or key transition between panels' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct anyplot light surface - Chrome: Title "Seismic Activity Spread · map-animated-temporal · python · letsplot · anyplot.ai" in dark bold text, well-centered at ~80% width (expected for long mandated title). Strip labels "Week 1/2/4/6" in bold dark text. Axis labels "Longitude" / "Latitude" in dark/muted colors. Caption "Cumulative aftershock sequence..." in small muted text at bottom. Legend "Magnitude (M)" on right with combined color+size entries. - Data: 2×2 facet grid showing Central California region with polygon basemap (grey fill, subtle border). Points sized 2–9 and colored via imprint_seq gradient (green #009E73 for low magnitude → blue #4467A3 for high). Epicenter marked with × symbol and "Epicenter" label. Alpha=0.75 handles overlapping in dense panels. Week 4/6 show clear cluster expansion from epicenter. - Legibility verdict: PASS — all text readable against light background. Minor concern: "Epicenter" label overlaps scattered data points in dense panels but remains legible. - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct anyplot dark surface - Chrome: Title in light (#F0EFE8) text clearly visible. Strip labels in light text on slightly elevated dark panel headers. Axis labels, tick labels, and caption in appropriate light-on-dark colors. Legend box uses elevated dark (#242420) background with light text. Map polygon basemap uses darker fill and border adapting to dark theme. - Data: Colors are identical to light render — same teal-to-blue imprint_seq gradient, confirming only chrome flips between themes. The X epicenter marker is white/light against dark background. Points visible with alpha=0.75. - Legibility verdict: PASS — no dark-on-dark failures. All text elements use light tokens against near-black background. No "dark text on dark background" issues detected. - criteria_checklist: - visual_quality: - score: 25 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: All font sizes explicitly set including dynamic title scaling; readable - in both themes; minor crowding of Epicenter label in dense Week 4/6 panels - - id: VQ-02 - name: No Overlap - score: 4 - max: 6 - passed: true - comment: Epicenter label overlaps data points in Week 4 and Week 6 accumulated - panels; label still legible but nudge_y=0.22 is insufficient for dense clusters - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: Good density adaptation with alpha=0.75 and size range 2-9; some - overplotting in cumulative panels expected and handled - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: imprint_seq gradient (green-blue) is CVD-safe; magnitude encoded - redundantly via color+size - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: 2x2 facet grid fills canvas well; legend properly placed right; ggsize(800,450) - scale=4 = 3200x1800 correct - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Longitude / Latitude labels; caption explains epicenter symbol; facet - labels show temporal steps - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'imprint_seq gradient (low=#009E73 high=#4467A3) correct for continuous - magnitude; backgrounds #FAF8F1/#1A1A17 correct; all chrome theme-adaptive' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Above defaults: custom polygon basemap, dual HTML/PNG output, proper - gradient, merged legend. Not yet publication-ready.' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: theme_void() appropriate for maps; custom panel borders 0.3pt; legend - with elevated background; caption in muted color - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Cumulative data progression shows spreading pattern clearly; redundant - magnitude encoding aids comprehension; consistent epicenter marker as focal - point - spec_compliance: - score: 13 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 4 - max: 5 - passed: true - comment: Correct geographic point-over-time visualization; small multiples - is sanctioned fallback per spec note for libraries without animation - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Basemap present, timestamp display via facet labels, cumulative trails - via data design; play/pause and time slider not implemented (fallback approach) - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: lat/lon correctly mapped to x/y; magnitude to color+size; week to - facets - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title 'Seismic Activity Spread · map-animated-temporal · python · - letsplot · anyplot.ai' correct; legend 'Magnitude (M)' with breaks matches - data - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'Shows all aspects: geographic spread, temporal progression, magnitude - variation, epicenter reference point, cumulative accumulation' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Realistic Central California earthquake scenario; plausible coordinates - (-119.5, 36.0); realistic aftershock decay pattern - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Magnitude 2-6 realistic for aftershocks; coordinate range within - California; event counts per week plausible - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Clean linear structure: imports → tokens → data → plot_png → plot_html - → save' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All 22 lets_plot imports are used across both PNG and HTML plots - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean dual-output pattern; justified theme differences between PNG - (theme_void) and HTML (geom_livemap) - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html with current letsplot - API - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Idiomatic ggplot grammar; guides() for merged legend; facet_wrap; - theme_void for map; proper scale alignment - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: geom_livemap with tilesets.CARTO_POSITRON/MIDNIGHT_COMMANDER is uniquely - letsplot — no other Python ggplot-style library has built-in tile-based - interactive maps - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - faceting - - layer-composition - - html-export - patterns: - - data-generation - dataprep: [] - styling: - - alpha-blending - - custom-colormap - - minimal-chrome diff --git a/plots/map-animated-temporal/metadata/python/matplotlib.yaml b/plots/map-animated-temporal/metadata/python/matplotlib.yaml deleted file mode 100644 index d01f45e8fd..0000000000 --- a/plots/map-animated-temporal/metadata/python/matplotlib.yaml +++ /dev/null @@ -1,289 +0,0 @@ -library: matplotlib -language: python -specification_id: map-animated-temporal -created: '2026-01-20T19:48:01Z' -updated: '2026-05-27T09:39:24Z' -generated_by: claude-sonnet -workflow_run: 26502015015 -issue: 3769 -language_version: 3.13.13 -library_version: 3.10.9 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/matplotlib/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/matplotlib/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 86 -review: - strengths: - - Custom ocean/land theme-adaptive colors that adapt correctly across both light - and dark renders - - Dual-encoding (diverging colormap + variable marker size) effectively communicates - both anomaly direction and magnitude, providing redundant CVD-safe encoding - - ScalarMappable with shared Normalize for cross-panel colorbar is idiomatic and - sophisticated matplotlib usage - - Correctly uses imprint_div with theme-adaptive midpoint matching the page background, - with documented climate-convention pole reversal - - 'Clean KISS structure: linear imports → data → plot → save, with np.random.seed(42) - for full reproducibility' - - Temporal warming narrative is clearly visible across the 6-panel sequence with - peak annotation in the final frame drawing viewer attention - weaknesses: - - Annotation text at fontsize=5.5pt (~31px at 400dpi) is very small and likely unreadable - when the image is scaled to mobile width (~400px) - increase annotation fontsize - to at least 6.5-7pt - - Minimum scatter marker size of s=60 is below the recommended 200-400 for sparse - data (<30 points per panel) - near-zero anomaly stations have small, less prominent - markers; consider a minimum of s=80-100 and wider size range - - DE scores slightly limited by standard small-multiples approach - consider adding - visual emphasis elements like a thin highlight ring on the most anomalous station - per frame to further guide the viewer's eye - - 'LM-02: Polygon-based coastline is a rough approximation with some irregular shapes - that may not clearly convey European geography to unfamiliar viewers - the technique - is distinctive but the result is visually rough' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) page background — correct. - Layout: 2×3 grid of small map panels covering Europe (lon -15 to 35, lat 33 to 72). Ocean areas rendered in light blue (#C8DDF0), land polygons in warm beige (#E8E4D8). A shared vertical colorbar sits on the right edge labeled "Temp. Anomaly (°C)". - Chrome: Title "map-animated-temporal · python · matplotlib · anyplot.ai" at the top in dark ink, clearly readable. "Latitude (°)" and "Longitude (°)" axis labels on the left column and bottom row in dark ink. Tick labels at 6pt — small but readable at full resolution; borderline for mobile. Month labels (bold, semi-transparent box) in each panel's top-left corner (Jan 2025, Mar 2025, May 2025, Jul 2025, Sep 2025, Nov 2025) — readable. Nov panel has a "+3.6°C / [station name]" annotation with an arrow — text at 5.5pt is very small. - Data: Scatter points colored with imprint_div (blue=cold, red=warm) and size-encoded by anomaly magnitude. January shows mostly neutral-to-slightly-warm stations; by November most stations appear distinctly red with larger markers. First series uses #009E73 — N/A (continuous colormap data). Brand green not applicable here; imprint_div used correctly for diverging data. - Legibility verdict: PASS — all text readable at full resolution; minor concern about 5.5pt annotation and 6pt ticks at mobile scale. - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) page background — correct. - Layout: Same 2×3 grid. Ocean areas in dark teal-blue (#1E3A4A), land polygons in dark warm-grey (#2E2E28). Colorbar remains on right side. - Chrome: Title, axis labels, and tick labels all appear in light-colored text (INK token #F0EFE8 for primary, INK_SOFT #B8B7B0 for ticks) — clearly readable against the near-black background. Month labels in semi-transparent elevated boxes (#242420) with INK text — readable. Annotation in Nov panel uses INK color on elevated background — consistent with dark theme. Spine colors use INK_SOFT. Dotted graticule lines visible but subtle. - Data: Scatter colors are identical to the light render — same blue-to-red diverging palette with size encoding. The midpoint (#1A1A17) of imprint_div matches the dark page background, making near-zero anomaly markers blend slightly with the background, but edge colors (INK) provide visible outlines. Warming progression still clearly visible across panels. - No "dark-on-dark" failures observed — all chrome correctly uses light tokens on the dark background. - Legibility verdict: PASS — all text readable; near-zero anomaly markers have slightly reduced fill contrast against dark background (midpoint=page_bg) but edge outlines preserve visibility. - criteria_checklist: - visual_quality: - score: 26 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 6 - max: 8 - passed: true - comment: All font sizes explicitly set; readable at full resolution in both - themes. Multi-panel layout requires small text (6pt ticks, 5.5pt annotation) - that limits mobile readability. - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: No text overlap. Minor expected marker overlap in denser map regions; - month labels in corner boxes clear of data. - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: Dual size+color encoding works well. Minimum marker size s=60 is - below recommended 200+ for <30-point sparse data; edge colors mitigate but - near-zero anomaly stations are smaller than ideal. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Size encoding provides redundant channel alongside color, making - the visualization accessible to CVD viewers. imprint_div uses perceptually-uniform - blue/red poles. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Canvas gate passed. Correct 3200x1800px landscape. 6-panel layout - fills canvas well with shared colorbar on right. subplots_adjust controls - margins precisely. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title follows exact format. Axis labels 'Latitude (°)' and 'Longitude - (°)' include units. Colorbar labeled 'Temp. Anomaly (°C)' with units. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'imprint_div used for diverging temperature anomaly data. Pole reversal - (blue-cold, red-warm) documented as intentional climate science convention. - Midpoint is theme-adaptive (#FAF8F1 light / #1A1A17 dark). Backgrounds correct - in both renders.' - design_excellence: - score: 14 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 6 - max: 8 - passed: true - comment: 'Strong design: custom theme-adaptive ocean/land palette, dual encoding - (color+size), semi-transparent callout boxes, edge-outlined markers, annotated - peak station. Clearly above configured defaults.' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: 'Good refinement: custom spine colors via INK_SOFT, subplots_adjust - for precise margins, theme-adaptive elevated boxes, dotted graticule lines - instead of grid. All spines retained (appropriate for map plots).' - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Warming narrative is clear across the temporal sequence. Size+color - dual encoding creates natural focal points in later panels where anomalies - are large. Peak annotation in Nov draws viewer attention. Could be stronger - with per-panel emphasis on the most anomalous station. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: 'Correct approach: small-multiples grid per matplotlib library rules - for animated specs. Spec explicitly notes ''For libraries without animation - support, implement a small multiples grid showing key time snapshots''.' - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: 'All applicable features implemented: basemap with geographic context - (simplified Europe coastline), timestamp display (month labels per panel), - geographic point data with value encoding. Animation-specific features (play/pause, - slider, transitions) are N/A for static library.' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: lat/lon correctly mapped to Y/X axes. Temperature anomaly mapped - to color (imprint_div). Anomaly magnitude mapped to marker size. Time dimension - represented via faceting. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title exactly 'map-animated-temporal · python · matplotlib · anyplot.ai'. - Colorbar labeled 'Temp. Anomaly (°C)'. No categorical legend needed for - continuous colormap. - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: 'Good coverage: 25 European weather stations, 12-month temporal sequence - (6 key snapshots shown), both cold and warm anomalies visible, geographic - spread across latitudes, dual encoding. Simplified coastline limits geographic - context slightly.' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: European weather station temperature anomaly monitoring — realistic, - scientifically neutral scenario. Named capital cities as stations. Warming - trend over 12 months is plausible climate scenario. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Temperature anomaly range -1 to 3.5°C is physically realistic. European - coordinate range (35-72°N, -15-35°E) correct. 25 stations per panel appropriate - for map density. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear: imports → theme tokens → data generation → plot loop → colorbar - → save. No functions or classes.' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set before all random operations. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: 'All 8 imports used: os, matplotlib.pyplot, numpy, pandas, ScalarMappable, - LinearSegmentedColormap, Normalize, Polygon.' - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean, Pythonic. zip() with strict=False for axes/month_idx iteration. - No fake functionality. Appropriate complexity for the visualization. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png. No bbox_inches='tight'. dpi=400. facecolor=PAGE_BG - passed to savefig. - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: 'Very idiomatic: axes methods throughout (scatter, text, annotate, - axhline, axvline, add_patch), fig.subplots_adjust for layout, fig.add_axes - for colorbar, fig.colorbar with cax parameter.' - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: 'Distinctive matplotlib patterns: matplotlib.patches.Polygon for - geographic coastline rendering, ScalarMappable with shared Normalize for - cross-panel colorbar synchronization, LinearSegmentedColormap.from_list - for custom diverging colormap with theme-adaptive midpoint.' - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - subplots - - colorbar - - annotations - - patches - patterns: - - data-generation - - matrix-construction - - iteration-over-groups - dataprep: - - time-series - styling: - - custom-colormap - - alpha-blending - - edge-highlighting diff --git a/plots/map-animated-temporal/metadata/python/plotly.yaml b/plots/map-animated-temporal/metadata/python/plotly.yaml deleted file mode 100644 index 28ac05d518..0000000000 --- a/plots/map-animated-temporal/metadata/python/plotly.yaml +++ /dev/null @@ -1,255 +0,0 @@ -library: plotly -language: python -specification_id: map-animated-temporal -created: '2026-01-20T19:44:41Z' -updated: '2026-05-27T09:29:32Z' -generated_by: claude-sonnet -workflow_run: 26502212333 -issue: 3769 -language_version: 3.13.13 -library_version: 6.7.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/plotly/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/plotly/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/plotly/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/plotly/plot-dark.html -quality_score: 90 -review: - strengths: - - 'Sophisticated theme-adaptive geographic styling: six separate color variables - for land, ocean, borders, lakes, grid, and page background — all flip correctly - between light and dark' - - Compelling earthquake aftershock dataset with geophysically realistic spreading, - decay, and exponential magnitude distribution - - Double magnitude encoding (size + color) creates clear visual hierarchy that guides - the viewer attention - - Ghost trail effect (previous 2 days at 0.2 opacity) adds temporal depth without - cluttering the map - - Dynamic title font scaling prevents overflow for the long mandated title string - - Correct use of imprint_seq for continuous magnitude data with properly bounded - cmin/cmax - weaknesses: - - 'Slider tick labels font size not explicitly set — only color is specified in - sliders[0].font, size defaults to Plotly global default. Fix: add size=10 to font=dict(color=INK_SOFT)' - - 'Slight horizontal gap between map right edge and colorbar creates mild compositional - imbalance. Fix: adjust colorbar.x or right margin to tighten spacing' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — matches spec exactly - Chrome: Title "Earthquake Aftershock Sequence · map-animated-temporal · python · plotly · anyplot.ai" fully visible in dark #1A1A17 text. Colorbar title "Magnitude" and tick labels (2, 4, 6) readable in dark ink. "Time: Day 1" current value label and Day 1–15 slider tick labels readable. Play/Pause button labels readable on elevated #FFFDF6 background. - Data: Earthquake markers clustered off Japan's Pacific coast (Day 1 tight cluster near epicenter 38.3N, 142.4E). Color from #009E73 (low magnitude ~2) to #4467A3 (high magnitude ~7.5) via imprint_seq. Marker size proportional to magnitude (min 4px, up to ~26px). Land #E0DDD5, ocean #C5D5E5. - Legibility verdict: PASS - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — matches spec exactly - Chrome: Title, slider labels, colorbar labels, and button labels all render in light #F0EFE8 text against the dark surface. No dark-on-dark issues. "Time: Day 1" and Day tick labels clearly visible. Colorbar on #242420 elevated background with light text. - Data: Data colors identical to light render — imprint_seq green (#009E73) to blue (#4467A3) unchanged. Land #252520, ocean #1E2832. Same marker cluster visible at same position with same sizes. - Legibility verdict: PASS - criteria_checklist: - visual_quality: - score: 28 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: Fonts explicitly set for title (adaptive), colorbar (12pt/10pt), - currentvalue (12pt), buttons (12pt). Slider tick label size not explicitly - set — inherits global default. Both renders fully readable. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No text overlaps. Colorbar, buttons, and slider well-separated from - map. Marker overlap is data-accurate for earthquake cluster. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Markers sized by magnitude (min 4, up to ~26px). Color + size double - encoding provides excellent visual hierarchy. All data elements clearly - visible. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: imprint_seq (green to blue) is CVD-safe with no red-green-only encoding. - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: 3200x1800 canvas used correctly. Map, colorbar, controls, and title - fill canvas appropriately for an animated geographic map. Slight gap between - map right edge and colorbar. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Colorbar labeled 'Magnitude'. Title is descriptive with spec-id and - full attribution. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'imprint_seq [[0.0, #009E73], [1.0, #4467A3]] used correctly for - continuous magnitude. Light bg #FAF8F1, dark bg #1A1A17. All chrome theme-adaptive.' - design_excellence: - score: 14 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 6 - max: 8 - passed: true - comment: Six theme-adaptive geographic color variables, magnitude-based marker - sizing, ghost trail effect at 0.2 opacity, dynamic title font scaling, styled - colorbar and buttons with ELEVATED_BG. Strongly above defaults. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Subtle 15%-opacity lat/lon grid, styled colorbar with bordered elevated - background, themed play/pause buttons with matching borders. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Earthquake aftershock narrative with spreading pattern over 15 days. - Size+color double encoding for magnitude creates hierarchy. Ghost trail - provides temporal context. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Animated geographic scatter map — exactly as specified. - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Play/Pause controls, time slider, timestamp overlay, smooth transitions, - basemap with countries/coastlines, configurable speed, ghost trail all present. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: lat/lon mapped to geographic position; magnitude used for color and - size; days used as animation frame driver. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title 'Earthquake Aftershock Sequence · map-animated-temporal · python - · plotly · anyplot.ai' follows required format. Colorbar labeled 'Magnitude'. - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Shows geographic distribution, temporal evolution, magnitude encoding, - trail effect, spreading pattern with intensity decay. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Earthquake aftershock sequence off Japan (38.3N, 142.4E). Realistic - coordinates, exponential magnitude distribution, spreading and decay match - real aftershock behavior. Neutral scientific topic. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: '15 time steps (spec: 10-50), ~30 points/day (~450 total, within - 50-500). Magnitude range 2.0-7.5 geophysically accurate.' - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Flat imperative: tokens → data → traces → frames → layout → save. - No functions or classes.' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only os, numpy, plotly.graph_objects imported; all used. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Appropriate complexity. No fake UI elements — controls are real Plotly - updatemenus/sliders. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html. Current Plotly API. - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Correct use of go.Scattergeo + go.Frame, coloraxis, updatemenus, - sliders — standard Plotly animation patterns. - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: go.Frame animation engine, updatemenus play/pause, sliders with currentvalue, - go.Scattergeo with projection settings, interactive HTML export — all distinctively - Plotly. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - colorbar - - hover-tooltips - - html-export - patterns: - - data-generation - - iteration-over-groups - dataprep: [] - styling: - - alpha-blending - - custom-colormap diff --git a/plots/map-animated-temporal/metadata/python/plotnine.yaml b/plots/map-animated-temporal/metadata/python/plotnine.yaml deleted file mode 100644 index 4d31795c10..0000000000 --- a/plots/map-animated-temporal/metadata/python/plotnine.yaml +++ /dev/null @@ -1,248 +0,0 @@ -library: plotnine -language: python -specification_id: map-animated-temporal -created: '2026-01-20T19:44:52Z' -updated: '2026-05-27T09:51:03Z' -generated_by: claude-sonnet -workflow_run: 26502508493 -issue: 3769 -language_version: 3.13.13 -library_version: 0.15.4 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/plotnine/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/plotnine/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 81 -review: - strengths: - - Correct handling of animation limitation via small multiples — exactly the right - plotnine approach per library rules - - All theme-adaptive chrome correctly applied across both renders; no dark-on-dark - failures in the dark render - - 'imprint_seq continuous colormap (#009E73 → #4467A3) correctly used for magnitude - encoding' - - Ordered categorical ensures temporal facets appear in the correct sequence - - coord_fixed(ratio=1.0) preserves geographic proportions — important for map-like - visualizations - - Multi-layer composition (coastline path + aftershock points + epicenter star) - adds meaningful geographic context - weaknesses: - - Magnitude colorbar range extends to ~10+, which is physically impossible for aftershocks - of an M5.5 main event — cap magnitude values below M5.5 using min(5.0, max(1.0, - 5.5 - i * 0.15 + np.random.exponential(0.8))) to yield a realistic 1–5 range - - Facet strip labels at size=6 are very small and serve as the primary timestamp - display required by the spec — increase strip_text size to 7 for better readability - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct anyplot light surface - Chrome: Title "Earthquake Aftershocks · map-animated-temporal · python · plotnine · anyplot.ai" bold and readable; subtitle at size=7 visible; axis labels "Longitude (°)" / "Latitude (°)" at 10pt readable; tick labels at 8pt readable; facet strip labels "Day N: Mon DD" at size=6 bold — small but legible at full resolution; legend "Magnitude" with colorbar visible - Data: 12 facets showing aftershock points colored with imprint_seq gradient #009E73 (low magnitude) → #4467A3 (high magnitude); California coastline outline in muted ink; epicenter star (★) in each panel; points size=2.5, alpha=0.75 - Legibility verdict: PASS — all text readable against light background; strip labels are small (size=6) but explicit and readable at 3200×1800 - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct anyplot dark surface - Chrome: Title and all labels appear in light cream/gray tones (INK = #F0EFE8, INK_SOFT = #B8B7B0); coastline path is lighter/visible; epicenter star is white and prominent; legend background uses elevated dark token #242420; no dark-on-dark failures detected - Data: Data point colors (teal-to-blue gradient) are identical to light render — only chrome flipped; points remain clearly distinguishable against dark background - Legibility verdict: PASS — all text readable against dark background; theme-adaptive chrome correctly applied throughout - criteria_checklist: - visual_quality: - score: 25 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 6 - max: 8 - passed: true - comment: All font sizes explicitly set; title scaled for length; axis labels - 10pt, tick labels 8pt, strip labels 6pt bold — small but explicit; borderline - for mobile - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: No text collisions; x-axis tick labels dense across 4 columns but - not overlapping; alpha=0.75 reduces overplotting - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: Points size=2.5 alpha=0.75 appropriate for 30-50 per panel density; - coastline visible; epicenter star prominent - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: imprint_seq CVD-safe; no red-green sole signal - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: Correct 3200x1800 canvas; 12 facets fill space well; slight x-axis - tick label crowding in 4-column layout - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Longitude (°) and Latitude (°) with units; descriptive subtitle - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'imprint_seq (#009E73 → #4467A3) for continuous magnitude; background - #FAF8F1 light / #1A1A17 dark; all chrome theme-adaptive' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Professional layered geographic composition above configured-default; - coastline is simple 25-point polygon limiting visual fidelity - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Panel borders removed; grid subtle at alpha=0.15; legend background - elevated token; axis lines styled - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: 12-panel temporal sequence creates clear spreading narrative; epicenter - star anchors spatial story; subtitle guides interpretation - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Animation spec correctly handled as small multiples per plotnine - library rules - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Timestamps in facet labels, geographic context via coastline, magnitude - color encoding; optional trails not implemented - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: lon→x, lat→y, magnitude→color; all 12 time steps; coord_fixed preserves - aspect ratio - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title matches {Descriptive Title} · {spec-id} · {language} · {library} - · anyplot.ai format; Magnitude legend correct - data_quality: - score: 12 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: Shows temporal evolution, spatial spread, magnitude variation, epicenter - reference - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Earthquake aftershock sequence in California — real, neutral, scientific - scenario - - id: DQ-03 - name: Appropriate Scale - score: 2 - max: 4 - passed: false - comment: Magnitude colorbar reaches ~10+ for M5.5 aftershocks — physically - impossible; aftershocks cannot exceed main event magnitude; exponential - term generates unrealistic outliers - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Flat script: imports → data → plot → save' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports are used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean plotnine grammar of graphics; appropriate complexity - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png; no deprecated API - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: facet_wrap with ordered categorical, coord_fixed, inherit_aes=False, - full theme() token chain - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: facet_wrap for temporal small multiples, ordered categorical panel - ordering, multi-layer separate data frames - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - faceting - - colorbar - - layer-composition - patterns: - - data-generation - dataprep: - - time-series - styling: - - alpha-blending - - custom-colormap diff --git a/plots/map-animated-temporal/metadata/python/pygal.yaml b/plots/map-animated-temporal/metadata/python/pygal.yaml deleted file mode 100644 index f9beacbc00..0000000000 --- a/plots/map-animated-temporal/metadata/python/pygal.yaml +++ /dev/null @@ -1,279 +0,0 @@ -library: pygal -language: python -specification_id: map-animated-temporal -created: '2026-01-20T19:47:28Z' -updated: '2026-05-27T10:01:41Z' -generated_by: claude-sonnet -workflow_run: 26502606731 -issue: 3769 -language_version: 3.13.13 -library_version: 3.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/pygal/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/pygal/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/pygal/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/pygal/plot-dark.html -quality_score: 87 -review: - strengths: - - 'Correctly uses imprint_seq colormap (#009E73 → #4467A3) for seismic intensity - binning, matching Imprint continuous palette rules' - - 'Full spec compliance: small multiples PNG (6 temporal panels in 2×3 grid) plus - interactive HTML with play/pause, time slider, and tab navigation' - - Both light (#FAF8F1) and dark (#1A1A17) themes render correctly with theme-adaptive - chrome; data colors are identical across themes - - Compelling seismic narrative (Pacific Ring of Fire 72-hour spread) with descriptive - subtitle and sequential intensity bins - - Deterministic data, clean imports, no functions or classes — clean KISS structure - throughout - weaknesses: - - Design Excellence is above default but the individual pygal map panels retain - stock pygal world-map styling — custom cartographic refinements (e.g., subtle - panel separation, background differentiation between land/ocean) would raise DE-01/DE-02 - - Library Mastery is adequate but PIL handles most of the composition work (grid - layout, title, legend) rather than leveraging pygal directly — LM-01/LM-02 ceiling - constrained by this - - 'Panel titles (T1: Initial, etc.) rendered inside pygal panels at 66px in a 1066px-wide - cell; readable at desktop scale but may become small when the PNG is displayed - below ~800px wide' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct anyplot light surface, not pure white. - Chrome: Title "Seismic Activity · map-animated-temporal · python · pygal · anyplot.ai" in dark ink, fully readable. Subtitle "Pacific Ring of Fire — 72-hour seismic spread from epicenter in Japan" in secondary-ink tone below. Panel time labels (T1: Initial through T6: +72h) are dark text within each map cell — readable at full resolution. Legend at bottom has 6 color swatches with descriptive labels (Minimal (<20) through Extreme (80+)) in dark text, well-spaced. - Data: Six world map panels in 2×3 grid. Countries colored with imprint_seq green (#009E73) to blue (#4467A3) sequential gradient. Japan, Philippines, Indonesia, Australia, NZ, Chile, Peru, Mexico — highlighted appropriately per time step. Unaffected countries in gray outlines on light background. - Legibility verdict: PASS — all text elements readable; no light-on-light issues. - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct anyplot dark surface, not pure black. - Chrome: Title and subtitle rendered in light off-white text, clearly visible against dark background. Panel labels (T1-T6) appear in light tone within cells. Legend labels in light text at bottom — readable. No dark-on-dark failures observed; all chrome elements use theme-adaptive light tokens. - Data: Data colors are identical to light render — same imprint_seq green-to-blue gradient, confirming only chrome flips between themes. Country outlines visible in subtle lighter tone against dark background. Highlighted countries pop clearly. - Legibility verdict: PASS — all text readable; theme adaptation is correct throughout. - criteria_checklist: - visual_quality: - score: 29 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: 'All font sizes explicitly set (PIL: 64/42/38pt; pygal Style: 66/44/36). - Readable at full resolution in both themes. Minor deduction: pygal panel - titles at 66px in 1066px-wide cells become small below ~800px display width.' - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No text overlaps anywhere — panel labels, legend items, title, subtitle - all well-separated. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Highlighted countries are clearly visible in both themes. Sequential - colormap provides good luminance separation across 6 bins. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: imprint_seq green-to-blue gradient is CVD-safe; sequential luminance - change aids discrimination for CVD viewers. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 3200×1800 canvas well-utilized. Title + subtitle header, 2×3 grid - of maps, shared legend at bottom — balanced proportions. Canvas gate passed. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Map visualization with no traditional axes. Title is descriptive; - time-step labels serve as temporal axis labels. Legend bins are clearly - described with value ranges. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Sequential colormap interpolates #009E73 → #4467A3 (imprint_seq). - First bin = brand green. Backgrounds: #FAF8F1 (light) / #1A1A17 (dark). - Data colors identical across themes. Chrome fully theme-adaptive.' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Above configured-default level: custom imprint_seq colormap, narrative - subtitle, clean PIL composition, shared legend at bottom. Map panels retain - stock pygal styling without cartographic refinements.' - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: true - comment: Good overall composition cleanliness; generous whitespace in header/footer - areas. Individual map panels are fairly stock pygal world map rendering - without additional refinement (e.g., no panel separation, no ocean/land - differentiation beyond defaults). - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: 'Clear narrative: subtitle names the story, time labels (T1-T6) guide - progression, sequential color intensity encodes seismic severity. Viewer - can follow the Pacific Ring spread. Above default but lacks annotation-level - emphasis.' - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: 'Animated map over time. For static PNG: small multiples (6 temporal - panels). For HTML: full animation with play/pause, time slider, and tab - navigation. Matches spec guidance for libraries without native animation.' - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Play/pause controls ✓, time slider ✓, current timestamp display ✓, - smooth CSS opacity transitions ✓, basemap with geographic context ✓, configurable - animation speed (1.2s) ✓, small multiples for static PNG ✓. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Country codes map correctly to pygal_maps_world World chart. Intensity - values correctly binned and color-mapped. Temporal progression visible across - 6 panels. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title: ''Seismic Activity · map-animated-temporal · python · pygal - · anyplot.ai'' — correct {Descriptive Title} · {spec-id} · {language} · - {library} · anyplot.ai format. Legend: 6 bins with descriptive labels and - value ranges.' - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: All 6 intensity bins represented across time steps. Clear temporal - evolution from concentrated origin to distributed spread. Geographic diversity - from T1 to T6 demonstrates full feature of animated temporal map. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Seismic aftershock tracking across the Pacific Ring of Fire is a - well-established scientific use case. Country selection (Japan → Philippines - → Indonesia → NZ → Australia → South America) follows plausible seismic - geography. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: 0-100 intensity scale with 6h/12h/24h/48h/72h time steps is plausible - for aftershock sequence monitoring. Decreasing intensity at epicenter (Japan - 95→35) while spreading outward is geophysically consistent. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'No functions or classes. Flat structure: imports → data → PNG render - → HTML render. Module import hack at top is a known necessity for anyplot - (filename pygal.py would shadow the package).' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Fully deterministic — all data is hardcoded dictionaries; no random - elements. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: 'All imports are used: importlib.util (module shadow fix), json (HTML - JS data), os/sys (theme env + path), io.BytesIO (PIL image loading), PIL - (image composition), pygal Style + World map.' - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: binned_by_time pre-computed once and reused for both PNG and HTML - render passes — avoids duplication. No fake functionality. Appropriate complexity - for the dual-output (PNG + HTML) requirement. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html using ANYPLOT_THEME - env var. Correct output for pygal (interactive library). - library_mastery: - score: 6 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 3 - max: 5 - passed: true - comment: Uses World from pygal_maps_world with proper Style theming and render_to_png()/render() - API. Correct usage but PIL handles most of the composition; the pygal API - surface used is fairly narrow. - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: 'pygal_maps_world.maps.World is a pygal-specific geographic map type - — the country-code-keyed data API is distinctive to pygal. The animated - HTML output leverages pygal''s SVG rendering pipeline. Partial credit: PIL - replaces what pygal could do with its own layout compositing.' - verdict: APPROVED -impl_tags: - dependencies: - - pillow - techniques: - - html-export - - custom-legend - - faceting - patterns: - - iteration-over-groups - dataprep: - - binning - styling: - - custom-colormap diff --git a/plots/map-animated-temporal/metadata/python/seaborn.yaml b/plots/map-animated-temporal/metadata/python/seaborn.yaml deleted file mode 100644 index 07e382d54e..0000000000 --- a/plots/map-animated-temporal/metadata/python/seaborn.yaml +++ /dev/null @@ -1,287 +0,0 @@ -library: seaborn -language: python -specification_id: map-animated-temporal -created: '2026-01-20T19:45:04Z' -updated: '2026-05-27T09:36:39Z' -generated_by: claude-sonnet -workflow_run: 26502113970 -issue: 3769 -language_version: 3.13.13 -library_version: 0.13.2 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/seaborn/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/python/seaborn/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 88 -review: - strengths: - - 'Excellent dual encoding: magnitude mapped to both hue (imprint_seq colormap) - and marker size simultaneously, maximizing data density without clutter' - - KDE density contours (sns.kdeplot) are a seaborn-distinctive feature used effectively - to show cumulative aftershock clustering across panels - - Ghost trail pattern (faded prior-period scatter points) elegantly communicates - temporal history without adding noise - - 'Full anyplot theme compliance: correct background tokens (#FAF8F1/#1A1A17), imprint_seq - colormap, INK/INK_SOFT tokens throughout, no bbox_inches=''tight''' - - 'Semantic use of #AE3030 for the epicenter marker is contextually appropriate - — danger/alert role matches the anyplot semantic anchor rules' - - Ocean tint adds meaningful geographic context without requiring an external mapping - library - - np.random.seed(42) ensures reproducibility; KISS flat-script structure with no - functions/classes - weaknesses: - - Tick labels and n= count annotations are set at fontsize=6.5pt — readable at full - 3200×1800 resolution but borderline at mobile scale (~400px); nudge to 7-7.5pt - to improve mobile legibility - - Ghost trail scatter uses ax.scatter(s=3) directly; at 3pt² the trail points are - nearly invisible in the rendered image — increase to s=5-6 to make the temporal - history more legible - - Small multiples are created with matplotlib plt.subplots() rather than seaborn's - FacetGrid — slightly misses the seaborn-idiomatic approach for faceted layouts - - The synthetic coastline is defined by only 10 hand-typed lat/lon points, producing - a visibly coarse approximation; a slightly denser set of waypoints (~20-30) would - improve geographic fidelity - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct, matches required token. - Chrome: Title "map-animated-temporal · python · seaborn · anyplot.ai" in dark INK at fontsize=12, clearly readable at the top. Panel subtitles "Day 1"–"Day 6" bold at 9pt, clearly visible. X-axis label "Lon (°E)" on bottom row, Y-axis label "Lat (°N)" on left column, both at 8pt and readable. Tick labels at 6.5pt show longitude (140-146) and latitude (36-41) values — readable at full resolution, borderline at mobile scale. Italic n= annotations in bottom-right of each panel at 6.5pt are readable. Colorbar label "Magnitude" rotated vertically at 7.5pt is readable. Legend at bottom center ("★ Epicenter", "— Coastline") at 7pt fully visible. - Data: Scatter points use imprint_seq colormap (#009E73 to #4467A3), sizes scaled 10-110pt² by magnitude. Ghost trail (faded prior-period points at alpha=0.15, s=3) accumulates from Day 2 onward. KDE density contours in INK_SOFT at alpha=0.30 appear from Day 2 onward. Epicenter marked with red star (#AE3030). Ocean tint (#D4E8F0 at alpha=0.35) fills east of coastline. Coastline drawn in INK_SOFT at 0.8pt linewidth. - Legibility verdict: PASS — all text readable against the light background; no light-on-light failures. - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct, matches required token. - Chrome: Title and all labels rendered in light INK (#F0EFE8), clearly visible against the dark background. Tick labels in INK_SOFT (#B8B7B0) are readable against the dark background — no dark-on-dark failures observed. Colorbar tick labels light-colored and readable. Legend frame uses ELEVATED_BG (#242420) — just slightly lighter than the plot background, clean separation. Panel subtitles "Day 1"–"Day 6" in INK are clearly visible. - Data: Scatter point colors (imprint_seq colormap) are identical to the light render — teal-to-blue gradient consistent across both themes, confirming only chrome flips. Ocean tint shifts to #192830 (darker blue-gray), maintaining geographic context. Ghost trail points in INK_SOFT (#B8B7B0 at alpha=0.15) are very faint but intentionally subtle. KDE contours in INK_SOFT visible against dark background. - Legibility verdict: PASS — all text readable against the dark background; no dark-on-dark failures; data colors unchanged between themes. - criteria_checklist: - visual_quality: - score: 27 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 6 - max: 8 - passed: true - comment: Title at 12pt and panel titles at 9pt are well-sized. Tick labels - and n= annotations at 6.5pt are readable at full resolution but borderline - at mobile scale; small deduction. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No text-on-text or text-on-data overlaps. Dense later panels (Day - 5/6) handled via alpha layering (ghost trail 0.15, main scatter 0.80). KDE - contours zorder-managed appropriately. - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: Main scatter markers (10-110pt²) are well-visible. Ghost trail at - s=3, alpha=0.15 is nearly invisible by design but could be slightly larger - (s=5) to make temporal history more apparent. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: imprint_seq (green→blue) is perceptually uniform and CVD-safe. Epicenter - red (#AE3030) used semantically. Size+color dual encoding adds redundancy. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Canvas exactly 3200x1800. subplots_adjust carefully set (left=0.08, - right=0.91, top=0.88, bottom=0.14, wspace=0.38, hspace=0.50) with colorbar - at 0.93. No overflow. Canvas gate passed. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: 'Title correct format. X: ''Lon (°E)'', Y: ''Lat (°N)'' — both descriptive - with units.' - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'imprint_seq colormap (correct for single-polarity magnitude data). - Backgrounds #FAF8F1/#1A1A17. INK/INK_SOFT tokens throughout. Epicenter uses - semantic red #AE3030. Full compliance.' - design_excellence: - score: 14 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 6 - max: 8 - passed: true - comment: Well above default (4). Dual encoding (hue+size), ghost trail, KDE - density contours, ocean tint, semantic epicenter marker — all intentional - and add meaning. Professional polish throughout. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Top/right spines removed on all panels. Subtle grid (alpha=0.12). - Ocean tint geographic context is a refined touch. Panel spacing could be - slightly more generous (panels feel slightly dense at wspace=0.38). - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: 'Strong temporal narrative: Day 1→6 progression shows aftershock - spreading. Ghost trails build historical context. KDE contours show clustering - evolution. n= annotations track data volume. Clear focal point (epicenter - star). Good but not exceptional — a brief annotation explaining the spreading - pattern would elevate to 5-6.' - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Small multiples geographic map is the correct static fallback for - seaborn per library rules. Correctly shows temporal snapshots with spatial - distribution. - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Geographic context (coastline+ocean tint), temporal progression (6 - panels), point data with value encoding, timestamp in panel titles, point - trails (ghost scatter). Animation controls not applicable for static library - — correctly omitted per seaborn rules. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: lat→y, lon→x correctly mapped. Timestamp→period labels. Magnitude→both - hue and size (dual encoding exceeds requirement). - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title: ''map-animated-temporal · python · seaborn · anyplot.ai'' - — exact correct format. Epicenter and Coastline legend items. Colorbar labeled - ''Magnitude''.' - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'Shows all aspects: spatial distribution, temporal evolution, magnitude - encoding, geographic context, cumulative density (KDE), historical trail, - point counts.' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Tohoku aftershock sequence is realistic, plausible, and contextually - neutral. Epicenter at (38.1°N, 142.8°E) is geographically accurate for the - 2011 Tōhoku earthquake zone. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Magnitude range 2.5-5.8 is realistic for aftershock sequences. ±3.5° - spatial extent appropriate for regional aftershock spread. 6 periods with - growing point counts (12→32) models realistic decay pattern. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Flat sequential script, no functions or classes. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set before all random operations. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All 7 imports (os, matplotlib.pyplot, numpy, pandas, seaborn, LinearSegmentedColormap, - Line2D) are actively used. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean loop structure, well-named variables, no fake UI elements. - Appropriate complexity for the visualization task. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: plt.savefig(f'plot-{THEME}.png', dpi=400, facecolor=PAGE_BG) — correct - format, no bbox_inches='tight'. - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: 'sns.scatterplot with hue+size+palette+hue_norm, sns.kdeplot with - data/x/y/fill/levels, sns.set_theme with rc dict — all idiomatic seaborn - axes-level patterns. Minor: small multiples done via plt.subplots() rather - than seaborn''s FacetGrid.' - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: sns.kdeplot for cumulative density contours is a genuinely seaborn-distinctive - feature — demonstrates statistical estimation capability unique to seaborn - vs matplotlib. Dual hue+size encoding in sns.scatterplot also idiomatic. - Could further leverage seaborn's FacetGrid or PairGrid for small multiples - to be more library-distinctive. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - colorbar - - annotations - - subplots - - custom-legend - patterns: - - data-generation - - iteration-over-groups - - explicit-figure - dataprep: - - kde - styling: - - custom-colormap - - alpha-blending - - edge-highlighting - - grid-styling diff --git a/plots/map-animated-temporal/metadata/r/ggplot2.yaml b/plots/map-animated-temporal/metadata/r/ggplot2.yaml deleted file mode 100644 index c46622d2aa..0000000000 --- a/plots/map-animated-temporal/metadata/r/ggplot2.yaml +++ /dev/null @@ -1,260 +0,0 @@ -library: ggplot2 -language: r -specification_id: map-animated-temporal -created: '2026-05-27T09:37:21Z' -updated: '2026-05-27T09:52:56Z' -generated_by: claude-sonnet -workflow_run: 26502897271 -issue: 3769 -language_version: 4.4.1 -library_version: 3.5.1 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/r/ggplot2/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/map-animated-temporal/r/ggplot2/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 90 -review: - strengths: - - Correct anyplot sequential colormap (#009E73→#4467A3) applied to continuous magnitude - data — no forbidden cmaps used - - 'Both light (#FAF8F1) and dark (#1A1A17) themes fully correct: all chrome tokens - (INK, INK_SOFT, ELEVATED_BG, PAGE_BG) wired through to every element' - - 'Excellent data domain: Tohoku earthquake Omori-law aftershock decay is scientifically - accurate, neutral, and tells a clear temporal story' - - Dual encoding of magnitude via both color and size provides rich information density - with minimal clutter - - Facet strip labels include event counts (n=45, n=28…) that numerically reinforce - the visible frequency decay — elegant data storytelling - - Epicenter cross-marker provides a geographic anchor across all 9 panels - - All font sizes explicitly set; both theme renders fully readable with no dark-on-dark - or light-on-light failures - - 'Perfect code quality: KISS structure, set.seed(42), clean imports, correct ggsave - dimensions (8×4.5 in, dpi=400 → 3200×1800 px)' - weaknesses: - - 'No geographic basemap: spec explicitly requires ''Include a basemap with geographic - context (country boundaries, coastlines, or terrain)''. ggplot2 without sf/ggmap - cannot render basemaps (out of scope per library guide), so the context relies - solely on lat/lon tick labels — an accepted library limitation but still a notable - spec gap' - - 'DE-01 moderate: design is above defaults (dual encoding, epicenter marker, informative - strip labels) but still founded on theme_minimal — the palette and theming are - correct but the overall aesthetic does not yet reach FiveThirtyEight / publication-quality - polish (score: 5/8)' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct anyplot light surface. Panel backgrounds match page background. - Chrome: Title "map-animated-temporal · r · ggplot2 · anyplot.ai" centered, dark ink, ~70% width — fits well. Subtitle in INK_SOFT, smaller, readable. Facet strip labels ("Day 1 (n=45)"…"Day 9 (n=9)") bold, dark, on ELEVATED_BG strip. Axis labels "Longitude" / "Latitude" in dark INK. Tick labels in INK_SOFT with degree notation (140°E, 142°E, 144°E; 36°N, 38°N, 40°N), rotated 45° on x-axis. Colorbar legend titled "Mag" on right with values 3–7, text in INK_SOFT/INK. All text clearly readable against light background. - Data: 3×3 facet grid of scatter points colored green (#009E73, low magnitude) to blue (#4467A3, high magnitude) via imprint_seq sequential colormap. Point size also encodes magnitude (dual encoding). Alpha=0.75. Epicenter marked with '+' cross in dark INK. Visible temporal decay in event count and growing spatial spread across panels. - Legibility verdict: PASS — all text readable, no overflow, no overlap. - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct anyplot dark surface. Panel backgrounds match page background. - Chrome: Title, subtitle, facet strips, axis labels, and tick labels all render in light (#F0EFE8 / #B8B7B0) against the near-black background. No dark-on-dark failures observed. Colorbar legend text light-colored and readable. Grid lines subtle against dark background. - Data: Data colors are identical to light render — green-to-blue gradient unchanged (only chrome flips). Epicenter cross visible in light INK. The decay pattern and spatial spread remain clearly visible. - Legibility verdict: PASS — all text readable in dark theme, no dark-on-dark failures. - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All font sizes explicitly set (base_size=7, axis.title=9, axis.text=8, - plot.title=12, etc.). Both theme renders fully readable. Rotated x-axis - tick labels prevent overlap. No overflow. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text in either render. Facets well-spaced. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Points sized 0.8–4.5 proportional to magnitude with alpha=0.75. Appropriate - for 9–45 points per panel. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Green-to-blue gradient is CVD-safe. Good contrast on both backgrounds. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 3×3 facet grid fills landscape canvas well. Margins balanced. Legend - positioned right. Canvas 3200×1800 confirmed. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Axes labeled 'Longitude' and 'Latitude' with degree tick notation - (140°E, 36°N). Descriptive with implicit units. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Continuous colormap uses imprint_seq (#009E73→#4467A3). Backgrounds - #FAF8F1/#1A1A17. All chrome tokens theme-correct in both renders.' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Above defaults: dual encoding (size+color), epicenter marker, informative - strip labels with event counts, correct palette. Still theme_minimal based - — not yet FiveThirtyEight polish.' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Very subtle grid (linewidth=0.15), panel borders appropriate for - map-style facets, good whitespace. Not fully stripped down but well-refined. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Subtitle states Omori law story. Event counts in strip labels numerically - show decay. Spatial spread visually grows across panels. Clear focal narrative. - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: ggplot2 cannot animate; correctly implements small multiples (facet_wrap) - as the static alternative per library guide. - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Time dimension via facets ✓, timestamp in strip labels ✓, magnitude - encoding ✓, size variation ✓. Basemap missing — spec requires 'geographic - context (country boundaries, coastlines)' but sf/ggmap are out-of-scope - for ggplot2 in this catalog. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Longitude→x, Latitude→y, Magnitude→color+size, Day→facet. All correct. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title 'map-animated-temporal · r · ggplot2 · anyplot.ai' matches - required format. Legend 'Mag' for colorbar is accurate. - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Shows temporal decay (event counts), spatial spread growth, magnitude - variation, and 9 distinct time steps. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Tohoku earthquake aftershock sequence — real historical event context, - scientifically grounded, neutral. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Magnitude 2.5–7.5, coordinates ~38°N/142°E (correct for Tohoku), - Omori-law decay parameters physically realistic. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Clean: theme tokens → data → plot → save. No functions or classes.' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: set.seed(42) present. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only ggplot2 and ragg imported, both used. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: 'Idiomatic R: do.call(rbind, lapply(...)) for data generation. No - over-engineering.' - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: ggsave with sprintf('plot-%s.png', THEME), ragg::agg_png, 8×4.5 in - @ dpi=400 → 3200×1800 px. - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: 'Proper grammar of graphics: aes mappings, scale_color_gradient, - scale_size_continuous with guide=''none'', factor level reordering, theme - layering on theme_minimal.' - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: facet_wrap with ordered factor levels and informative strip labels - is a distinctive ggplot2 strength. guide='none' on size while keeping colorbar - shows ggplot2 legend control. Could push further with coord_fixed() for - true geographic proportions. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - faceting - - colorbar - patterns: - - data-generation - dataprep: [] - styling: - - alpha-blending - - custom-colormap diff --git a/plots/map-animated-temporal/specification.md b/plots/map-animated-temporal/specification.md deleted file mode 100644 index e5fbb3d552..0000000000 --- a/plots/map-animated-temporal/specification.md +++ /dev/null @@ -1,33 +0,0 @@ -# map-animated-temporal: Animated Map over Time - -## Description - -An animated geographic map visualization that displays point data changing over time, with interactive play controls and a time slider for navigation. Each frame shows the spatial distribution of data at a specific timestamp, with smooth transitions between frames revealing temporal patterns and movement. This visualization is ideal for storytelling with geospatial data, allowing viewers to observe how geographic phenomena evolve, spread, or shift across locations over time. - -## Applications - -- Visualizing disease spread patterns across regions, showing how an outbreak expands geographically over days or weeks -- Tracking historical boundary changes or migration patterns, revealing temporal shifts in population or territorial control -- Displaying temporal sequences of events like earthquake aftershocks, crime incidents, or social media activity hotspots -- Animating weather pattern movement such as storm tracks, temperature changes, or air quality measurements across a geographic area - -## Data - -- `lat` (numeric) - Latitude coordinate for each point (-90 to 90) -- `lon` (numeric) - Longitude coordinate for each point (-180 to 180) -- `timestamp` (datetime) - Time dimension that drives the animation frames -- `value` (numeric, optional) - Data value for color or size encoding at each point -- `label` (string, optional) - Point label or identifier for tooltips -- Size: 50-500 points across 10-50 time steps recommended for smooth, comprehensible animation -- Example: Daily COVID case locations over a month, hourly earthquake aftershock sequences, weekly crime incident reports - -## Notes - -- Include play/pause controls that are prominently visible and intuitive to use -- Provide a time slider for manual navigation to any point in the animation sequence -- Display the current timestamp clearly during animation (as title, subtitle, or overlay text) -- Use smooth transitions between frames to enhance the visual narrative -- Include a basemap with geographic context (country boundaries, coastlines, or terrain) -- Animation speed should be configurable or use a sensible default duration -- For libraries without animation support, implement a small multiples grid showing key time snapshots -- Consider adding optional point trails to show movement paths over recent frames diff --git a/plots/map-animated-temporal/specification.yaml b/plots/map-animated-temporal/specification.yaml deleted file mode 100644 index 0429e69ad9..0000000000 --- a/plots/map-animated-temporal/specification.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Specification-level metadata for map-animated-temporal -# Auto-synced to PostgreSQL on push to main - -spec_id: map-animated-temporal -title: Animated Map over Time - -# Specification tracking -created: 2026-01-11T22:50:31Z -updated: 2026-05-27T09:07:12Z -issue: 3769 -suggested: MarkusNeusinger - -# Classification tags (applies to all library implementations) -# See docs/reference/tagging-system.md for detailed guidelines -tags: - plot_type: - - map - - scatter - data_type: - - geospatial - - time-series - - numeric - domain: - - general - - healthcare - - science - features: - - animated - - interactive - - temporal - - geographic diff --git a/plots/map-drilldown-geographic/implementations/julia/makie.jl b/plots/map-drilldown-geographic/implementations/julia/makie.jl deleted file mode 100644 index 1f996d0d58..0000000000 --- a/plots/map-drilldown-geographic/implementations/julia/makie.jl +++ /dev/null @@ -1,195 +0,0 @@ -# anyplot.ai -# map-drilldown-geographic: Drillable Geographic Map -# Library: makie 0.22.10 | Julia 1.11.9 -# Quality: 84/100 | Created: 2026-05-23 - -using CairoMakie -using Colors -using Random - -Random.seed!(42) - -const THEME = get(ENV, "ANYPLOT_THEME", "light") -const PAGE_BG = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17" -const ELEVATED_BG = THEME == "light" ? colorant"#FFFDF6" : colorant"#242420" -const INK = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8" -const INK_SOFT = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0" -const INK_MUTED = THEME == "light" ? colorant"#6B6A63" : colorant"#A8A79F" - -const ANYPLOT_SEQ = cgrad([colorant"#009E73", colorant"#003D94"]) - -# World regions — annual sales ($B) -w_names = ["N. America", "Europe", "Asia-Pacific", "S. America", "Middle East", "Africa"] -w_lon = Float64[-100.0, 15.0, 110.0, -60.0, 45.0, 20.0] -w_lat = Float64[ 45.0, 52.0, 25.0, -15.0, 25.0, 0.0] -w_vals = Float64[ 2.80, 2.10, 3.60, 1.40, 1.10, 0.70] - -# Approximate geographic bounding boxes per world region (for map context) -w_boxes = [ - (-170.0, 15.0, -50.0, 80.0), # N. America - ( -15.0, 35.0, 45.0, 72.0), # Europe - ( 60.0,-50.0, 180.0, 78.0), # Asia-Pacific - ( -85.0,-58.0, -33.0, 15.0), # S. America - ( 25.0, 10.0, 65.0, 42.0), # Middle East - ( -20.0,-38.0, 55.0, 38.0), # Africa -] - -# US states — drill into North America (values sum to $2.80B = parent node) -s_abbr = ["CA", "TX", "NY", "FL", "WA", "IL", "MA", "CO"] -s_lon = Float64[-119.0, -99.0, -74.0, -81.0, -120.0, -89.0, -71.0, -105.0] -s_lat = Float64[ 37.0, 31.0, 43.0, 27.0, 47.0, 40.0, 42.0, 39.0] -s_vals = Float64[ 0.60, 0.46, 0.49, 0.36, 0.27, 0.24, 0.20, 0.18] - -# California cities — drill into California (sorted ascending for bar chart) -c_names = ["Sacramento", "San Jose", "San Diego", "Los Angeles", "San Francisco"] -c_vals = Float64[0.03, 0.07, 0.09, 0.19, 0.24] - -# Normalize per-level for color mapping -w_norm = (w_vals .- minimum(w_vals)) ./ (maximum(w_vals) - minimum(w_vals)) -s_norm = (s_vals .- minimum(s_vals)) ./ (maximum(s_vals) - minimum(s_vals)) -c_norm = (c_vals .- minimum(c_vals)) ./ (maximum(c_vals) - minimum(c_vals)) -c_colors = [ANYPLOT_SEQ[v] for v in c_norm] - -# NY (-74°,43°) and MA (-71°,42°) are adjacent — stagger labels left/right -s_offsets = [(0,14),(0,14),(-14,10),(0,14),(0,14),(0,14),(14,10),(0,14)] - -# Highlight the drilled-into region with a bold INK ring -w_sw = Float64[i == 1 ? 2.8 : 1.2 for i in 1:length(w_names)] -w_sc = [i == 1 ? INK : INK_SOFT for i in 1:length(w_names)] -s_sw = Float64[i == 1 ? 2.8 : 1.2 for i in 1:length(s_abbr)] -s_sc = [i == 1 ? INK : INK_SOFT for i in 1:length(s_abbr)] - -box_fill = RGBAf(INK.r, INK.g, INK.b, 0.06) -box_border = RGBAf(INK.r, INK.g, INK.b, 0.22) - -fig = Figure(size=(1600, 900), fontsize=13, backgroundcolor=PAGE_BG) - -# Main title -Label(fig[0, 1:3], - "Sales Drill-Down · map-drilldown-geographic · julia · makie · anyplot.ai", - fontsize=17, color=INK, font=:bold, halign=:center, padding=(0, 0, 2, 0), -) - -# Breadcrumb navigation strip -Label(fig[1, 1:3], - "World ▶ N. America ▶ California", - fontsize=11, color=INK_SOFT, halign=:center, padding=(0, 0, 0, 2), -) - -# Panel 1 — World regions bubble map -ax1 = Axis(fig[2, 1]; - title="① World Regions", - titlesize=14, titlecolor=INK, - xlabel="Longitude", ylabel="Latitude", - xlabelsize=11, ylabelsize=11, - xlabelcolor=INK, ylabelcolor=INK, - xticklabelsize=9, yticklabelsize=9, - xticklabelcolor=INK_SOFT, yticklabelcolor=INK_SOFT, - xtickcolor=INK_SOFT, ytickcolor=INK_SOFT, - backgroundcolor=PAGE_BG, - leftspinecolor=INK_SOFT, bottomspinecolor=INK_SOFT, - topspinevisible=false, rightspinevisible=false, - xgridvisible=false, ygridvisible=false, -) - -for (xmin, ymin, xmax, ymax) in w_boxes - poly!(ax1, - [Point2f(xmin, ymin), Point2f(xmax, ymin), Point2f(xmax, ymax), Point2f(xmin, ymax)]; - color=box_fill, strokecolor=box_border, strokewidth=0.7, - ) -end -scatter!(ax1, w_lon, w_lat; - color=w_norm, colormap=ANYPLOT_SEQ, colorrange=(0.0, 1.0), - markersize=28, strokewidth=w_sw, strokecolor=w_sc, -) -for i in eachindex(w_names) - text!(ax1, w_lon[i], w_lat[i]; - text="$(w_names[i])\n\$$(w_vals[i])B", - align=(:center, :bottom), offset=(0, 17), - fontsize=8, color=i == 1 ? INK : INK_SOFT, - ) -end -xlims!(ax1, -180, 185) -ylims!(ax1, -65, 82) - -# Panel 2 — US states bubble map (ylabel omitted — same axis type as Panel 1) -ax2 = Axis(fig[2, 2]; - title="② N. America → US States", - titlesize=14, titlecolor=INK, - xlabel="Longitude", ylabel="", - xlabelsize=11, ylabelsize=11, - xlabelcolor=INK, ylabelcolor=INK, - xticklabelsize=9, yticklabelsize=9, - xticklabelcolor=INK_SOFT, yticklabelcolor=INK_SOFT, - xtickcolor=INK_SOFT, ytickcolor=INK_SOFT, - backgroundcolor=PAGE_BG, - leftspinecolor=INK_SOFT, bottomspinecolor=INK_SOFT, - topspinevisible=false, rightspinevisible=false, - xgridvisible=false, ygridvisible=false, -) - -poly!(ax2, - [Point2f(-127, 48), Point2f(-95, 49), Point2f(-82, 46), - Point2f(-67, 47), Point2f(-67, 25), Point2f(-97, 26), - Point2f(-117, 32), Point2f(-127, 34)]; - color=box_fill, strokecolor=box_border, strokewidth=0.7, -) -scatter!(ax2, s_lon, s_lat; - color=s_norm, colormap=ANYPLOT_SEQ, colorrange=(0.0, 1.0), - markersize=24, strokewidth=s_sw, strokecolor=s_sc, -) -for i in eachindex(s_abbr) - text!(ax2, s_lon[i], s_lat[i]; - text=s_abbr[i], - align=(:center, :bottom), offset=s_offsets[i], - fontsize=9, color=i == 1 ? INK : INK_SOFT, - ) -end -xlims!(ax2, -132, -62) -ylims!(ax2, 22, 52) - -# Panel 3 — California cities horizontal bar chart -ax3 = Axis(fig[2, 3]; - title="③ California → Cities", - titlesize=14, titlecolor=INK, - xlabel="Sales (\$B)", ylabel="", - xlabelsize=11, ylabelsize=11, - xlabelcolor=INK, ylabelcolor=INK, - xticklabelsize=9, yticklabelsize=9, - xticklabelcolor=INK_SOFT, yticklabelcolor=INK_SOFT, - xtickcolor=INK_SOFT, ytickcolor=INK_SOFT, - backgroundcolor=PAGE_BG, - leftspinecolor=INK_SOFT, bottomspinecolor=INK_SOFT, - topspinevisible=false, rightspinevisible=false, - xgridvisible=false, ygridvisible=false, - yticks=(1:5, c_names), -) - -barplot!(ax3, 1:5, c_vals; direction=:x, color=c_colors) -for i in eachindex(c_vals) - text!(ax3, c_vals[i] + 0.006, i; - text="\$$(c_vals[i])B", - align=(:left, :center), - fontsize=9, color=INK_SOFT, - ) -end -xlims!(ax3, 0.0, 0.30) - -# Shared colorbar — green = low, blue = high within each level -Colorbar(fig[3, 1:3]; - colormap=ANYPLOT_SEQ, - limits=(0.0, 1.0), - vertical=false, - label="Relative Sales Volume (within each level)", - labelsize=10, labelcolor=INK, - ticklabelcolor=INK_SOFT, tickcolor=INK_SOFT, - ticks=([0.0, 0.5, 1.0], ["Low", "Mid", "High"]), - height=14, tellheight=true, -) - -rowgap!(fig.layout, 1, 4) -rowgap!(fig.layout, 2, 10) -rowgap!(fig.layout, 3, 6) -colgap!(fig.layout, 12) - -save("plot-$(THEME).png", fig; px_per_unit=2) diff --git a/plots/map-drilldown-geographic/implementations/python/altair.py b/plots/map-drilldown-geographic/implementations/python/altair.py deleted file mode 100644 index 882e9f1f2c..0000000000 --- a/plots/map-drilldown-geographic/implementations/python/altair.py +++ /dev/null @@ -1,457 +0,0 @@ -""" anyplot.ai -map-drilldown-geographic: Drillable Geographic Map -Library: altair 6.1.0 | Python 3.13.13 -Quality: 81/100 | Updated: 2026-05-23 -""" - -import os -import sys - - -sys.path.pop(0) -import altair as alt -import pandas as pd -from PIL import Image - - -# 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" - -IMPRINT = ["#009E73", "#C475FD", "#AE3030", "#4467A3"] -REGIONS = ["West", "South", "Midwest", "Northeast"] - -# Data — US state-level sales ($K), Region → State → City hierarchy -us_states_url = "https://cdn.jsdelivr.net/npm/vega-datasets@v1.29.0/data/us-10m.json" - -states_data = pd.DataFrame( - { - "id": [ - 1, - 2, - 4, - 5, - 6, - 8, - 9, - 10, - 11, - 12, - 13, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - 32, - 33, - 34, - 35, - 36, - 37, - 38, - 39, - 40, - 41, - 42, - 44, - 45, - 46, - 47, - 48, - 49, - 50, - 51, - 53, - 54, - 55, - 56, - ], - "state": [ - "Alabama", - "Alaska", - "Arizona", - "Arkansas", - "California", - "Colorado", - "Connecticut", - "Delaware", - "District of Columbia", - "Florida", - "Georgia", - "Hawaii", - "Idaho", - "Illinois", - "Indiana", - "Iowa", - "Kansas", - "Kentucky", - "Louisiana", - "Maine", - "Maryland", - "Massachusetts", - "Michigan", - "Minnesota", - "Mississippi", - "Missouri", - "Montana", - "Nebraska", - "Nevada", - "New Hampshire", - "New Jersey", - "New Mexico", - "New York", - "North Carolina", - "North Dakota", - "Ohio", - "Oklahoma", - "Oregon", - "Pennsylvania", - "Rhode Island", - "South Carolina", - "South Dakota", - "Tennessee", - "Texas", - "Utah", - "Vermont", - "Virginia", - "Washington", - "West Virginia", - "Wisconsin", - "Wyoming", - ], - "value": [ - 145, - 78, - 210, - 98, - 520, - 185, - 165, - 72, - 95, - 380, - 275, - 88, - 62, - 310, - 155, - 92, - 88, - 125, - 142, - 48, - 195, - 225, - 245, - 168, - 85, - 155, - 45, - 68, - 175, - 52, - 285, - 78, - 425, - 265, - 35, - 295, - 112, - 145, - 320, - 42, - 135, - 38, - 195, - 485, - 95, - 28, - 255, - 215, - 58, - 165, - 25, - ], - "region": [ - "South", - "West", - "West", - "South", - "West", - "West", - "Northeast", - "South", - "South", - "South", - "South", - "West", - "West", - "Midwest", - "Midwest", - "Midwest", - "Midwest", - "South", - "South", - "Northeast", - "South", - "Northeast", - "Midwest", - "Midwest", - "South", - "Midwest", - "West", - "Midwest", - "West", - "Northeast", - "Northeast", - "West", - "Northeast", - "South", - "Midwest", - "Midwest", - "South", - "West", - "Northeast", - "Northeast", - "South", - "Midwest", - "South", - "South", - "West", - "Northeast", - "South", - "West", - "South", - "Midwest", - "West", - ], - } -) - -# City-level sales data (level 3 leaf nodes — CA, TX, NY, FL only) -cities_data = pd.DataFrame( - { - "state": [ - "California", - "California", - "California", - "California", - "California", - "Texas", - "Texas", - "Texas", - "Texas", - "New York", - "New York", - "New York", - "Florida", - "Florida", - "Florida", - ], - "city": [ - "Los Angeles", - "San Francisco", - "San Diego", - "San Jose", - "Sacramento", - "Houston", - "Dallas", - "Austin", - "San Antonio", - "New York City", - "Buffalo", - "Albany", - "Miami", - "Tampa", - "Orlando", - ], - "value": [182, 148, 96, 58, 36, 188, 148, 105, 44, 335, 62, 28, 182, 124, 74], - } -) - -# Region aggregation (level 1 → 2 rollup) -region_data = ( - states_data.groupby("region").agg(total_value=("value", "sum"), num_states=("state", "count")).reset_index() -) - -# Interactive selections — defaults produce a meaningful static PNG; clickable in HTML -region_select = alt.selection_point(fields=["region"], name="region_drill", value=[{"region": "West"}]) -state_select = alt.selection_point(fields=["state"], name="state_drill", value=[{"state": "California"}]) - -# Choropleth map — states colored by sales value using anyplot_seq colormap -states_map = alt.topo_feature(us_states_url, "states") - -choropleth = ( - alt.Chart(states_map) - .mark_geoshape(stroke=PAGE_BG, strokeWidth=0.8) - .encode( - color=alt.Color( - "value:Q", - scale=alt.Scale(range=["#009E73", "#003D94"], domain=[0, 550]), - legend=alt.Legend( - title="Sales ($K)", - titleFontSize=12, - labelFontSize=10, - orient="bottom-left", - gradientLength=130, - gradientThickness=12, - offset=8, - titleColor=INK, - labelColor=INK_SOFT, - ), - ), - tooltip=[ - alt.Tooltip("state:N", title="State"), - alt.Tooltip("region:N", title="Region"), - alt.Tooltip("value:Q", title="Sales ($K)", format=",.0f"), - ], - ) - .transform_lookup(lookup="id", from_=alt.LookupData(states_data, "id", ["state", "value", "region"])) - .project(type="albersUsa") - .properties(width=340, height=220) -) - -# Region bars — ① Country → Regions (click to drill down) -region_bars = ( - alt.Chart(region_data) - .mark_bar(cornerRadiusTopRight=4, cornerRadiusBottomRight=4) - .encode( - y=alt.Y("region:N", sort="-x", title=None), - x=alt.X("total_value:Q", title="Sales ($K)", axis=alt.Axis(grid=True, gridOpacity=0.10)), - color=alt.Color( - "region:N", - scale=alt.Scale(domain=REGIONS, range=IMPRINT), - legend=alt.Legend(title="Region", titleFontSize=10, labelFontSize=11, orient="right", direction="vertical"), - ), - opacity=alt.condition(region_select, alt.value(1.0), alt.value(0.55)), - tooltip=[ - alt.Tooltip("region:N", title="Region"), - alt.Tooltip("total_value:Q", title="Total Sales ($K)", format=",.0f"), - alt.Tooltip("num_states:Q", title="States"), - ], - ) - .add_params(region_select) - .properties(width=185, height=60, title=alt.Title("① USA → Regions", fontSize=11, color=INK)) -) - -# State bars — ② Region → States (filtered by selected region via transform_filter) -state_bars = ( - alt.Chart(states_data) - .mark_bar(cornerRadiusTopRight=4, cornerRadiusBottomRight=4) - .encode( - y=alt.Y("state:N", sort="-x", title=None), - x=alt.X("value:Q", title="Sales ($K)", axis=alt.Axis(grid=True, gridOpacity=0.10)), - color=alt.Color("region:N", scale=alt.Scale(domain=REGIONS, range=IMPRINT), legend=None), - opacity=alt.condition(state_select, alt.value(1.0), alt.value(0.55)), - tooltip=[ - alt.Tooltip("state:N", title="State"), - alt.Tooltip("region:N", title="Region"), - alt.Tooltip("value:Q", title="Sales ($K)", format=",.0f"), - ], - ) - .transform_filter(region_select) - .add_params(state_select) - .properties(width=185, height=75, title=alt.Title("② Region → States", fontSize=11, color=INK)) -) - -# City bars — ③ State → Cities (filtered by selected state; CA, TX, NY, FL have data) -city_bars = ( - alt.Chart(cities_data) - .mark_bar(cornerRadiusTopRight=4, cornerRadiusBottomRight=4, color=IMPRINT[0]) - .encode( - y=alt.Y("city:N", sort="-x", title=None), - x=alt.X("value:Q", title="Sales ($K)", axis=alt.Axis(grid=True, gridOpacity=0.10)), - tooltip=[alt.Tooltip("city:N", title="City"), alt.Tooltip("value:Q", title="Sales ($K)", format=",.0f")], - ) - .transform_filter(state_select) - .properties(width=185, height=45, title=alt.Title("③ State → Cities", fontSize=11, color=INK)) -) - -# Breadcrumb showing current drill-down path -breadcrumb_df = pd.DataFrame({"label": ["USA ▸ West ▸ California ▸ Cities"]}) -breadcrumb = ( - alt.Chart(breadcrumb_df) - .mark_text(fontSize=10, align="left", fontWeight="bold", color=INK_SOFT) - .encode(text="label:N", x=alt.value(2), y=alt.value(10)) - .properties(width=185, height=13) -) - -# Note: city drill-down available for CA, TX, NY, FL only -city_note_df = pd.DataFrame({"label": ["City data available for CA · TX · NY · FL only"]}) -city_note = ( - alt.Chart(city_note_df) - .mark_text(fontSize=9, align="left", fontStyle="italic", color=INK_SOFT) - .encode(text="label:N", x=alt.value(2), y=alt.value(10)) - .properties(width=185, height=10) -) - -# Sidebar: breadcrumb + all three hierarchy levels + note -sidebar = alt.vconcat(breadcrumb, region_bars, state_bars, city_bars, city_note, spacing=7).resolve_legend( - color="independent" -) - -# Full chart composition -chart = ( - alt.hconcat(choropleth, sidebar, spacing=22) - .properties( - background=PAGE_BG, - title=alt.Title( - "map-drilldown-geographic · python · altair · anyplot.ai", - fontSize=16, - anchor="middle", - color=INK, - subtitle=["Hierarchical US Sales: Country → Region → State → City (click to drill down in HTML)"], - subtitleFontSize=10, - subtitleColor=INK_SOFT, - ), - ) - .configure_view(fill=PAGE_BG, stroke=INK_SOFT) - .configure_axis( - domainColor=INK_SOFT, - tickColor=INK_SOFT, - gridColor=INK, - gridOpacity=0.10, - labelColor=INK_SOFT, - titleColor=INK, - labelFontSize=10, - titleFontSize=11, - ) - .configure_title(color=INK) - .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK) -) - -# Save PNG -chart.save(f"plot-{THEME}.png", scale_factor=4.0) - -# Pad-only to exact 3200×1800 canvas (do NOT crop — AR-09 auto-reject) -TW, TH = 3200, 1800 -_img = Image.open(f"plot-{THEME}.png").convert("RGB") -_w, _h = _img.size -if _w > TW or _h > TH: - raise SystemExit( - f"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. " - f"Shrink chart .properties(width=, height=) values and re-render." - ) -if _w < TW or _h < TH: - _canvas = Image.new("RGB", (TW, TH), PAGE_BG) - _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2)) - _canvas.save(f"plot-{THEME}.png") - -# Save HTML (interactive drill-down works here) -chart.save(f"plot-{THEME}.html") diff --git a/plots/map-drilldown-geographic/implementations/python/bokeh.py b/plots/map-drilldown-geographic/implementations/python/bokeh.py deleted file mode 100644 index 5238dabae7..0000000000 --- a/plots/map-drilldown-geographic/implementations/python/bokeh.py +++ /dev/null @@ -1,729 +0,0 @@ -""" anyplot.ai -map-drilldown-geographic: Drillable Geographic Map -Library: bokeh 3.9.0 | Python 3.13.13 -Quality: 88/100 | Updated: 2026-05-23 -""" - -import base64 -import os -import time -from pathlib import Path - -import numpy as np -from bokeh.io import output_file, save -from bokeh.layouts import column -from bokeh.models import ( - BooleanFilter, - Button, - CDSView, - ColorBar, - ColumnDataSource, - CustomJS, - HoverTool, - Label, - LinearColorMapper, - TapTool, - Title, -) -from bokeh.plotting import figure -from selenium import webdriver -from selenium.webdriver.chrome.options import Options - - -np.random.seed(42) - -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" -OCEAN_BG = "#D6EAF8" if THEME == "light" else "#1C2833" - - -# anyplot_seq: brand green (#009E73) → dark azure (#003D94) -ANYPLOT_SEQ256 = [ - "#{:02X}{:02X}{:02X}".format( - int(round(0x00 + (0x00 - 0x00) * t / 255)), - int(round(0x9E + (0x3D - 0x9E) * t / 255)), - int(round(0x73 + (0x94 - 0x73) * t / 255)), - ) - for t in range(256) -] - -# Data: Americas region, annual sales ($M) — hierarchical countries → states → cities - -country_xs = [ - [-125, -65, -65, -125], - [-140, -50, -50, -140], - [-118, -86, -86, -118], - [-74, -34, -34, -74], - [-73, -53, -53, -73], -] -country_ys = [[24, 24, 49, 49], [49, 49, 72, 72], [14, 14, 32, 32], [-34, -34, 5, 5], [-55, -55, -22, -22]] -country_names = ["United States", "Canada", "Mexico", "Brazil", "Argentina"] -country_values = [850, 320, 180, 420, 95] -country_cx = [-95, -95, -102, -54, -63] -country_cy = [38, 61, 24, -14, -37] -country_cy_val = [32, 55, 18, -20, -43] -country_labels = [f"${v}M" for v in country_values] - -us_xs = [ - [-124, -114, -114, -124], - [-106, -93, -93, -106], - [-80, -72, -72, -80], - [-87, -80, -80, -87], - [-91, -87, -87, -91], - [-124, -117, -117, -124], -] -us_ys = [[32, 32, 42, 42], [26, 26, 36, 36], [40, 40, 45, 45], [25, 25, 31, 31], [37, 37, 42, 42], [45, 45, 49, 49]] -us_names = ["California", "Texas", "New York", "Florida", "Illinois", "Washington"] -us_values = [220, 180, 150, 120, 90, 90] -us_cx = [-119, -99.5, -76, -83.5, -89, -120.5] -us_cy = [38.5, 32.5, 43.0, 29.5, 40.5, 47.5] -us_cy_val = [36.5, 30.5, 41.5, 27.5, 38.5, 46.0] -us_labels = [f"${v}M" for v in us_values] - -ca_xs = [ - [-123.3, -122.9, -122.9, -123.3], - [-123.5, -123.2, -123.2, -123.5], - [-122.9, -122.5, -122.5, -122.9], - [-123.0, -122.8, -122.8, -123.0], - [-119.6, -119.3, -119.3, -119.6], -] -ca_ys = [ - [33.5, 33.5, 34.5, 34.5], - [37.3, 37.3, 38.0, 38.0], - [32.4, 32.4, 33.2, 33.2], - [38.2, 38.2, 39.0, 39.0], - [37.0, 37.0, 37.6, 37.6], -] -ca_names = ["Los Angeles", "San Francisco", "San Diego", "Sacramento", "San Jose"] -ca_values = [85, 55, 35, 25, 20] -ca_cx = [-118.15, -122.4, -117.15, -121.4, -121.85] -ca_cy = [34.2, 37.85, 33.0, 38.8, 37.5] -ca_cy_val = [33.8, 37.45, 32.6, 38.4, 37.15] -ca_labels = [f"${v}M" for v in ca_values] - -cn_xs = [ - [-95, -74, -74, -95], - [-80, -57, -57, -80], - [-139, -120, -120, -139], - [-120, -110, -110, -120], - [-102, -95, -95, -102], -] -cn_ys = [[42, 42, 57, 57], [45, 45, 63, 63], [49, 49, 60, 60], [49, 49, 60, 60], [49, 49, 60, 60]] -cn_names = ["Ontario", "Quebec", "British Columbia", "Alberta", "Manitoba"] -cn_values = [120, 80, 65, 40, 15] -cn_cx = [-84.5, -68.5, -129.5, -115.0, -98.5] -cn_cy = [50.5, 55.0, 55.0, 55.0, 55.0] -cn_cy_val = [48.5, 53.0, 53.0, 53.0, 53.0] -cn_labels = [f"${v}M" for v in cn_values] - -bc_xs = [ - [-123.3, -122.9, -122.9, -123.3], - [-123.5, -123.2, -123.2, -123.5], - [-122.9, -122.5, -122.5, -122.9], - [-123.0, -122.8, -122.8, -123.0], - [-119.6, -119.3, -119.3, -119.6], -] -bc_ys = [ - [49.2, 49.2, 49.4, 49.4], - [48.4, 48.4, 48.6, 48.6], - [49.0, 49.0, 49.2, 49.2], - [49.2, 49.2, 49.3, 49.3], - [49.8, 49.8, 50.1, 50.1], -] -bc_names = ["Vancouver", "Victoria", "Surrey", "Burnaby", "Kelowna"] -bc_values = [30, 15, 10, 6, 4] -bc_cx = [-123.1, -123.35, -122.7, -122.9, -119.45] -bc_cy = [49.35, 48.55, 49.15, 49.27, 49.97] -bc_cy_val = [49.23, 48.43, 49.03, 49.22, 49.85] -bc_labels = [f"${v}M" for v in bc_values] - -# Color mapper: full data range across all levels -color_mapper = LinearColorMapper(palette=ANYPLOT_SEQ256, low=0, high=850) - -# Figure — canonical 3200×1800; btn_back is hidden initially (display:none) so adds no height -p = figure( - width=3200, - height=1800, - x_range=(-140, -25), - y_range=(-60, 75), - tools="pan,wheel_zoom,reset", - toolbar_location=None, - min_border_bottom=160, - min_border_left=180, - min_border_top=110, - min_border_right=280, -) - -p.title.text = "map-drilldown-geographic · python · bokeh · anyplot.ai" -p.title.text_font_size = "50pt" -p.title.align = "center" -p.title.text_color = INK - -breadcrumb_title = Title( - text="📍 World", - text_font_size="30pt", - text_color=INK, - align="left", - background_fill_color=ELEVATED_BG, - background_fill_alpha=0.9, - border_line_color=INK_SOFT, - border_line_width=1, - standoff=6, -) -p.add_layout(breadcrumb_title, "above") - -instruction_title = Title( - text="Click a shaded region to drill down • Use ← Back button to navigate up the hierarchy", - text_font_size="24pt", - text_color=INK_SOFT, - align="center", - standoff=8, -) -p.add_layout(instruction_title, "below") - -p.background_fill_color = OCEAN_BG -p.border_fill_color = PAGE_BG -p.outline_line_color = INK_SOFT -p.outline_line_width = 1 - -p.xaxis.axis_label = "Longitude" -p.yaxis.axis_label = "Latitude" -p.xaxis.axis_label_text_font_size = "42pt" -p.yaxis.axis_label_text_font_size = "42pt" -p.xaxis.major_label_text_font_size = "34pt" -p.yaxis.major_label_text_font_size = "34pt" -p.xaxis.axis_label_text_color = INK -p.yaxis.axis_label_text_color = INK -p.xaxis.major_label_text_color = INK_SOFT -p.yaxis.major_label_text_color = INK_SOFT -p.xaxis.axis_line_color = INK_SOFT -p.yaxis.axis_line_color = INK_SOFT -p.xaxis.major_tick_line_color = INK_SOFT -p.yaxis.major_tick_line_color = INK_SOFT -p.xgrid.grid_line_color = INK -p.ygrid.grid_line_color = INK -p.xgrid.grid_line_alpha = 0.10 -p.ygrid.grid_line_alpha = 0.10 - -# Country source shared between drillable and leaf renderers -country_src = ColumnDataSource( - data={ - "xs": country_xs, - "ys": country_ys, - "name": country_names, - "value": country_values, - "cx": country_cx, - "cy": country_cy, - "cy_val": country_cy_val, - "label_val": country_labels, - } -) - -# CDSViews split drillable (US, Canada) from leaf (Mexico, Brazil, Argentina) -drillable_view = CDSView(filter=BooleanFilter(booleans=[True, True, False, False, False])) -leaf_view = CDSView(filter=BooleanFilter(booleans=[False, False, True, True, True])) - -# Drillable countries: full opacity, solid border -country_drillable_r = p.patches( - xs="xs", - ys="ys", - source=country_src, - view=drillable_view, - fill_color={"field": "value", "transform": color_mapper}, - line_color=INK_SOFT, - line_width=4, - fill_alpha=0.85, - hover_fill_alpha=0.95, - hover_line_color=INK, - hover_line_width=5, - visible=True, -) - -# Leaf countries: reduced opacity + dashed border signals "no drill-down available" -country_leaf_r = p.patches( - xs="xs", - ys="ys", - source=country_src, - view=leaf_view, - fill_color={"field": "value", "transform": color_mapper}, - line_color=INK_SOFT, - line_width=3, - fill_alpha=0.45, - line_dash="dashed", - hover_fill_alpha=0.55, - hover_line_color=INK, - hover_line_width=4, - visible=True, -) - -country_names_r = p.text( - x="cx", - y="cy", - text="name", - source=country_src, - text_font_size="34pt", - text_align="center", - text_baseline="middle", - text_color=INK, - text_font_style="bold", - background_fill_color=ELEVATED_BG, - background_fill_alpha=0.88, - visible=True, -) -country_vals_r = p.text( - x="cx", - y="cy_val", - text="label_val", - source=country_src, - text_font_size="28pt", - text_align="center", - text_baseline="middle", - text_color=INK_SOFT, - background_fill_color=ELEVATED_BG, - background_fill_alpha=0.88, - visible=True, -) - -# Narrative annotation: guide viewer to the top-performing region -us_top_label = Label( - x=-123, - y=45, - text="★ United States leads — $850M", - text_font_size="26pt", - text_color="#009E73", - text_font_style="bold", - background_fill_color=ELEVATED_BG, - background_fill_alpha=0.92, - border_line_color="#009E73", - border_line_width=2, - visible=True, -) -p.add_layout(us_top_label) - -us_src = ColumnDataSource( - data={ - "xs": us_xs, - "ys": us_ys, - "name": us_names, - "value": us_values, - "cx": us_cx, - "cy": us_cy, - "cy_val": us_cy_val, - "label_val": us_labels, - } -) -us_patches_r = p.patches( - xs="xs", - ys="ys", - source=us_src, - fill_color={"field": "value", "transform": color_mapper}, - line_color=INK_SOFT, - line_width=3, - fill_alpha=0.85, - hover_fill_alpha=0.95, - hover_line_color=INK, - hover_line_width=4, - visible=False, -) -us_names_r = p.text( - x="cx", - y="cy", - text="name", - source=us_src, - text_font_size="28pt", - text_align="center", - text_baseline="middle", - text_color=INK, - text_font_style="bold", - background_fill_color=ELEVATED_BG, - background_fill_alpha=0.88, - visible=False, -) -us_vals_r = p.text( - x="cx", - y="cy_val", - text="label_val", - source=us_src, - text_font_size="22pt", - text_align="center", - text_baseline="middle", - text_color=INK_SOFT, - background_fill_color=ELEVATED_BG, - background_fill_alpha=0.88, - visible=False, -) - -ca_src = ColumnDataSource( - data={ - "xs": ca_xs, - "ys": ca_ys, - "name": ca_names, - "value": ca_values, - "cx": ca_cx, - "cy": ca_cy, - "cy_val": ca_cy_val, - "label_val": ca_labels, - } -) -ca_patches_r = p.patches( - xs="xs", - ys="ys", - source=ca_src, - fill_color={"field": "value", "transform": color_mapper}, - line_color=INK_SOFT, - line_width=2, - fill_alpha=0.85, - hover_fill_alpha=0.95, - hover_line_color=INK, - hover_line_width=3, - visible=False, -) -ca_names_r = p.text( - x="cx", - y="cy", - text="name", - source=ca_src, - text_font_size="22pt", - text_align="center", - text_baseline="middle", - text_color=INK, - text_font_style="bold", - background_fill_color=ELEVATED_BG, - background_fill_alpha=0.88, - visible=False, -) -ca_vals_r = p.text( - x="cx", - y="cy_val", - text="label_val", - source=ca_src, - text_font_size="18pt", - text_align="center", - text_baseline="middle", - text_color=INK_SOFT, - background_fill_color=ELEVATED_BG, - background_fill_alpha=0.88, - visible=False, -) - -cn_src = ColumnDataSource( - data={ - "xs": cn_xs, - "ys": cn_ys, - "name": cn_names, - "value": cn_values, - "cx": cn_cx, - "cy": cn_cy, - "cy_val": cn_cy_val, - "label_val": cn_labels, - } -) -cn_patches_r = p.patches( - xs="xs", - ys="ys", - source=cn_src, - fill_color={"field": "value", "transform": color_mapper}, - line_color=INK_SOFT, - line_width=3, - fill_alpha=0.85, - hover_fill_alpha=0.95, - hover_line_color=INK, - hover_line_width=4, - visible=False, -) -cn_names_r = p.text( - x="cx", - y="cy", - text="name", - source=cn_src, - text_font_size="28pt", - text_align="center", - text_baseline="middle", - text_color=INK, - text_font_style="bold", - background_fill_color=ELEVATED_BG, - background_fill_alpha=0.88, - visible=False, -) -cn_vals_r = p.text( - x="cx", - y="cy_val", - text="label_val", - source=cn_src, - text_font_size="22pt", - text_align="center", - text_baseline="middle", - text_color=INK_SOFT, - background_fill_color=ELEVATED_BG, - background_fill_alpha=0.88, - visible=False, -) - -bc_src = ColumnDataSource( - data={ - "xs": bc_xs, - "ys": bc_ys, - "name": bc_names, - "value": bc_values, - "cx": bc_cx, - "cy": bc_cy, - "cy_val": bc_cy_val, - "label_val": bc_labels, - } -) -bc_patches_r = p.patches( - xs="xs", - ys="ys", - source=bc_src, - fill_color={"field": "value", "transform": color_mapper}, - line_color=INK_SOFT, - line_width=2, - fill_alpha=0.85, - hover_fill_alpha=0.95, - hover_line_color=INK, - hover_line_width=3, - visible=False, -) -bc_names_r = p.text( - x="cx", - y="cy", - text="name", - source=bc_src, - text_font_size="22pt", - text_align="center", - text_baseline="middle", - text_color=INK, - text_font_style="bold", - background_fill_color=ELEVATED_BG, - background_fill_alpha=0.88, - visible=False, -) -bc_vals_r = p.text( - x="cx", - y="cy_val", - text="label_val", - source=bc_src, - text_font_size="18pt", - text_align="center", - text_baseline="middle", - text_color=INK_SOFT, - background_fill_color=ELEVATED_BG, - background_fill_alpha=0.88, - visible=False, -) - -color_bar = ColorBar( - color_mapper=color_mapper, - width=50, - height=600, - location=(0, 0), - title="Sales ($M)", - title_text_font_size="28pt", - title_text_font_style="bold", - title_text_color=INK, - major_label_text_font_size="22pt", - major_label_text_color=INK_SOFT, - title_standoff=20, - margin=30, - padding=20, - background_fill_color=PAGE_BG, -) -p.add_layout(color_bar, "right") - -hover = HoverTool( - renderers=[country_drillable_r, country_leaf_r, us_patches_r, ca_patches_r, cn_patches_r, bc_patches_r], - tooltips=[("Region", "@name"), ("Sales", "$@value{0}M")], -) -p.add_tools(hover) - -# Back navigation button — hidden initially (display:none adds no layout height for PNG capture) -btn_back = Button( - label="← World", - visible=False, - width=380, - height=55, - button_type="default", - styles={"margin-left": "180px", "margin-bottom": "6px", "font-size": "20px", "font-weight": "bold"}, -) - -tap_callback = CustomJS( - args={ - "p": p, - "country_src": country_src, - "country_drillable_r": country_drillable_r, - "country_leaf_r": country_leaf_r, - "country_names_r": country_names_r, - "country_vals_r": country_vals_r, - "us_patches_r": us_patches_r, - "us_names_r": us_names_r, - "us_vals_r": us_vals_r, - "ca_patches_r": ca_patches_r, - "ca_names_r": ca_names_r, - "ca_vals_r": ca_vals_r, - "cn_patches_r": cn_patches_r, - "cn_names_r": cn_names_r, - "cn_vals_r": cn_vals_r, - "bc_patches_r": bc_patches_r, - "bc_names_r": bc_names_r, - "bc_vals_r": bc_vals_r, - "breadcrumb": breadcrumb_title, - "btn_back": btn_back, - "us_top_label": us_top_label, - }, - code=""" -const show = (r) => { r.visible = true; }; -const hide = (r) => { r.visible = false; }; - -const sel_c = country_src.selected.indices; -const sel_us = us_patches_r.data_source.selected.indices; -const sel_ca = ca_patches_r.data_source.selected.indices; -const sel_cn = cn_patches_r.data_source.selected.indices; -const sel_bc = bc_patches_r.data_source.selected.indices; - -if (sel_c.length > 0 && country_drillable_r.visible) { - const idx = sel_c[0]; - const name = country_src.data['name'][idx]; - if (name === 'United States') { - [country_drillable_r, country_leaf_r, country_names_r, country_vals_r, us_top_label].forEach(hide); - [us_patches_r, us_names_r, us_vals_r].forEach(show); - p.x_range.start = -130; p.x_range.end = -65; - p.y_range.start = 20; p.y_range.end = 55; - breadcrumb.text = "📍 World › United States"; - btn_back.label = "← World"; - btn_back.visible = true; - } else if (name === 'Canada') { - [country_drillable_r, country_leaf_r, country_names_r, country_vals_r, us_top_label].forEach(hide); - [cn_patches_r, cn_names_r, cn_vals_r].forEach(show); - p.x_range.start = -145; p.x_range.end = -50; - p.y_range.start = 42; p.y_range.end = 75; - breadcrumb.text = "📍 World › Canada"; - btn_back.label = "← World"; - btn_back.visible = true; - } - // Leaf regions (Mexico, Brazil, Argentina): tap silently ignored — dashed border signals leaf - country_src.selected.indices = []; -} - -if (sel_us.length > 0 && us_patches_r.visible) { - const name = us_patches_r.data_source.data['name'][sel_us[0]]; - if (name === 'California') { - [us_patches_r, us_names_r, us_vals_r].forEach(hide); - [ca_patches_r, ca_names_r, ca_vals_r].forEach(show); - p.x_range.start = -125; p.x_range.end = -114; - p.y_range.start = 32; p.y_range.end = 42; - breadcrumb.text = "📍 World › United States › California"; - btn_back.label = "← United States"; - } - us_patches_r.data_source.selected.indices = []; -} - -if (sel_cn.length > 0 && cn_patches_r.visible) { - const name = cn_patches_r.data_source.data['name'][sel_cn[0]]; - if (name === 'British Columbia') { - [cn_patches_r, cn_names_r, cn_vals_r].forEach(hide); - [bc_patches_r, bc_names_r, bc_vals_r].forEach(show); - p.x_range.start = -140; p.x_range.end = -118; - p.y_range.start = 47; p.y_range.end = 62; - breadcrumb.text = "📍 World › Canada › British Columbia"; - btn_back.label = "← Canada"; - } - cn_patches_r.data_source.selected.indices = []; -} - -if (sel_ca.length > 0) { ca_patches_r.data_source.selected.indices = []; } -if (sel_bc.length > 0) { bc_patches_r.data_source.selected.indices = []; } -""", -) - -back_callback = CustomJS( - args={ - "p": p, - "country_drillable_r": country_drillable_r, - "country_leaf_r": country_leaf_r, - "country_names_r": country_names_r, - "country_vals_r": country_vals_r, - "us_patches_r": us_patches_r, - "us_names_r": us_names_r, - "us_vals_r": us_vals_r, - "ca_patches_r": ca_patches_r, - "ca_names_r": ca_names_r, - "ca_vals_r": ca_vals_r, - "cn_patches_r": cn_patches_r, - "cn_names_r": cn_names_r, - "cn_vals_r": cn_vals_r, - "bc_patches_r": bc_patches_r, - "bc_names_r": bc_names_r, - "bc_vals_r": bc_vals_r, - "breadcrumb": breadcrumb_title, - "btn_back": btn_back, - "us_top_label": us_top_label, - }, - code=""" -const show = (r) => { r.visible = true; }; -const hide = (r) => { r.visible = false; }; -const lbl = btn_back.label; - -if (lbl === "← World") { - [us_patches_r, us_names_r, us_vals_r, - cn_patches_r, cn_names_r, cn_vals_r].forEach(hide); - [country_drillable_r, country_leaf_r, country_names_r, country_vals_r, us_top_label].forEach(show); - p.x_range.start = -140; p.x_range.end = -25; - p.y_range.start = -60; p.y_range.end = 75; - breadcrumb.text = "📍 World"; - btn_back.visible = false; -} else if (lbl === "← United States") { - [ca_patches_r, ca_names_r, ca_vals_r].forEach(hide); - [us_patches_r, us_names_r, us_vals_r].forEach(show); - p.x_range.start = -130; p.x_range.end = -65; - p.y_range.start = 20; p.y_range.end = 55; - breadcrumb.text = "📍 World › United States"; - btn_back.label = "← World"; -} else if (lbl === "← Canada") { - [bc_patches_r, bc_names_r, bc_vals_r].forEach(hide); - [cn_patches_r, cn_names_r, cn_vals_r].forEach(show); - p.x_range.start = -145; p.x_range.end = -50; - p.y_range.start = 42; p.y_range.end = 75; - breadcrumb.text = "📍 World › Canada"; - btn_back.label = "← World"; -} -""", -) -btn_back.js_on_click(back_callback) - -tap_tool = TapTool( - callback=tap_callback, - renderers=[country_drillable_r, country_leaf_r, us_patches_r, ca_patches_r, cn_patches_r, bc_patches_r], -) -p.add_tools(tap_tool) - -# Layout: back button above plot; when hidden (initial state) it has display:none so adds no height -layout = column(btn_back, p, spacing=0) - -output_file(f"plot-{THEME}.html") -save(layout) - -W, H = 3200, 1800 -opts = Options() -for arg in ( - "--headless=new", - "--no-sandbox", - "--disable-dev-shm-usage", - "--disable-gpu", - f"--window-size={W},{H + 200}", - "--hide-scrollbars", -): - opts.add_argument(arg) -driver = webdriver.Chrome(options=opts) -driver.get(f"file://{Path(f'plot-{THEME}.html').resolve()}") -time.sleep(3) -screenshot = driver.execute_cdp_cmd( - "Page.captureScreenshot", - {"format": "png", "captureBeyondViewport": True, "clip": {"x": 0, "y": 0, "width": W, "height": H, "scale": 1}}, -) -driver.quit() -img_bytes = base64.b64decode(screenshot["data"]) -with open(f"plot-{THEME}.png", "wb") as f: - f.write(img_bytes) diff --git a/plots/map-drilldown-geographic/implementations/python/letsplot.py b/plots/map-drilldown-geographic/implementations/python/letsplot.py deleted file mode 100644 index 3b1cd7f736..0000000000 --- a/plots/map-drilldown-geographic/implementations/python/letsplot.py +++ /dev/null @@ -1,423 +0,0 @@ -""" anyplot.ai -map-drilldown-geographic: Drillable Geographic Map -Library: letsplot 4.10.1 | Python 3.13.13 -Quality: 81/100 | Updated: 2026-05-23 -""" - -import os - -import numpy as np -import pandas as pd -from lets_plot import ( - LetsPlot, - aes, - coord_fixed, - element_rect, - element_text, - geom_point, - geom_polygon, - geom_text, - gggrid, - ggplot, - labs, - layer_tooltips, - scale_fill_gradient, - scale_size, - theme, - theme_void, -) -from lets_plot.export import ggsave - - -LetsPlot.setup_html() - -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" -CONTINENT_FILL = "#E0DDD6" if THEME == "light" else "#2E2E2A" -CONTINENT_BORDER = "#C4C0B8" if THEME == "light" else "#4A4A44" -STROKE_DRILLABLE = "#C475FD" - -np.random.seed(42) - -# ── Level 1: Country bubbles (16 countries across all continents) ───────────── -countries = [ - {"id": "usa", "name": "USA", "value": 4850, "lat": 39.8, "lon": -98.5, "drillable": True}, - {"id": "canada", "name": "Canada", "value": 1420, "lat": 56.1, "lon": -106.3, "drillable": True}, - {"id": "mexico", "name": "Mexico", "value": 980, "lat": 23.6, "lon": -102.5, "drillable": False}, - {"id": "brazil", "name": "Brazil", "value": 1650, "lat": -14.2, "lon": -51.9, "drillable": False}, - {"id": "argentina", "name": "Argentina", "value": 720, "lat": -34.6, "lon": -64.2, "drillable": False}, - {"id": "uk", "name": "UK", "value": 2100, "lat": 55.0, "lon": -3.4, "drillable": False}, - {"id": "germany", "name": "Germany", "value": 2350, "lat": 51.2, "lon": 10.5, "drillable": False}, - {"id": "france", "name": "France", "value": 1890, "lat": 46.2, "lon": 2.2, "drillable": False}, - {"id": "italy", "name": "Italy", "value": 1230, "lat": 42.5, "lon": 14.0, "drillable": False}, - {"id": "spain", "name": "Spain", "value": 1050, "lat": 40.4, "lon": -3.7, "drillable": False}, - {"id": "china", "name": "China", "value": 3600, "lat": 35.9, "lon": 104.2, "drillable": False}, - {"id": "japan", "name": "Japan", "value": 2800, "lat": 36.2, "lon": 138.3, "drillable": False}, - {"id": "india", "name": "India", "value": 1950, "lat": 20.6, "lon": 78.9, "drillable": False}, - {"id": "southafrica", "name": "S. Africa", "value": 890, "lat": -30.6, "lon": 25.1, "drillable": False}, - {"id": "nigeria", "name": "Nigeria", "value": 640, "lat": 9.1, "lon": 8.7, "drillable": False}, - {"id": "australia", "name": "Australia", "value": 1280, "lat": -25.3, "lon": 133.8, "drillable": False}, -] - -# ── Level 2: US state drill-down ────────────────────────────────────────────── -us_states = [ - {"name": "California", "abbr": "CA", "value": 920, "lat": 36.7, "lon": -119.4}, - {"name": "Texas", "abbr": "TX", "value": 810, "lat": 31.0, "lon": -99.9}, - {"name": "New York", "abbr": "NY", "value": 680, "lat": 43.0, "lon": -75.5}, - {"name": "Florida", "abbr": "FL", "value": 560, "lat": 27.7, "lon": -81.5}, - {"name": "Illinois", "abbr": "IL", "value": 430, "lat": 40.0, "lon": -89.2}, - {"name": "Washington", "abbr": "WA", "value": 380, "lat": 47.5, "lon": -120.5}, - {"name": "Georgia", "abbr": "GA", "value": 320, "lat": 32.7, "lon": -83.4}, - {"name": "Colorado", "abbr": "CO", "value": 290, "lat": 39.1, "lon": -105.4}, - {"name": "Ohio", "abbr": "OH", "value": 270, "lat": 40.4, "lon": -82.8}, - {"name": "Arizona", "abbr": "AZ", "value": 210, "lat": 34.3, "lon": -111.6}, -] - -# ── Continent outlines ──────────────────────────────────────────────────────── -continent_rows = [] - -na_coords = [ - (-170, 66), - (-140, 60), - (-125, 48), - (-118, 34), - (-105, 25), - (-97, 26), - (-82, 23), - (-80, 25), - (-83, 30), - (-92, 29), - (-97, 28), - (-87, 15), - (-78, 9), - (-62, 10), - (-60, 14), - (-67, 18), - (-72, 21), - (-80, 25), - (-76, 35), - (-72, 41), - (-67, 45), - (-60, 47), - (-55, 50), - (-63, 58), - (-85, 65), - (-110, 70), - (-145, 68), - (-165, 65), - (-170, 66), -] -sa_coords = [ - (-80, 10), - (-62, 10), - (-52, 4), - (-48, -2), - (-35, -8), - (-35, -18), - (-43, -23), - (-52, -33), - (-65, -42), - (-72, -52), - (-64, -55), - (-65, -45), - (-72, -30), - (-75, -15), - (-81, -5), - (-78, 2), - (-80, 10), -] -europe_coords = [ - (-10, 36), - (-8, 43), - (0, 43), - (3, 42), - (13, 45), - (14, 42), - (25, 37), - (40, 41), - (50, 45), - (60, 55), - (50, 60), - (60, 70), - (25, 71), - (15, 68), - (5, 62), - (10, 55), - (8, 52), - (-5, 50), - (-10, 44), - (-10, 36), -] -asia_coords = [ - (26, 41), - (40, 41), - (50, 45), - (60, 55), - (80, 72), - (100, 70), - (130, 60), - (140, 55), - (145, 45), - (140, 40), - (145, 35), - (155, 20), - (145, 10), - (130, 0), - (125, -8), - (120, -10), - (105, -5), - (100, 2), - (90, 12), - (80, 10), - (65, 25), - (57, 22), - (50, 30), - (40, 36), - (28, 41), - (26, 41), -] -africa_coords = [ - (-10, 36), - (0, 37), - (10, 37), - (25, 37), - (35, 35), - (42, 20), - (50, 12), - (50, 5), - (42, -12), - (38, -20), - (35, -24), - (26, -34), - (20, -35), - (18, -35), - (12, -30), - (10, -22), - (8, -12), - (5, -4), - (2, 0), - (-8, 5), - (-10, 8), - (-15, 12), - (-18, 16), - (-18, 22), - (-10, 36), -] -aus_coords = [ - (115, -20), - (128, -15), - (137, -16), - (142, -11), - (145, -15), - (153, -25), - (153, -30), - (151, -34), - (147, -38), - (138, -35), - (125, -35), - (114, -32), - (114, -26), - (115, -20), -] - -for coords, cname in [ - (na_coords, "North America"), - (sa_coords, "South America"), - (europe_coords, "Europe"), - (asia_coords, "Asia"), - (africa_coords, "Africa"), - (aus_coords, "Australia"), -]: - for lon, lat in coords: - continent_rows.append({"lon": lon, "lat": lat, "continent": cname}) - -continents = pd.DataFrame(continent_rows) - -# ── USA outline for drilldown panel ────────────────────────────────────────── -usa_outline_coords = [ - (-124, 49), - (-117, 49), - (-109, 49), - (-97, 49), - (-88, 48), - (-83, 46), - (-80, 43), - (-75, 45), - (-68, 47), - (-67, 45), - (-70, 42), - (-72, 41), - (-75, 38), - (-76, 34), - (-80, 30), - (-80, 25), - (-82, 24), - (-85, 29), - (-88, 30), - (-89, 29), - (-97, 26), - (-105, 25), - (-117, 32), - (-118, 34), - (-124, 40), - (-124, 46), - (-124, 49), -] -usa_outline = pd.DataFrame([{"lon": lon, "lat": lat, "region": "USA"} for lon, lat in usa_outline_coords]) - -# ── Label offsets for world map ─────────────────────────────────────────────── -label_offsets = { - "usa": (0, -9), - "canada": (22, 3), - "mexico": (0, -8), - "brazil": (0, -10), - "argentina": (0, -9), - "uk": (-18, 8), - "germany": (18, 7), - "france": (-18, -9), - "italy": (12, -9), - "spain": (-15, -10), - "china": (0, -9), - "japan": (12, 5), - "india": (0, -9), - "southafrica": (0, -9), - "nigeria": (-18, 5), - "australia": (-35, 0), -} - -# ── Prepare DataFrames ──────────────────────────────────────────────────────── -records = [] -for c in countries: - nx, ny = label_offsets.get(c["id"], (0, -8)) - records.append( - { - "name": c["name"], - "lon": c["lon"], - "lat": c["lat"], - "value": c["value"], - "drillable": c["drillable"], - "label_lon": c["lon"] + nx, - "label_lat": c["lat"] + ny, - "sales": f"${c['value'] / 1000:.2f}B", - "type": "▼ Drillable" if c["drillable"] else "Leaf node", - } - ) -df = pd.DataFrame(records) -df_drill = df[df["drillable"]] -df_leaf = df[~df["drillable"]] - -df_states = pd.DataFrame(us_states) -df_states["sales"] = df_states["value"].apply(lambda v: f"${v}M") - -# ── World map — Level 1 ─────────────────────────────────────────────────────── -world_plot = ( - ggplot() - + geom_polygon( - data=continents, - mapping=aes(x="lon", y="lat", group="continent"), - fill=CONTINENT_FILL, - color=CONTINENT_BORDER, - alpha=0.7, - size=0.4, - tooltips="none", - ) - + geom_point( - data=df_leaf, - mapping=aes(x="lon", y="lat", size="value", fill="value"), - color=INK_SOFT, - alpha=0.85, - shape=21, - stroke=1.5, - tooltips=layer_tooltips().title("@name").line("Sales|@sales").line("@type"), - ) - # Purple stroke flags drillable countries - + geom_point( - data=df_drill, - mapping=aes(x="lon", y="lat", size="value", fill="value"), - color=STROKE_DRILLABLE, - alpha=0.85, - shape=21, - stroke=2.5, - tooltips=layer_tooltips().title("@name").line("Sales|@sales").line("@type"), - ) - + geom_text( - data=df, - mapping=aes(x="label_lon", y="label_lat", label="name"), - size=11, - color=INK, - fontface="bold", - tooltips="none", - ) - + scale_size(range=[5, 18], guide="none") - + scale_fill_gradient(low="#009E73", high="#003D94", name="Sales (M $)") - + coord_fixed(ratio=1.3, xlim=[-180, 200], ylim=[-60, 75]) - + labs( - title="map-drilldown-geographic · python · letsplot · anyplot.ai", - subtitle="World → Country Level · Purple border = click to drill into states", - ) - + theme_void() - + theme( - plot_title=element_text(size=16, face="bold", hjust=0.5, color=INK), - plot_subtitle=element_text(size=12, hjust=0.5, color=INK_SOFT), - legend_title=element_text(size=11, color=INK), - legend_text=element_text(size=10, color=INK_SOFT), - legend_position=[0.92, 0.18], - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT), - ) -) - -# ── US States — Level 2 drill-down ──────────────────────────────────────────── -us_plot = ( - ggplot() - + geom_polygon( - data=usa_outline, - mapping=aes(x="lon", y="lat", group="region"), - fill=CONTINENT_FILL, - color=CONTINENT_BORDER, - alpha=0.7, - size=0.4, - tooltips="none", - ) - + geom_point( - data=df_states, - mapping=aes(x="lon", y="lat", size="value", fill="value"), - color=INK_SOFT, - alpha=0.85, - shape=21, - stroke=1.5, - tooltips=layer_tooltips().title("@name").line("Sales|@sales"), - ) - + geom_text( - data=df_states, - mapping=aes(x="lon", y="lat", label="abbr"), - size=10, - color=INK, - fontface="bold", - nudge_y=-2.5, - tooltips="none", - ) - + scale_size(range=[5, 16], guide="none") - + scale_fill_gradient(low="#009E73", high="#003D94", name="Sales (M $)") - + coord_fixed(ratio=1.3, xlim=[-128, -65], ylim=[23, 52]) - + labs(title="USA → State Level (drill-down)", subtitle="Breadcrumb: World > United States") - + theme_void() - + theme( - plot_title=element_text(size=13, face="bold", hjust=0.5, color=INK), - plot_subtitle=element_text(size=11, hjust=0.5, color=INK_SOFT), - legend_title=element_text(size=10, color=INK), - legend_text=element_text(size=9, color=INK_SOFT), - legend_position=[0.85, 0.15], - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT), - ) -) - -# ── Combine panels and save ─────────────────────────────────────────────────── -grid = gggrid([world_plot, us_plot], ncol=2, widths=[2, 1]) - -ggsave(grid, f"plot-{THEME}.png", path=".", w=3200, h=1800, unit="px") -ggsave(grid, f"plot-{THEME}.html", path=".", w=800, h=450, unit="px") diff --git a/plots/map-drilldown-geographic/implementations/python/matplotlib.py b/plots/map-drilldown-geographic/implementations/python/matplotlib.py deleted file mode 100644 index d049d70484..0000000000 --- a/plots/map-drilldown-geographic/implementations/python/matplotlib.py +++ /dev/null @@ -1,354 +0,0 @@ -""" anyplot.ai -map-drilldown-geographic: Drillable Geographic Map -Library: matplotlib 3.10.9 | Python 3.13.13 -Quality: 83/100 | Updated: 2026-05-23 -""" - -import os - -import matplotlib.patches as mpatches -import matplotlib.pyplot as plt -from matplotlib.collections import PatchCollection -from matplotlib.colors import LinearSegmentedColormap, Normalize - - -# Theme tokens -THEME = os.getenv("ANYPLOT_THEME", "light") -PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" -INK = "#1A1A17" if THEME == "light" else "#F0EFE8" -INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" -BOX_TEXT = "#F0EFE8" # light text on dark anyplot_seq boxes (good contrast on both ends) -HIGHLIGHT = "#C475FD" # Imprint palette position 2 — selected-region indicator - -# anyplot sequential colormap: green → dark azure (single-polarity continuous) -anyplot_seq = LinearSegmentedColormap.from_list("anyplot_seq", ["#009E73", "#003D94"]) - -# Data: Three-level hierarchy — Annual Sales Drill-down (World → USA → California) -world_regions = {"North America": 245, "South America": 68, "Europe": 189, "Africa": 42, "Asia": 312, "Oceania": 34} -world_labels = { - "North America": "N.AM", - "South America": "S.AM", - "Europe": "EUR", - "Africa": "AFR", - "Asia": "ASIA", - "Oceania": "OCE", -} -world_values_text = { - "North America": "$245B", - "South America": "$68B", - "Europe": "$189B", - "Africa": "$42B", - "Asia": "$312B", - "Oceania": "$34B", -} -world_positions = { - "North America": (1, 2), - "South America": (1, 0), - "Europe": (2, 2), - "Africa": (2, 1), - "Asia": (3, 2), - "Oceania": (3, 0), -} - -us_states = { - "California": 42500, - "Texas": 31200, - "New York": 28900, - "Florida": 19800, - "Illinois": 16500, - "Pennsylvania": 14200, - "Ohio": 12800, - "Georgia": 11500, - "Michigan": 10200, - "Washington": 9800, -} -state_labels = { - "California": "CA", - "Texas": "TX", - "New York": "NY", - "Florida": "FL", - "Illinois": "IL", - "Pennsylvania": "PA", - "Ohio": "OH", - "Georgia": "GA", - "Michigan": "MI", - "Washington": "WA", -} -state_values_text = { - "California": "$42.5M", - "Texas": "$31.2M", - "New York": "$28.9M", - "Florida": "$19.8M", - "Illinois": "$16.5M", - "Pennsylvania": "$14.2M", - "Ohio": "$12.8M", - "Georgia": "$11.5M", - "Michigan": "$10.2M", - "Washington": "$9.8M", -} -state_positions = { - "California": (0, 2), - "Washington": (0, 3), - "Texas": (1, 1), - "Florida": (2, 0), - "Illinois": (1, 2), - "New York": (2, 3), - "Pennsylvania": (2, 2), - "Ohio": (1, 3), - "Georgia": (2, 1), - "Michigan": (0, 4), -} - -ca_cities = { - "Los Angeles": 12800, - "San Francisco": 8600, - "San Diego": 5200, - "San Jose": 4800, - "Sacramento": 2400, - "Oakland": 2100, - "Fresno": 1800, - "Long Beach": 1600, - "Irvine": 1400, - "Santa Monica": 1200, -} -city_labels = { - "Los Angeles": "LA", - "San Francisco": "SF", - "San Diego": "SD", - "San Jose": "SJ", - "Sacramento": "SAC", - "Oakland": "OAK", - "Fresno": "FRE", - "Long Beach": "LB", - "Irvine": "IRV", - "Santa Monica": "SM", -} -city_values_text = { - "Los Angeles": "$12.8M", - "San Francisco": "$8.6M", - "San Diego": "$5.2M", - "San Jose": "$4.8M", - "Sacramento": "$2.4M", - "Oakland": "$2.1M", - "Fresno": "$1.8M", - "Long Beach": "$1.6M", - "Irvine": "$1.4M", - "Santa Monica": "$1.2M", -} -city_positions = { - "Los Angeles": (1, 1), - "San Francisco": (0, 3), - "San Diego": (1, 0), - "San Jose": (0, 2), - "Sacramento": (1, 3), - "Oakland": (0, 4), - "Fresno": (1, 2), - "Long Beach": (2, 1), - "Irvine": (2, 0), - "Santa Monica": (0, 1), -} - -# Global color normalization: all values converted to $M for consistent cross-level scale -world_regions_m = {k: v * 1000 for k, v in world_regions.items()} -all_values_m = list(world_regions_m.values()) + list(us_states.values()) + list(ca_cities.values()) -global_vmin = min(all_values_m) -global_vmax = max(all_values_m) -global_norm = Normalize(vmin=global_vmin, vmax=global_vmax) - -# Figure — 3200 × 1800 px (landscape 16:9) -fig = plt.figure(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG) -gs = fig.add_gridspec(1, 3, left=0.04, right=0.88, top=0.84, bottom=0.14, wspace=0.20) -axes = [fig.add_subplot(gs[0, i]) for i in range(3)] -for ax in axes: - ax.set_facecolor(PAGE_BG) - -# ---- Panel 1: World Regions ---- -patches1, colors1 = [], [] -for name in world_positions: - col, row = world_positions[name] - patches1.append( - mpatches.FancyBboxPatch( - (col * 1.2, row * 1.2), - 1.0, - 1.0, - boxstyle="round,pad=0.02,rounding_size=0.1", - linewidth=1.5, - edgecolor=PAGE_BG, - ) - ) - colors1.append(anyplot_seq(global_norm(world_regions_m[name]))) - -axes[0].add_collection(PatchCollection(patches1, facecolors=colors1, edgecolors=PAGE_BG, linewidths=1.5)) - -for name in world_positions: - col, row = world_positions[name] - cx, cy = col * 1.2 + 0.5, row * 1.2 + 0.5 - axes[0].text( - cx, cy + 0.12, world_labels[name], ha="center", va="center", fontsize=7, fontweight="bold", color=BOX_TEXT - ) - axes[0].text(cx, cy - 0.14, world_values_text[name], ha="center", va="center", fontsize=6, color=BOX_TEXT) - -hcol, hrow = world_positions["North America"] -axes[0].add_patch( - mpatches.FancyBboxPatch( - (hcol * 1.2 - 0.06, hrow * 1.2 - 0.06), - 1.12, - 1.12, - boxstyle="round,pad=0.02,rounding_size=0.13", - linewidth=3.5, - edgecolor=HIGHLIGHT, - facecolor="none", - zorder=10, - ) -) - -axes[0].set_xlim(0.9, 4.9) -axes[0].set_ylim(-0.3, 3.8) -axes[0].set_aspect("equal") -axes[0].axis("off") -axes[0].set_title("Level 1: World Regions", fontsize=9, fontweight="bold", color=INK, pad=6) -axes[0].text(0.5, -0.11, "World", transform=axes[0].transAxes, ha="center", fontsize=7, color=INK_MUTED, style="italic") - -# ---- Panel 2: US States ---- -patches2, colors2 = [], [] -for name in state_positions: - col, row = state_positions[name] - patches2.append( - mpatches.FancyBboxPatch( - (col * 1.2, row * 1.2), - 1.0, - 1.0, - boxstyle="round,pad=0.02,rounding_size=0.1", - linewidth=1.5, - edgecolor=PAGE_BG, - ) - ) - colors2.append(anyplot_seq(global_norm(us_states[name]))) - -axes[1].add_collection(PatchCollection(patches2, facecolors=colors2, edgecolors=PAGE_BG, linewidths=1.5)) - -for name in state_positions: - col, row = state_positions[name] - cx, cy = col * 1.2 + 0.5, row * 1.2 + 0.5 - axes[1].text( - cx, cy + 0.12, state_labels[name], ha="center", va="center", fontsize=7, fontweight="bold", color=BOX_TEXT - ) - axes[1].text(cx, cy - 0.14, state_values_text[name], ha="center", va="center", fontsize=6, color=BOX_TEXT) - -hcol2, hrow2 = state_positions["California"] -axes[1].add_patch( - mpatches.FancyBboxPatch( - (hcol2 * 1.2 - 0.06, hrow2 * 1.2 - 0.06), - 1.12, - 1.12, - boxstyle="round,pad=0.02,rounding_size=0.13", - linewidth=3.5, - edgecolor=HIGHLIGHT, - facecolor="none", - zorder=10, - ) -) - -axes[1].set_xlim(-0.5, 4.0) -axes[1].set_ylim(-0.8, 6.2) -axes[1].set_aspect("equal") -axes[1].axis("off") -axes[1].set_title("Level 2: US States", fontsize=9, fontweight="bold", color=INK, pad=6) -axes[1].text( - 0.5, - -0.11, - "World ▶ United States", - transform=axes[1].transAxes, - ha="center", - fontsize=7, - color=INK_MUTED, - style="italic", -) - -# ---- Panel 3: California Cities ---- -patches3, colors3 = [], [] -for name in city_positions: - col, row = city_positions[name] - patches3.append( - mpatches.FancyBboxPatch( - (col * 1.2, row * 1.2), - 1.0, - 1.0, - boxstyle="round,pad=0.02,rounding_size=0.1", - linewidth=1.5, - edgecolor=PAGE_BG, - ) - ) - colors3.append(anyplot_seq(global_norm(ca_cities[name]))) - -axes[2].add_collection(PatchCollection(patches3, facecolors=colors3, edgecolors=PAGE_BG, linewidths=1.5)) - -for name in city_positions: - col, row = city_positions[name] - cx, cy = col * 1.2 + 0.5, row * 1.2 + 0.5 - axes[2].text( - cx, cy + 0.12, city_labels[name], ha="center", va="center", fontsize=7, fontweight="bold", color=BOX_TEXT - ) - axes[2].text(cx, cy - 0.14, city_values_text[name], ha="center", va="center", fontsize=6, color=BOX_TEXT) - -axes[2].set_xlim(-0.5, 4.0) -axes[2].set_ylim(-0.8, 6.2) -axes[2].set_aspect("equal") -axes[2].axis("off") -axes[2].set_title("Level 3: CA Cities", fontsize=9, fontweight="bold", color=INK, pad=6) -axes[2].text( - 0.5, - -0.11, - "World ▶ United States ▶ California", - transform=axes[2].transAxes, - ha="center", - fontsize=7, - color=INK_MUTED, - style="italic", -) - -# Colorbar — consistent scale across all three hierarchy levels -sm = plt.cm.ScalarMappable(cmap=anyplot_seq, norm=global_norm) -sm.set_array([]) -cbar_ax = fig.add_axes([0.905, 0.18, 0.020, 0.52]) -cbar = fig.colorbar(sm, cax=cbar_ax) -cbar.set_label("Annual Sales", fontsize=7, color=INK_SOFT, labelpad=3) -cbar.ax.tick_params(labelsize=6, colors=INK_SOFT, length=2) -cbar.set_ticks([global_vmin, (global_vmin + global_vmax) / 2, global_vmax]) -cbar.set_ticklabels(["$1.2M", "$157B", "$312B"]) -cbar.outline.set_edgecolor(INK_SOFT) -cbar.outline.set_linewidth(0.5) - -# Drill-down arrows between panels -for i in range(2): - x_start = axes[i].get_position().x1 + 0.005 - x_end = axes[i + 1].get_position().x0 - 0.005 - y_mid = 0.46 - fig.patches.append( - mpatches.FancyArrowPatch( - (x_start, y_mid), - (x_end, y_mid), - transform=fig.transFigure, - arrowstyle="-|>", - color=HIGHLIGHT, - lw=2, - mutation_scale=14, - zorder=100, - ) - ) - -# Title and subtitle -fig.suptitle( - "map-drilldown-geographic · python · matplotlib · anyplot.ai", fontsize=12, fontweight="medium", color=INK, y=0.97 -) -fig.text( - 0.46, - 0.90, - "Annual Sales Drill-down: World → North America → California ▸ = selected for next level", - ha="center", - fontsize=8, - color=INK_SOFT, -) - -plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG) diff --git a/plots/map-drilldown-geographic/implementations/python/plotly.py b/plots/map-drilldown-geographic/implementations/python/plotly.py deleted file mode 100644 index a85b686a2b..0000000000 --- a/plots/map-drilldown-geographic/implementations/python/plotly.py +++ /dev/null @@ -1,359 +0,0 @@ -""" anyplot.ai -map-drilldown-geographic: Drillable Geographic Map -Library: plotly 6.7.0 | Python 3.13.13 -Quality: 88/100 | Updated: 2026-05-23 -""" - -import os - -import numpy as np -import plotly.graph_objects as go - - -# 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" - -ANYPLOT_SEQ = [[0.0, "#009E73"], [1.0, "#003D94"]] - -# Geo background colors per theme -LAND_COLOR = "rgb(228, 224, 210)" if THEME == "light" else "rgb(40, 38, 33)" -LAKE_COLOR = "rgb(197, 220, 245)" if THEME == "light" else "rgb(20, 40, 65)" -OCEAN_COLOR = "rgb(210, 228, 248)" if THEME == "light" else "rgb(15, 30, 55)" -BORDER_COLOR = "rgb(180, 175, 160)" if THEME == "light" else "rgb(70, 68, 60)" - -# Data — US states with cities, sales values ($K) -np.random.seed(42) - -us_states_data = { - "California": { - "abbrev": "CA", - "cities": ["Los Angeles", "San Francisco", "San Diego", "San Jose", "Sacramento"], - "city_values": [850, 720, 480, 520, 280], - }, - "Texas": { - "abbrev": "TX", - "cities": ["Houston", "Dallas", "Austin", "San Antonio", "Fort Worth"], - "city_values": [780, 690, 580, 420, 320], - }, - "Florida": { - "abbrev": "FL", - "cities": ["Miami", "Orlando", "Tampa", "Jacksonville", "Fort Lauderdale"], - "city_values": [620, 510, 450, 380, 290], - }, - "New York": { - "abbrev": "NY", - "cities": ["New York City", "Buffalo", "Rochester", "Albany", "Syracuse"], - "city_values": [950, 280, 250, 220, 180], - }, - "Illinois": { - "abbrev": "IL", - "cities": ["Chicago", "Aurora", "Naperville", "Rockford", "Joliet"], - "city_values": [720, 180, 165, 145, 130], - }, - "Pennsylvania": { - "abbrev": "PA", - "cities": ["Philadelphia", "Pittsburgh", "Allentown", "Erie", "Reading"], - "city_values": [580, 420, 180, 150, 140], - }, - "Ohio": { - "abbrev": "OH", - "cities": ["Columbus", "Cleveland", "Cincinnati", "Toledo", "Akron"], - "city_values": [450, 380, 360, 220, 190], - }, - "Georgia": { - "abbrev": "GA", - "cities": ["Atlanta", "Augusta", "Savannah", "Columbus", "Athens"], - "city_values": [680, 220, 200, 180, 150], - }, - "North Carolina": { - "abbrev": "NC", - "cities": ["Charlotte", "Raleigh", "Greensboro", "Durham", "Wilmington"], - "city_values": [520, 450, 280, 260, 180], - }, - "Michigan": { - "abbrev": "MI", - "cities": ["Detroit", "Grand Rapids", "Ann Arbor", "Lansing", "Flint"], - "city_values": [480, 280, 250, 180, 150], - }, -} - -states = list(us_states_data.keys()) -state_abbrevs = [us_states_data[s]["abbrev"] for s in states] -state_values = [sum(us_states_data[s]["city_values"]) for s in states] - -# City coordinates (approximate lat/lon) -city_coords = { - "Los Angeles": (-118.24, 34.05), - "San Francisco": (-122.42, 37.77), - "San Diego": (-117.16, 32.72), - "San Jose": (-121.89, 37.34), - "Sacramento": (-121.49, 38.58), - "Houston": (-95.37, 29.76), - "Dallas": (-96.80, 32.78), - "Austin": (-97.74, 30.27), - "San Antonio": (-98.49, 29.42), - "Fort Worth": (-97.33, 32.75), - "Miami": (-80.19, 25.76), - "Orlando": (-81.38, 28.54), - "Tampa": (-82.46, 27.95), - "Jacksonville": (-81.66, 30.33), - "Fort Lauderdale": (-80.14, 26.12), - "New York City": (-74.01, 40.71), - "Buffalo": (-78.88, 42.89), - "Rochester": (-77.61, 43.16), - "Albany": (-73.76, 42.65), - "Syracuse": (-76.15, 43.05), - "Chicago": (-87.63, 41.88), - "Aurora": (-88.32, 41.76), - "Naperville": (-88.15, 41.79), - "Rockford": (-89.09, 42.27), - "Joliet": (-88.08, 41.53), - "Philadelphia": (-75.16, 39.95), - "Pittsburgh": (-79.99, 40.44), - "Allentown": (-75.49, 40.60), - "Erie": (-80.09, 42.13), - "Reading": (-75.93, 40.34), - "Columbus": (-82.99, 39.96), - "Cleveland": (-81.69, 41.50), - "Cincinnati": (-84.51, 39.10), - "Toledo": (-83.54, 41.65), - "Akron": (-81.52, 41.08), - "Atlanta": (-84.39, 33.75), - "Augusta": (-82.01, 33.47), - "Savannah": (-81.10, 32.08), - "Athens": (-83.38, 33.96), - "Charlotte": (-80.84, 35.23), - "Raleigh": (-78.64, 35.79), - "Greensboro": (-79.79, 36.07), - "Durham": (-78.90, 35.99), - "Wilmington": (-77.95, 34.23), - "Detroit": (-83.05, 42.33), - "Grand Rapids": (-85.67, 42.96), - "Ann Arbor": (-83.74, 42.28), - "Lansing": (-84.55, 42.73), - "Flint": (-83.69, 43.01), -} - -# Flatten city data for scattergeo layer -all_city_lons, all_city_lats = [], [] -all_city_names, all_city_values, all_city_states = [], [], [] - -for state, data in us_states_data.items(): - for city, value in zip(data["cities"], data["city_values"], strict=False): - if city in city_coords: - lon, lat = city_coords[city] - all_city_lons.append(lon) - all_city_lats.append(lat) - all_city_names.append(city) - all_city_values.append(value) - all_city_states.append(state) - -# Plot — state choropleth (top level) + city scatter (drill-down layer) -fig = go.Figure() - -fig.add_trace( - go.Choropleth( - locations=state_abbrevs, - z=state_values, - locationmode="USA-states", - colorscale=ANYPLOT_SEQ, - colorbar={ - "title": {"text": "Sales ($K)", "font": {"size": 12, "color": INK}}, - "tickfont": {"size": 10, "color": INK_SOFT}, - "len": 0.6, - "thickness": 18, - "x": 1.01, - "bgcolor": ELEVATED_BG, - "bordercolor": INK_SOFT, - "borderwidth": 1, - }, - marker={"line": {"color": BORDER_COLOR, "width": 1.5}}, - hovertemplate="%{text}
Total Sales: $%{z:,.0f}K", - text=states, - name="States", - ) -) - -fig.add_trace( - go.Scattergeo( - lon=all_city_lons, - lat=all_city_lats, - mode="markers+text", - marker={ - "size": [max(v / 40, 8) for v in all_city_values], - "color": all_city_values, - "colorscale": ANYPLOT_SEQ, - "line": {"color": PAGE_BG, "width": 1.5}, - "sizemin": 8, - "showscale": False, - }, - text=all_city_names, - textposition="top center", - textfont={"size": 10, "color": INK}, - hovertemplate=("%{text}
%{customdata}
Sales: $%{marker.color:,.0f}K"), - customdata=all_city_states, - name="Cities", - visible=False, - ) -) - -# Dropdown buttons for hierarchical navigation -geo_base = { - "scope": "usa", - "showlakes": True, - "lakecolor": LAKE_COLOR, - "landcolor": LAND_COLOR, - "showland": True, - "showcountries": False, - "showcoastlines": True, - "coastlinecolor": BORDER_COLOR, - "bgcolor": PAGE_BG, -} - -title_base = "map-drilldown-geographic · python · plotly · anyplot.ai" - -buttons = [ - { - "label": "All States", - "method": "update", - "args": [ - {"visible": [True, False, True]}, - { - "geo": {**geo_base}, - "title.text": f"{title_base}
US Sales by State — use dropdown to drill into cities", - }, - ], - }, - { - "label": "All Cities", - "method": "update", - "args": [ - {"visible": [True, True, True]}, - {"geo": {**geo_base}, "title.text": f"{title_base}
US Sales — All Cities Overlay"}, - ], - }, -] - -state_centers = { - "California": {"lon": -119.5, "lat": 37.0, "zoom": 4.5}, - "Texas": {"lon": -99.0, "lat": 31.5, "zoom": 4.5}, - "Florida": {"lon": -82.5, "lat": 28.0, "zoom": 5.0}, - "New York": {"lon": -75.5, "lat": 43.0, "zoom": 5.0}, - "Illinois": {"lon": -89.0, "lat": 40.0, "zoom": 5.5}, - "Pennsylvania": {"lon": -77.5, "lat": 41.0, "zoom": 5.5}, - "Ohio": {"lon": -82.5, "lat": 40.5, "zoom": 5.5}, - "Georgia": {"lon": -83.5, "lat": 32.5, "zoom": 5.5}, - "North Carolina": {"lon": -79.5, "lat": 35.5, "zoom": 5.5}, - "Michigan": {"lon": -85.0, "lat": 44.0, "zoom": 5.0}, -} - -# Spotlight annotations — top/bottom performers create a data story focal point -state_totals = {s: sum(us_states_data[s]["city_values"]) for s in states} -sorted_by_total = sorted(states, key=lambda s: state_totals[s], reverse=True) -spotlight = [ - ( - sorted_by_total[0], - "#99B314", - "star", - 15, - f"★ Top: {us_states_data[sorted_by_total[0]]['abbrev']} ${state_totals[sorted_by_total[0]]:,}K", - ), - ( - sorted_by_total[1], - "#99B314", - "star", - 12, - f"★ 2nd: {us_states_data[sorted_by_total[1]]['abbrev']} ${state_totals[sorted_by_total[1]]:,}K", - ), - ( - sorted_by_total[-1], - "#AE3030", - "triangle-down", - 12, - f"▼ Low: {us_states_data[sorted_by_total[-1]]['abbrev']} ${state_totals[sorted_by_total[-1]]:,}K", - ), -] - -fig.add_trace( - go.Scattergeo( - lon=[state_centers[s[0]]["lon"] for s in spotlight], - lat=[state_centers[s[0]]["lat"] for s in spotlight], - mode="markers+text", - marker={ - "symbol": [s[2] for s in spotlight], - "size": [s[3] for s in spotlight], - "color": [s[1] for s in spotlight], - "line": {"color": PAGE_BG, "width": 1.5}, - }, - text=[s[4] for s in spotlight], - textposition="bottom center", - textfont={"size": 9, "color": INK}, - hovertemplate="%{text}", - name="", - showlegend=False, - visible=True, - ) -) - -for state in states: - center = state_centers[state] - state_total = sum(us_states_data[state]["city_values"]) - buttons.append( - { - "label": f"{state} (${state_total}K)", - "method": "update", - "args": [ - {"visible": [True, True, False]}, - { - "geo": { - **geo_base, - "center": {"lon": center["lon"], "lat": center["lat"]}, - "projection": {"scale": center["zoom"]}, - }, - "title.text": f"{title_base}
United States > {state} | City-Level Sales", - }, - ], - } - ) - -# Style -fig.update_layout( - autosize=False, - title={ - "text": f"{title_base}
US Sales by State — use dropdown to drill into cities", - "font": {"size": 16, "color": INK}, - "x": 0.5, - "xanchor": "center", - }, - geo=dict(**geo_base), - updatemenus=[ - { - "type": "dropdown", - "direction": "down", - "active": 0, - "x": 0.01, - "y": 0.99, - "xanchor": "left", - "yanchor": "top", - "buttons": buttons, - "font": {"size": 12, "color": INK}, - "bgcolor": ELEVATED_BG, - "bordercolor": INK_SOFT, - "borderwidth": 1, - "showactive": True, - } - ], - paper_bgcolor=PAGE_BG, - plot_bgcolor=PAGE_BG, - font={"color": INK}, - legend={"bgcolor": ELEVATED_BG, "bordercolor": INK_SOFT, "borderwidth": 1, "font": {"color": INK_SOFT}}, - margin={"l": 40, "r": 60, "t": 80, "b": 40}, -) - -# Save -fig.write_image(f"plot-{THEME}.png", width=800, height=450, scale=4) -fig.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn") diff --git a/plots/map-drilldown-geographic/implementations/python/plotnine.py b/plots/map-drilldown-geographic/implementations/python/plotnine.py deleted file mode 100644 index 57f6e9f143..0000000000 --- a/plots/map-drilldown-geographic/implementations/python/plotnine.py +++ /dev/null @@ -1,353 +0,0 @@ -""" anyplot.ai -map-drilldown-geographic: Drillable Geographic Map -Library: plotnine 0.15.4 | Python 3.13.13 -Quality: 81/100 | Updated: 2026-05-23 -""" - -import os - -import numpy as np -import pandas as pd -from plotnine import ( - aes, - annotate, - coord_fixed, - element_blank, - element_line, - element_rect, - element_text, - geom_polygon, - geom_text, - ggplot, - labs, - scale_fill_gradient, - scale_x_continuous, - scale_y_continuous, - theme, -) - - -# 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" -PANEL_BG = "#D6EAF8" if THEME == "light" else "#1A2A3A" - -# Seed for reproducibility -np.random.seed(42) - -# Hierarchical geographic data: USA states with performance metrics -states_data = { - "California": { - "coords": [ - (-124.4, 42.0), - (-124.2, 40.0), - (-122.4, 37.8), - (-120.0, 34.5), - (-117.1, 32.5), - (-114.6, 32.7), - (-114.6, 34.9), - (-120.0, 39.0), - (-121.5, 41.2), - (-124.4, 42.0), - ], - "value": 85, - "centroid": (-119.4, 37.2), - "abbrev": "CA", - }, - "Texas": { - "coords": [ - (-106.6, 32.0), - (-103.0, 32.0), - (-103.0, 36.5), - (-100.0, 36.5), - (-100.0, 34.5), - (-94.4, 33.6), - (-93.5, 31.0), - (-94.0, 29.5), - (-97.1, 26.0), - (-99.0, 26.0), - (-101.4, 29.8), - (-104.0, 29.5), - (-106.5, 31.8), - (-106.6, 32.0), - ], - "value": 72, - "centroid": (-99.5, 31.2), - "abbrev": "TX", - }, - "New York": { - "coords": [ - (-79.8, 43.0), - (-75.0, 45.0), - (-73.3, 45.0), - (-73.3, 41.2), - (-74.7, 41.4), - (-75.4, 39.9), - (-79.8, 42.3), - (-79.8, 43.0), - ], - "value": 91, - "centroid": (-76.0, 42.8), - "abbrev": "NY", - }, - "Florida": { - "coords": [ - (-87.6, 31.0), - (-85.0, 31.0), - (-82.0, 30.4), - (-81.5, 29.0), - (-80.4, 25.8), - (-80.0, 24.5), - (-82.8, 24.5), - (-83.0, 27.0), - (-84.9, 29.7), - (-87.6, 30.4), - (-87.6, 31.0), - ], - "value": 68, - "centroid": (-82.5, 28.5), - "abbrev": "FL", - }, - "Illinois": { - "coords": [ - (-91.5, 42.5), - (-87.5, 42.5), - (-87.5, 39.5), - (-88.0, 37.5), - (-89.5, 36.5), - (-91.5, 36.9), - (-91.0, 40.0), - (-91.5, 42.5), - ], - "value": 78, - "centroid": (-89.8, 40.8), - "abbrev": "IL", - }, - "Washington": { - "coords": [(-124.7, 48.4), (-117.0, 49.0), (-117.0, 46.0), (-119.0, 45.9), (-124.0, 46.3), (-124.7, 48.4)], - "value": 82, - "centroid": (-120.5, 47.4), - "abbrev": "WA", - }, - "Colorado": { - "coords": [(-109.0, 41.0), (-102.0, 41.0), (-102.0, 37.0), (-109.0, 37.0), (-109.0, 41.0)], - "value": 75, - "centroid": (-105.5, 39.0), - "abbrev": "CO", - }, - "Arizona": { - "coords": [(-114.8, 37.0), (-109.0, 37.0), (-109.0, 31.3), (-111.1, 31.3), (-114.8, 32.5), (-114.8, 37.0)], - "value": 64, - "centroid": (-111.9, 34.2), - "abbrev": "AZ", - }, - "Georgia": { - "coords": [ - (-85.6, 35.0), - (-83.1, 35.0), - (-83.4, 34.5), - (-82.2, 33.5), - (-81.0, 32.1), - (-80.9, 30.4), - (-82.0, 30.4), - (-84.9, 30.7), - (-85.0, 32.0), - (-85.6, 35.0), - ], - "value": 71, - "centroid": (-83.5, 32.7), - "abbrev": "GA", - }, - "Ohio": { - "coords": [(-84.8, 41.7), (-80.5, 42.0), (-80.5, 39.5), (-81.7, 38.9), (-84.8, 39.1), (-84.8, 41.7)], - "value": 69, - "centroid": (-82.2, 40.8), - "abbrev": "OH", - }, - "Pennsylvania": { - "coords": [(-80.5, 42.0), (-75.0, 42.0), (-75.0, 39.7), (-80.5, 39.7), (-80.5, 42.0)], - "value": 76, - "centroid": (-77.8, 40.9), - "abbrev": "PA", - }, - "Michigan": { - "coords": [ - (-90.4, 46.0), - (-84.0, 46.5), - (-82.5, 45.0), - (-82.5, 43.5), - (-84.5, 41.7), - (-87.0, 41.7), - (-87.5, 43.0), - (-88.0, 45.0), - (-90.4, 46.0), - ], - "value": 73, - "centroid": (-85.0, 44.5), - "abbrev": "MI", - }, - "Nevada": { - "coords": [(-120.0, 42.0), (-114.0, 42.0), (-114.0, 36.0), (-117.0, 36.0), (-120.0, 39.0), (-120.0, 42.0)], - "value": 79, - "centroid": (-117.0, 39.5), - "abbrev": "NV", - }, - "Oregon": { - "coords": [(-124.5, 46.0), (-117.0, 46.0), (-117.0, 42.0), (-124.5, 42.0), (-124.5, 46.0)], - "value": 81, - "centroid": (-120.5, 44.0), - "abbrev": "OR", - }, - "North Carolina": { - "coords": [(-84.3, 36.6), (-75.5, 36.5), (-75.5, 34.0), (-78.5, 33.8), (-84.0, 35.0), (-84.3, 36.6)], - "value": 77, - "centroid": (-79.5, 35.5), - "abbrev": "NC", - }, - "Virginia": { - "coords": [(-83.7, 36.6), (-75.2, 37.2), (-75.5, 38.0), (-77.0, 39.0), (-79.5, 39.5), (-83.7, 36.6)], - "value": 83, - "centroid": (-78.5, 37.5), - "abbrev": "VA", - }, - "Tennessee": { - "coords": [(-90.3, 35.0), (-81.7, 36.6), (-81.7, 35.0), (-88.0, 35.0), (-90.3, 35.0)], - "value": 70, - "centroid": (-86.5, 35.4), - "abbrev": "TN", - }, - "Missouri": { - "coords": [(-95.8, 40.6), (-89.1, 40.6), (-89.1, 36.5), (-94.6, 36.5), (-95.8, 37.0), (-95.8, 40.6)], - "value": 74, - "centroid": (-92.5, 38.5), - "abbrev": "MO", - }, - "Indiana": { - "coords": [(-88.1, 41.8), (-84.8, 41.8), (-84.8, 38.0), (-88.1, 37.8), (-88.1, 41.8)], - "value": 67, - "centroid": (-86.2, 39.0), - "abbrev": "IN", - }, - "Wisconsin": { - "coords": [(-92.9, 47.0), (-86.8, 46.0), (-86.8, 42.5), (-90.6, 42.5), (-92.9, 44.0), (-92.9, 47.0)], - "value": 80, - "centroid": (-89.5, 44.5), - "abbrev": "WI", - }, -} - -# Identify top-3 and bottom-3 states for focal emphasis -sorted_by_value = sorted(states_data.items(), key=lambda x: x[1]["value"]) -bottom_3 = {s[0] for s in sorted_by_value[:3]} # AZ (64), IN (67), FL (68) -top_3 = {s[0] for s in sorted_by_value[-3:]} # VA (83), CA (85), NY (91) - -# Build dataframe for state polygons -polygon_rows = [] -for state_name, state_info in states_data.items(): - for idx, (lon, lat) in enumerate(state_info["coords"]): - polygon_rows.append( - { - "state": state_name, - "lon": lon, - "lat": lat, - "order": idx, - "value": state_info["value"], - "abbrev": state_info["abbrev"], - } - ) - -df_states = pd.DataFrame(polygon_rows) -df_top3 = df_states[df_states["state"].isin(top_3)].copy() -df_bottom3 = df_states[df_states["state"].isin(bottom_3)].copy() - -# Nudge centroids for crowded northeastern states to reduce label overlap -centroid_nudge = { - "New York": (-76.0, 43.3), - "Tennessee": (-86.3, 36.4), # push north — TN polygon is a thin strip - "Virginia": (-78.5, 38.4), # push north to separate from NC - "Illinois": (-89.5, 40.2), - "Wisconsin": (-89.7, 44.8), - "Michigan": (-85.5, 44.3), -} - -label_rows = [] -for state_name, state_info in states_data.items(): - centroid = centroid_nudge.get(state_name, state_info["centroid"]) - label_rows.append( - { - "state": state_name, - "lon": centroid[0], - "lat": centroid[1], - "value": state_info["value"], - "abbrev": state_info["abbrev"], - "label": f"{state_info['abbrev']}\n{state_info['value']}", - } - ) - -df_labels = pd.DataFrame(label_rows) - -min_value = df_labels["value"].min() -max_value = df_labels["value"].max() - -breadcrumb_text = "World > USA > States" - -# Build choropleth map using anyplot_seq colormap (green → dark azure) -# Highlight outlines: lime for top-3 performers, red for bottom-3 -plot = ( - ggplot() - + geom_polygon( - aes(x="lon", y="lat", group="state", fill="value"), data=df_states, color=PAGE_BG, size=1.5, alpha=0.92 - ) - + geom_polygon(aes(x="lon", y="lat", group="state"), data=df_top3, color="#99B314", fill="none", size=2.5) - + geom_polygon(aes(x="lon", y="lat", group="state"), data=df_bottom3, color="#AE3030", fill="none", size=2.5) - + geom_text( - aes(x="lon", y="lat", label="label"), - data=df_labels, - size=4, - color="white", - fontweight="bold", - va="center", - ha="center", - ) - + scale_fill_gradient( - low="#009E73", - high="#003D94", - name="Performance\nScore", - limits=(min_value, max_value), - breaks=[64, 70, 76, 82, 88, 91], - ) - + scale_x_continuous( - breaks=[-120, -110, -100, -90, -80], labels=["120°W", "110°W", "100°W", "90°W", "80°W"], limits=(-126, -72) - ) - + scale_y_continuous( - breaks=[25, 30, 35, 40, 45, 50], labels=["25°N", "30°N", "35°N", "40°N", "45°N", "50°N"], limits=(22, 51) - ) - + coord_fixed(ratio=1.3) - + annotate("text", x=-125, y=50, label=breadcrumb_text, size=9, color=INK, fontweight="bold", ha="left") - + annotate("text", x=-74, y=25.5, label="▲ Top 3", size=8, color="#99B314", ha="right", fontweight="bold") - + annotate("text", x=-74, y=23.5, label="▼ Bottom 3", size=8, color="#AE3030", ha="right", fontweight="bold") - + labs(title="map-drilldown-geographic · python · plotnine · anyplot.ai", x="Longitude", y="Latitude") - + theme( - figure_size=(8, 4.5), - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - panel_background=element_rect(fill=PANEL_BG), - panel_grid_major=element_line(color=INK, size=0.3, alpha=0.10), - panel_grid_minor=element_blank(), - panel_border=element_rect(color=INK_SOFT, fill=None), - plot_title=element_text(size=12, weight="bold", color=INK, ha="center"), - axis_title=element_text(size=10, color=INK), - axis_text=element_text(size=8, color=INK_SOFT), - axis_line=element_blank(), - axis_ticks=element_line(color=INK_SOFT, size=0.5), - legend_title=element_text(size=8, weight="bold", color=INK), - legend_text=element_text(size=8, color=INK_SOFT), - legend_position="right", - legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT), - ) -) - -# Save -plot.save(f"plot-{THEME}.png", dpi=400, width=8, height=4.5, units="in", verbose=False) diff --git a/plots/map-drilldown-geographic/implementations/python/pygal.py b/plots/map-drilldown-geographic/implementations/python/pygal.py deleted file mode 100644 index 55d255212a..0000000000 --- a/plots/map-drilldown-geographic/implementations/python/pygal.py +++ /dev/null @@ -1,614 +0,0 @@ -""" anyplot.ai -map-drilldown-geographic: Drillable Geographic Map -Library: pygal 3.1.0 | Python 3.13.13 -Quality: 79/100 | Updated: 2026-05-23 -""" - -import json -import os -import sys - - -# Remove the script's own directory from sys.path so `import pygal` finds the -# installed package rather than this file (which shares the same name). -_here = os.path.dirname(os.path.abspath(__file__)) -sys.path = [p for p in sys.path if os.path.abspath(p) != _here] - -import pygal -from pygal.style import Style - - -# 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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -title_str = "map-drilldown-geographic · python · pygal · anyplot.ai" -title_font_size = 66 - - -# anyplot_seq sequential colormap: brand green → dark azure (choropleth scale) -def _lerp_hex(c0, c1, t): - r0, g0, b0 = (int(c0[i : i + 2], 16) for i in (1, 3, 5)) - r1, g1, b1 = (int(c1[i : i + 2], 16) for i in (1, 3, 5)) - r = int(round(r0 + (r1 - r0) * t)) - g = int(round(g0 + (g1 - g0) * t)) - b = int(round(b0 + (b1 - b0) * t)) - return f"#{r:02X}{g:02X}{b:02X}" - - -def seq_color(val, lo, hi): - t = (val - lo) / (hi - lo) if hi > lo else 0.5 - # Dark end capped at #0A5AC4 (not #003D94) so tiles remain distinguishable on #1A1A17 - return _lerp_hex("#009E73", "#0A5AC4", t) - - -# Hierarchical regional sales data (millions USD): World -> Country -> State -> City -hierarchy = { - "world": {"name": "World", "parent": None, "children": ["us", "de", "jp", "br", "au"], "value": None}, - "us": { - "name": "United States", - "short": "US", - "parent": "world", - "value": 2100, - "children": ["us_ca", "us_tx", "us_ny", "us_fl"], - }, - "de": { - "name": "Germany", - "short": "Germany", - "parent": "world", - "value": 580, - "children": ["de_by", "de_nw", "de_he"], - }, - "jp": {"name": "Japan", "short": "Japan", "parent": "world", "value": 850, "children": ["jp_13", "jp_27", "jp_23"]}, - "br": { - "name": "Brazil", - "short": "Brazil", - "parent": "world", - "value": 520, - "children": ["br_sp", "br_rj", "br_mg"], - }, - "au": { - "name": "Australia", - "short": "Australia", - "parent": "world", - "value": 380, - "children": ["au_nsw", "au_vic", "au_qld"], - }, - "us_ca": { - "name": "California", - "short": "California", - "parent": "us", - "value": 680, - "children": ["us_ca_la", "us_ca_sf", "us_ca_sd"], - }, - "us_tx": { - "name": "Texas", - "short": "Texas", - "parent": "us", - "value": 520, - "children": ["us_tx_hou", "us_tx_dal", "us_tx_aus"], - }, - "us_ny": { - "name": "New York", - "short": "New York", - "parent": "us", - "value": 580, - "children": ["us_ny_nyc", "us_ny_buf", "us_ny_alb"], - }, - "us_fl": { - "name": "Florida", - "short": "Florida", - "parent": "us", - "value": 320, - "children": ["us_fl_mia", "us_fl_orl", "us_fl_tam"], - }, - "de_by": { - "name": "Bavaria", - "short": "Bavaria", - "parent": "de", - "value": 210, - "children": ["de_by_muc", "de_by_nur"], - }, - "de_nw": { - "name": "North Rhine-Westphalia", - "short": "NRW", - "parent": "de", - "value": 240, - "children": ["de_nw_col", "de_nw_dus"], - }, - "de_he": {"name": "Hesse", "short": "Hesse", "parent": "de", "value": 130, "children": ["de_he_fra", "de_he_wie"]}, - "jp_13": {"name": "Tokyo", "short": "Tokyo", "parent": "jp", "value": 420, "children": ["jp_13_shi", "jp_13_min"]}, - "jp_27": {"name": "Osaka", "short": "Osaka", "parent": "jp", "value": 280, "children": ["jp_27_osa", "jp_27_sak"]}, - "jp_23": {"name": "Aichi", "short": "Aichi", "parent": "jp", "value": 150, "children": ["jp_23_nag", "jp_23_toy"]}, - "br_sp": { - "name": "Sao Paulo", - "short": "Sao Paulo", - "parent": "br", - "value": 280, - "children": ["br_sp_sao", "br_sp_cam"], - }, - "br_rj": { - "name": "Rio de Janeiro", - "short": "Rio de Janeiro", - "parent": "br", - "value": 160, - "children": ["br_rj_rio", "br_rj_nit"], - }, - "br_mg": { - "name": "Minas Gerais", - "short": "Minas Gerais", - "parent": "br", - "value": 80, - "children": ["br_mg_bho", "br_mg_ube"], - }, - "au_nsw": { - "name": "New South Wales", - "short": "NSW", - "parent": "au", - "value": 180, - "children": ["au_nsw_syd", "au_nsw_new"], - }, - "au_vic": { - "name": "Victoria", - "short": "Victoria", - "parent": "au", - "value": 140, - "children": ["au_vic_mel", "au_vic_gee"], - }, - "au_qld": { - "name": "Queensland", - "short": "QLD", - "parent": "au", - "value": 60, - "children": ["au_qld_bri", "au_qld_gol"], - }, - "us_ca_la": {"name": "Los Angeles", "short": "Los Angeles", "parent": "us_ca", "value": 320, "children": []}, - "us_ca_sf": {"name": "San Francisco", "short": "San Francisco", "parent": "us_ca", "value": 240, "children": []}, - "us_ca_sd": {"name": "San Diego", "short": "San Diego", "parent": "us_ca", "value": 120, "children": []}, - "us_tx_hou": {"name": "Houston", "short": "Houston", "parent": "us_tx", "value": 220, "children": []}, - "us_tx_dal": {"name": "Dallas", "short": "Dallas", "parent": "us_tx", "value": 180, "children": []}, - "us_tx_aus": {"name": "Austin", "short": "Austin", "parent": "us_tx", "value": 120, "children": []}, - "us_ny_nyc": {"name": "New York City", "short": "NYC", "parent": "us_ny", "value": 420, "children": []}, - "us_ny_buf": {"name": "Buffalo", "short": "Buffalo", "parent": "us_ny", "value": 90, "children": []}, - "us_ny_alb": {"name": "Albany", "short": "Albany", "parent": "us_ny", "value": 70, "children": []}, - "us_fl_mia": {"name": "Miami", "short": "Miami", "parent": "us_fl", "value": 140, "children": []}, - "us_fl_orl": {"name": "Orlando", "short": "Orlando", "parent": "us_fl", "value": 100, "children": []}, - "us_fl_tam": {"name": "Tampa", "short": "Tampa", "parent": "us_fl", "value": 80, "children": []}, - "de_by_muc": {"name": "Munich", "short": "Munich", "parent": "de_by", "value": 150, "children": []}, - "de_by_nur": {"name": "Nuremberg", "short": "Nuremberg", "parent": "de_by", "value": 60, "children": []}, - "de_nw_col": {"name": "Cologne", "short": "Cologne", "parent": "de_nw", "value": 130, "children": []}, - "de_nw_dus": {"name": "Dusseldorf", "short": "Dusseldorf", "parent": "de_nw", "value": 110, "children": []}, - "de_he_fra": {"name": "Frankfurt", "short": "Frankfurt", "parent": "de_he", "value": 90, "children": []}, - "de_he_wie": {"name": "Wiesbaden", "short": "Wiesbaden", "parent": "de_he", "value": 40, "children": []}, - "jp_13_shi": {"name": "Shibuya", "short": "Shibuya", "parent": "jp_13", "value": 280, "children": []}, - "jp_13_min": {"name": "Minato", "short": "Minato", "parent": "jp_13", "value": 140, "children": []}, - "jp_27_osa": {"name": "Osaka City", "short": "Osaka City", "parent": "jp_27", "value": 200, "children": []}, - "jp_27_sak": {"name": "Sakai", "short": "Sakai", "parent": "jp_27", "value": 80, "children": []}, - "jp_23_nag": {"name": "Nagoya", "short": "Nagoya", "parent": "jp_23", "value": 100, "children": []}, - "jp_23_toy": {"name": "Toyota", "short": "Toyota", "parent": "jp_23", "value": 50, "children": []}, - "br_sp_sao": {"name": "Sao Paulo City", "short": "Sao Paulo City", "parent": "br_sp", "value": 220, "children": []}, - "br_sp_cam": {"name": "Campinas", "short": "Campinas", "parent": "br_sp", "value": 60, "children": []}, - "br_rj_rio": {"name": "Rio City", "short": "Rio City", "parent": "br_rj", "value": 120, "children": []}, - "br_rj_nit": {"name": "Niteroi", "short": "Niteroi", "parent": "br_rj", "value": 40, "children": []}, - "br_mg_bho": {"name": "Belo Horizonte", "short": "Belo Horizonte", "parent": "br_mg", "value": 60, "children": []}, - "br_mg_ube": {"name": "Uberlandia", "short": "Uberlandia", "parent": "br_mg", "value": 20, "children": []}, - "au_nsw_syd": {"name": "Sydney", "short": "Sydney", "parent": "au_nsw", "value": 140, "children": []}, - "au_nsw_new": {"name": "Newcastle", "short": "Newcastle", "parent": "au_nsw", "value": 40, "children": []}, - "au_vic_mel": {"name": "Melbourne", "short": "Melbourne", "parent": "au_vic", "value": 110, "children": []}, - "au_vic_gee": {"name": "Geelong", "short": "Geelong", "parent": "au_vic", "value": 30, "children": []}, - "au_qld_bri": {"name": "Brisbane", "short": "Brisbane", "parent": "au_qld", "value": 45, "children": []}, - "au_qld_gol": {"name": "Gold Coast", "short": "Gold Coast", "parent": "au_qld", "value": 15, "children": []}, -} - -country_ids = hierarchy["world"]["children"] - -# Choropleth sequential scale across all country values -country_vals = [hierarchy[cid]["value"] for cid in country_ids] -c_min, c_max = min(country_vals), max(country_vals) -# Each country series gets a choropleth color based on its sales value -choropleth_palette = tuple(seq_color(hierarchy[cid]["value"], c_min, c_max) for cid in country_ids) - -# PNG — Treemap with anyplot_seq choropleth coloring (color encodes sales magnitude) -# print_values disabled to avoid dark-ink-on-dark-cell contrast failure in dark theme -png_style = Style( - background=PAGE_BG, - plot_background=PAGE_BG, - foreground=INK, - foreground_strong=INK, - foreground_subtle=INK_SOFT, - colors=choropleth_palette, - title_font_size=title_font_size, - label_font_size=56, - legend_font_size=44, - major_label_font_size=44, - value_font_size=36, - tooltip_font_size=40, - no_data_font_size=44, - stroke_style={"width": 2, "linecap": "round", "linejoin": "round"}, -) - -png_chart = pygal.Treemap( - style=png_style, - width=3200, - height=1800, - title=title_str, - show_legend=True, - legend_at_bottom=True, - legend_at_bottom_columns=5, - legend_box_size=50, - print_values=True, - print_values_position="center", - margin=40, - margin_bottom=160, - explicit_size=True, -) - -# Shortened country name for legend to prevent truncation; value in label -for country_id in country_ids: - country = hierarchy[country_id] - short = country["short"] - state_values = [{"value": hierarchy[sid]["value"], "label": hierarchy[sid]["name"]} for sid in country["children"]] - png_chart.add(f"{short} (${country['value']}M)", state_values) - -png_chart.render_to_png(f"plot-{THEME}.png") - -# HTML — Interactive drilldown with choropleth coloring at each level -html_style = Style( - background=PAGE_BG, - plot_background=PAGE_BG, - foreground=INK, - foreground_strong=INK, - foreground_subtle=INK_MUTED, - colors=choropleth_palette, - title_font_size=20, - label_font_size=14, - legend_font_size=14, - major_label_font_size=12, - value_font_size=12, - tooltip_font_size=14, -) - -# World-level treemap SVG (choropleth palette, no print_values to avoid contrast issue) -world_treemap = pygal.Treemap( - style=html_style, - width=860, - height=480, - show_legend=True, - legend_at_bottom=True, - legend_at_bottom_columns=5, - legend_box_size=18, - print_values=False, - explicit_size=True, -) -for country_id in country_ids: - country = hierarchy[country_id] - state_values = [{"value": hierarchy[sid]["value"], "label": hierarchy[sid]["name"]} for sid in country["children"]] - world_treemap.add(country["short"], state_values) - -world_svg = world_treemap.render(is_unicode=True) - -# Bar chart SVGs for drillable nodes — choropleth colored per level -svg_data = {"world": world_svg} - -for node_id, node_data in hierarchy.items(): - if node_id != "world" and node_data.get("children"): - children_ids = node_data["children"] - if children_ids: - child_vals = [hierarchy[cid]["value"] for cid in children_ids] - lo, hi = min(child_vals), max(child_vals) - # Per-child choropleth colors at this level - level_palette = tuple(seq_color(v, lo, hi) for v in child_vals) - bar_style = Style( - background=PAGE_BG, - plot_background=PAGE_BG, - foreground=INK, - foreground_strong=INK, - foreground_subtle=INK_MUTED, - colors=level_palette, - title_font_size=18, - label_font_size=13, - legend_font_size=13, - value_font_size=11, - tooltip_font_size=13, - ) - bar = pygal.Bar( - style=bar_style, - width=860, - height=480, - show_legend=False, - show_y_guides=True, - y_title="Sales ($M)", - print_values=True, - print_values_position="top", - value_formatter=lambda x: f"${x}M", - human_readable=True, - explicit_size=True, - ) - # Each child as its own single-value series to get individual choropleth colors - bar.x_labels = [hierarchy[cid]["name"] for cid in children_ids] - for i, cid in enumerate(children_ids): - vals = [None] * len(children_ids) - vals[i] = hierarchy[cid]["value"] - bar.add("", vals) - svg_data[node_id] = bar.render(is_unicode=True) - -hierarchy_json = json.dumps(hierarchy) - -# Build HTML — theme-adaptive colors baked in, CSS fade transition between levels -html_bg = PAGE_BG -html_elevated = ELEVATED_BG -html_ink = INK -html_ink_soft = INK_SOFT -html_ink_muted = INK_MUTED -brand_color = "#009E73" - -html_content = f""" - - - - {title_str} - - - -
-

{title_str}

- -
- Low -
- High — color encodes sales magnitude ($M) -
-
World View — Sales by Country (click to drill down)
-
-

Click any region or bar to explore sub-regions

-
- - -""" - -with open(f"plot-{THEME}.html", "w") as f: - f.write(html_content) diff --git a/plots/map-drilldown-geographic/implementations/python/seaborn.py b/plots/map-drilldown-geographic/implementations/python/seaborn.py deleted file mode 100644 index e082d648be..0000000000 --- a/plots/map-drilldown-geographic/implementations/python/seaborn.py +++ /dev/null @@ -1,324 +0,0 @@ -""" anyplot.ai -map-drilldown-geographic: Drillable Geographic Map -Library: seaborn 0.13.2 | Python 3.13.13 -Quality: 77/100 | Updated: 2026-05-23 -""" - -import os - -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd -import seaborn as sns -from matplotlib.colors import LinearSegmentedColormap -from matplotlib.patches import Rectangle - - -# Theme -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" -ACCENT = "#009E73" - -anyplot_seq = LinearSegmentedColormap.from_list("anyplot_seq", ["#009E73", "#003D94"]) - -sns.set_theme( - style="ticks", - rc={ - "figure.facecolor": PAGE_BG, - "axes.facecolor": PAGE_BG, - "axes.edgecolor": INK_SOFT, - "axes.labelcolor": INK, - "text.color": INK, - "xtick.color": INK_SOFT, - "ytick.color": INK_SOFT, - "grid.color": INK, - "grid.alpha": 0.10, - "legend.facecolor": ELEVATED_BG, - "legend.edgecolor": INK_SOFT, - }, -) - -np.random.seed(42) - -# Data: Europe → Germany → Bavaria (annual sales revenue, €M) -countries_data = { - "Germany": {"lon": 10.5, "lat": 51.0, "value": 4200, "bounds": (5.8, 15.0, 47.2, 55.0)}, - "France": {"lon": 2.5, "lat": 46.5, "value": 3100, "bounds": (-5.0, 8.2, 42.2, 51.2)}, - "UK": {"lon": -2.5, "lat": 54.0, "value": 2800, "bounds": (-8.0, 2.0, 49.8, 60.8)}, - "Spain": {"lon": -3.5, "lat": 40.0, "value": 1900, "bounds": (-9.5, 4.5, 36.0, 44.0)}, - "Italy": {"lon": 12.5, "lat": 42.5, "value": 2200, "bounds": (6.5, 18.5, 36.5, 47.5)}, -} - -germany_states_data = { - "Bavaria": {"lon": 11.5, "lat": 48.8, "value": 1100, "bounds": (9.0, 13.8, 47.2, 50.5)}, - "Baden-Württ.": {"lon": 8.7, "lat": 48.5, "value": 850, "bounds": (7.5, 10.5, 47.5, 49.8)}, - "NRW": {"lon": 7.5, "lat": 51.5, "value": 1200, "bounds": (5.9, 9.4, 50.3, 52.5)}, - "Hesse": {"lon": 9.0, "lat": 50.6, "value": 680, "bounds": (7.7, 10.2, 49.4, 51.7)}, - "Saxony": {"lon": 13.3, "lat": 51.0, "value": 520, "bounds": (11.9, 15.0, 50.2, 51.7)}, - "Brandenburg": {"lon": 13.0, "lat": 52.5, "value": 340, "bounds": (11.3, 14.8, 51.4, 53.6)}, -} - -bavaria_cities_data = { - "Munich": {"lon": 11.58, "lat": 48.14, "value": 450}, - "Nuremberg": {"lon": 11.08, "lat": 49.45, "value": 280}, - "Augsburg": {"lon": 10.90, "lat": 48.37, "value": 160}, - "Regensburg": {"lon": 12.10, "lat": 49.02, "value": 140}, - "Ingolstadt": {"lon": 11.43, "lat": 48.76, "value": 95}, - "Würzburg": {"lon": 9.93, "lat": 49.80, "value": 75}, -} - -all_values = ( - [c["value"] for c in countries_data.values()] - + [s["value"] for s in germany_states_data.values()] - + [c["value"] for c in bavaria_cities_data.values()] -) -vmin, vmax = min(all_values), max(all_values) -norm = plt.Normalize(vmin=vmin, vmax=vmax) - -# Figure — exact 3200 × 1800 px canvas -fig, axes = plt.subplots(1, 3, figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG) - -# --- Panel 1: European countries --- -ax1 = axes[0] -ax1.set_facecolor(PAGE_BG) - -for name, data in countries_data.items(): - b = data["bounds"] - rect = Rectangle( - (b[0], b[2]), - b[1] - b[0], - b[3] - b[2], - facecolor=anyplot_seq(norm(data["value"])), - edgecolor=ACCENT if name == "Germany" else INK_SOFT, - linewidth=3.5 if name == "Germany" else 0.8, - alpha=0.92 if name == "Germany" else 0.72, - ) - ax1.add_patch(rect) - -for name, data in countries_data.items(): - ax1.text(data["lon"], data["lat"] + 0.8, name, fontsize=6.5, ha="center", fontweight="bold", color=INK) - ax1.text(data["lon"], data["lat"] - 1.6, f"€{data['value']}M", fontsize=6, ha="center", color=INK_SOFT) - -# Centroid scatter via seaborn — visible size+hue encoding -country_df = pd.DataFrame([{"lon": v["lon"], "lat": v["lat"], "value": v["value"]} for v in countries_data.values()]) -sns.scatterplot( - data=country_df, - x="lon", - y="lat", - size="value", - sizes=(40, 120), - hue="value", - palette=anyplot_seq, - hue_norm=(vmin, vmax), - edgecolors=PAGE_BG, - linewidth=0.5, - alpha=0.6, - ax=ax1, - legend=False, -) - -ax1.set_xlim(-11, 23) -ax1.set_ylim(33, 64) -ax1.set_title("Level 1: Countries", fontsize=10, fontweight="medium", color=INK, pad=3) -ax1.set_xlabel("Longitude (°)", fontsize=8, color=INK) -ax1.set_ylabel("Latitude (°)", fontsize=8, color=INK) -ax1.tick_params(axis="both", labelsize=7, colors=INK_SOFT) -ax1.spines["top"].set_visible(False) -ax1.spines["right"].set_visible(False) -ax1.spines["left"].set_color(INK_SOFT) -ax1.spines["bottom"].set_color(INK_SOFT) -ax1.text( - 0.04, - 0.97, - "Europe", - transform=ax1.transAxes, - fontsize=7, - fontweight="bold", - color=ACCENT, - va="top", - bbox={"boxstyle": "round,pad=0.3", "facecolor": ELEVATED_BG, "edgecolor": ACCENT, "alpha": 0.9}, -) - -# --- Panel 2: German states --- -ax2 = axes[1] -ax2.set_facecolor(PAGE_BG) - -for name, data in germany_states_data.items(): - b = data["bounds"] - rect = Rectangle( - (b[0], b[2]), - b[1] - b[0], - b[3] - b[2], - facecolor=anyplot_seq(norm(data["value"])), - edgecolor=ACCENT if name == "Bavaria" else INK_SOFT, - linewidth=3.5 if name == "Bavaria" else 0.8, - alpha=0.92 if name == "Bavaria" else 0.72, - ) - ax2.add_patch(rect) - ax2.text(data["lon"], data["lat"] + 0.35, name, fontsize=6.5, ha="center", fontweight="bold", color=INK) - ax2.text(data["lon"], data["lat"] - 0.65, f"€{data['value']}M", fontsize=6, ha="center", color=INK_SOFT) - -# Centroid scatter via seaborn — visible size+hue encoding -state_df = pd.DataFrame([{"lon": v["lon"], "lat": v["lat"], "value": v["value"]} for v in germany_states_data.values()]) -sns.scatterplot( - data=state_df, - x="lon", - y="lat", - size="value", - sizes=(40, 120), - hue="value", - palette=anyplot_seq, - hue_norm=(vmin, vmax), - edgecolors=PAGE_BG, - linewidth=0.5, - alpha=0.6, - ax=ax2, - legend=False, -) - -ax2.set_xlim(5.0, 16.0) -ax2.set_ylim(46.5, 55.4) -ax2.set_title("Level 2: States (Germany)", fontsize=10, fontweight="medium", color=INK, pad=3) -ax2.set_xlabel("Longitude (°)", fontsize=8, color=INK) -ax2.set_ylabel("", fontsize=8) -ax2.tick_params(axis="both", labelsize=7, colors=INK_SOFT) -ax2.spines["top"].set_visible(False) -ax2.spines["right"].set_visible(False) -ax2.spines["left"].set_color(INK_SOFT) -ax2.spines["bottom"].set_color(INK_SOFT) -ax2.text( - 0.04, - 0.97, - "Europe > Germany", - transform=ax2.transAxes, - fontsize=7, - fontweight="bold", - color=ACCENT, - va="top", - bbox={"boxstyle": "round,pad=0.3", "facecolor": ELEVATED_BG, "edgecolor": ACCENT, "alpha": 0.9}, -) - -# --- Panel 3: Bavarian cities (seaborn scatter with size + hue) --- -ax3 = axes[2] -ax3.set_facecolor(PAGE_BG) - -bav = germany_states_data["Bavaria"] -ax3.add_patch( - Rectangle( - (bav["bounds"][0], bav["bounds"][2]), - bav["bounds"][1] - bav["bounds"][0], - bav["bounds"][3] - bav["bounds"][2], - facecolor=anyplot_seq(norm(bav["value"])), - edgecolor=ACCENT, - linewidth=1.2, - alpha=0.25, - ) -) - -cities_df = pd.DataFrame( - [{"name": k, "lon": v["lon"], "lat": v["lat"], "value": v["value"]} for k, v in bavaria_cities_data.items()] -) - -sns.scatterplot( - data=cities_df, - x="lon", - y="lat", - size="value", - sizes=(50, 320), - hue="value", - palette=anyplot_seq, - hue_norm=(vmin, vmax), - edgecolors=PAGE_BG, - linewidth=0.5, - alpha=0.85, - ax=ax3, - legend=False, -) - -# City labels — manual offsets to avoid overlap -label_cfg = { - "Augsburg": {"dx": -0.30, "dy_n": -0.20, "dy_v": -0.40, "ha": "right"}, - "Ingolstadt": {"dx": +0.28, "dy_n": +0.10, "dy_v": -0.12, "ha": "left"}, -} -for _, row in cities_df.iterrows(): - cfg = label_cfg.get(row["name"]) - if cfg: - ax3.text( - row["lon"] + cfg["dx"], - row["lat"] + cfg["dy_n"], - row["name"], - fontsize=6, - ha=cfg["ha"], - va="center", - fontweight="bold", - color=INK, - ) - ax3.text( - row["lon"] + cfg["dx"], - row["lat"] + cfg["dy_v"], - f"€{int(row['value'])}M", - fontsize=5, - ha=cfg["ha"], - va="center", - color=INK_SOFT, - ) - else: - ax3.text( - row["lon"], - row["lat"] + 0.20, - row["name"], - fontsize=6, - ha="center", - va="bottom", - fontweight="bold", - color=INK, - ) - ax3.text( - row["lon"], row["lat"] - 0.18, f"€{int(row['value'])}M", fontsize=5, ha="center", va="top", color=INK_SOFT - ) - -ax3.set_xlim(9.3, 13.2) -ax3.set_ylim(46.9, 50.8) -ax3.set_title("Level 3: Cities (Bavaria)", fontsize=10, fontweight="medium", color=INK, pad=3) -ax3.set_xlabel("Longitude (°)", fontsize=8, color=INK) -ax3.set_ylabel("", fontsize=8) -ax3.tick_params(axis="both", labelsize=7, colors=INK_SOFT) -ax3.spines["top"].set_visible(False) -ax3.spines["right"].set_visible(False) -ax3.spines["left"].set_color(INK_SOFT) -ax3.spines["bottom"].set_color(INK_SOFT) -ax3.text( - 0.04, - 0.97, - "Europe > Germany > Bavaria", - transform=ax3.transAxes, - fontsize=6.5, - fontweight="bold", - color=ACCENT, - va="top", - bbox={"boxstyle": "round,pad=0.3", "facecolor": ELEVATED_BG, "edgecolor": ACCENT, "alpha": 0.9}, -) - -# Drill-down arrows between panels -for xpos in (0.356, 0.662): - fig.text(xpos, 0.535, "→", fontsize=16, ha="center", va="center", color=ACCENT, fontweight="bold") - -# Main title -fig.suptitle( - "map-drilldown-geographic · python · seaborn · anyplot.ai", fontsize=12, fontweight="medium", color=INK, y=0.98 -) - -# Layout — reserve bottom for colorbar, place it via add_axes to avoid overlap -fig.subplots_adjust(left=0.08, right=0.94, top=0.88, bottom=0.28, wspace=0.33) - -sm = plt.cm.ScalarMappable(cmap=anyplot_seq, norm=norm) -cbar_ax = fig.add_axes([0.10, 0.12, 0.82, 0.038]) -cbar = fig.colorbar(sm, cax=cbar_ax, orientation="horizontal") -cbar.set_label("Sales Revenue (€M)", fontsize=8, color=INK) -cbar.ax.tick_params(labelsize=7, colors=INK_SOFT) -cbar.outline.set_edgecolor(INK_SOFT) - -plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG) diff --git a/plots/map-drilldown-geographic/metadata/julia/makie.yaml b/plots/map-drilldown-geographic/metadata/julia/makie.yaml deleted file mode 100644 index 674d1d2ae0..0000000000 --- a/plots/map-drilldown-geographic/metadata/julia/makie.yaml +++ /dev/null @@ -1,291 +0,0 @@ -library: makie -language: julia -specification_id: map-drilldown-geographic -created: '2026-05-23T09:27:14Z' -updated: '2026-05-23T09:53:25Z' -generated_by: claude-sonnet -workflow_run: 26328880462 -issue: 3770 -language_version: 1.11.9 -library_version: 0.22.10 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/julia/makie/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/julia/makie/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 84 -review: - strengths: - - Elegant three-panel drill-down design shows all three geographic hierarchy levels - simultaneously — a well-reasoned static alternative to interactive drill-down - for CairoMakie - - Highlighted drill path with thick INK ring on selected bubbles (N. America, CA) - creates clear visual hierarchy communicating which region is currently drilled - into - - Breadcrumb navigation strip 'World ▶ N. America ▶ California' effectively communicates - the current drill state as a static label - - ANYPLOT_SEQ colormap (green→blue) correctly applied as a continuous sequential - colormap for choropleth-style bubble coloring across all three levels - - Theme-adaptive chrome fully implemented — all INK/INK_SOFT/INK_MUTED tokens wired - through every Axis attribute; dark render shows no dark-on-dark text failures - - Geographic bounding boxes with subtle semi-transparent fills (alpha=0.06 fill, - 0.22 border) add spatial context without cluttering the map panels - - 'Clean KISS code structure: Imports → Data → Plot → Save, no functions or classes, - seed(42) for reproducibility' - - Shared colorbar spanning all three panels with descriptive label 'Relative Sales - Volume (within each level)' efficiently unifies the color scale explanation - weaknesses: - - California city values sum ($0.03+$0.07+$0.09+$0.19+$0.24 = $0.62B) slightly exceeds - the parent California state total ($0.60B) — minor data hierarchy inconsistency, - cities should sum to ≤ state value - - 'World Regions panel: geographic bounding boxes for Middle East, Africa, and Asia-Pacific - overlap and create a visually cluttered center zone; consider using lighter stroke - or removing overlapping boxes' - - Region bubble labels in World Regions panel use fontsize=8 — very small; 'Africa\n$0.7B' - and 'Middle East\n$1.1B' may be unreadable at mobile widths (~400px); increase - to fontsize=9-10 or increase bubble markersize slightly to give more room - - Panel 2 (US States) Y-axis label is empty (ylabel='') — while Panel 1 has 'Latitude', - the middle panel's unlabeled Y-axis creates a minor inconsistency; could add 'Latitude' - for completeness or use linked axes - - Minor crowding of NY/MA abbreviations in Panel 2 — the pixel offset approach works - but the labels are close together; small fontsize=9 helps but they remain tight - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct, not pure white - Chrome: Title "Sales Drill-Down · map-drilldown-geographic · julia · makie · anyplot.ai" in dark bold text, fully readable. Breadcrumb "World ▶ N. America ▶ California" in INK_SOFT. All three panel titles (① World Regions, ② N. America → US States, ③ California → Cities) readable in dark text. Axis labels "Longitude", "Latitude", "Sales ($B)" visible. Tick labels in INK_SOFT, readable. Colorbar label "Relative Sales Volume (within each level)" visible at bottom. - Data: Three-panel layout. Panel 1: six colored bubbles on a schematic world map with semi-transparent geographic bounding boxes; N. America bubble has thick bold ring indicating it is the drilled-into region. Colors range from brand green (#009E73) for low values to deep blue (#003D94) for high values via ANYPLOT_SEQ. Panel 2: eight US state bubbles positioned by geographic coordinates with a polygon outline of the US; CA bubble has thick ring. Panel 3: horizontal bar chart for five California cities with bars colored via ANYPLOT_SEQ from green (Sacramento lowest) to dark blue (San Francisco highest). Value labels ($0.03B–$0.24B) alongside each bar. - Legibility verdict: PASS — all text readable; minor concern on fontsize=8 world region labels but visible at full resolution - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct, not pure black - Chrome: Title in light cream text (#F0EFE8 equivalent via INK token), fully readable against dark background. Breadcrumb in INK_SOFT light gray. All panel titles visible in light text. Axis labels and tick labels in light INK_SOFT color. Colorbar label readable. No dark-on-dark failures observed — all chrome elements are light-colored on dark background. - Data: Data colors are identical to light render — same green-to-blue ANYPLOT_SEQ gradient used for all bubble and bar coloring. Geographic bounding boxes have very subtle dark fills. CA and N. America highlighted bubbles retain thick INK (now light cream) rings. - Legibility verdict: PASS — all text clearly readable against dark background; data colors identical to light render confirming only chrome flipped - criteria_checklist: - visual_quality: - score: 27 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: 'All font sizes explicitly set; title, labels, tick labels all readable - in both themes. Minor concern: world region bubble labels at fontsize=8 - are very small; readable at full resolution but borderline at mobile width.' - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: No major overlaps. NY and MA abbreviations are adjacent in Panel - 2 but managed with pixel offsets. Minor crowding but content readable. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Bubbles (markersize=28, 24) and bar chart bars are clearly visible - and well-sized for the data density. Colorbar gradient clearly shows range. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: ANYPLOT_SEQ (green→blue) is a perceptually uniform sequential colormap, - CVD-safe. Thick rings on highlighted bubbles provide additional shape-based - encoding. - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: 'Three panels fill the canvas well with shared colorbar. Minor: overlapping - geographic bounding boxes in world map panel look slightly cluttered. Overall - good use of canvas.' - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Longitude/Latitude for map panels, Sales ($B) with units for bar - chart. Colorbar labeled with descriptive text. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'ANYPLOT_SEQ correctly used for continuous choropleth coloring. Backgrounds - #FAF8F1/#1A1A17. All chrome tokens properly theme-adaptive. Data colors - identical between light and dark renders.' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Above average: the three-panel drill-down concept with highlighted - selected regions is a thoughtful static design. Custom colormap, semi-transparent - geographic boxes, and ring-highlighting show design intent. Not publication-ready - but clearly above defaults.' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Grid removed on all panels, top/right spines removed, generous whitespace - via rowgap/colgap, semi-transparent box fills are refined. Good refinement - visible above library defaults. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: 'Clear visual hierarchy: left-to-right panel flow communicates the - drill-down path, highlighted bubbles (thick ring) show the selected region - at each level, breadcrumb strip confirms the navigation state. Viewer immediately - understands the geographic drill-down structure.' - spec_compliance: - score: 13 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 4 - max: 5 - passed: true - comment: Three-panel static layout correctly represents hierarchical geographic - drill-down for CairoMakie. Choropleth-style bubble coloring at each level - is correct. Cannot score 5 because the interactive click/zoom-transition - core of the spec is inherently absent in a static library. - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Hierarchical levels (World→State→City) ✓, choropleth coloring ✓, - breadcrumb navigation ✓, color legend ✓, aggregated values ✓. Interactive - drill-down/zoom inherently absent for static library. One minor point deducted - for missing hover tooltips (explicitly requested in spec even for interactive - libraries). - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Geographic coordinates correctly mapped to bubble positions. Bar - chart correctly shows cities at leaf level. Color encoding reflects relative - value within each level. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title 'Sales Drill-Down · map-drilldown-geographic · julia · makie - · anyplot.ai' matches the allowed format with optional descriptive prefix. - Colorbar legend is descriptive. - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Shows all three levels of geographic hierarchy with meaningful value - variation at each level. Color encoding, value labels, and highlighted drill - path all demonstrate the full feature set. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Regional sales performance ($B) is a realistic, neutral, comprehensible - business scenario. All geographic regions and city names are real and appropriately - placed. - - id: DQ-03 - name: Appropriate Scale - score: 3 - max: 4 - passed: true - comment: 'Geographic coordinates are approximately correct. State sales values - are plausible. Minor: California cities sum ($0.62B) slightly exceeds California - state total ($0.60B) — a small hierarchy inconsistency.' - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Clean Imports → Data → Plot → Save structure. No functions or classes - defined. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Random.seed!(42) set at top of file. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: CairoMakie, Colors, Random — all three are used throughout. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean, idiomatic Julia/Makie code. No fake UI elements or simulated - interactivity. Thoughtful label offset array for adjacent states. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-$(THEME).png with px_per_unit=2. Correct format. - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Figure/Axis/Label/Colorbar layout API used correctly. scatter!, barplot!, - poly!, text! are idiomatic primitives. rowgap!/colgap! for layout control. - cgrad for colormap construction. Good above-default usage. - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Uses poly! for geographic polygon bounding boxes (distinctly Makie), - cgrad for two-stop continuous colormap, fig[row, col] spanning (1:3) for - shared colorbar across all columns, Point2f for polygon vertices. These - are Makie-specific features not easily replicated in matplotlib syntax. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - subplots - - colorbar - - annotations - - custom-legend - patterns: - - data-generation - - iteration-over-groups - dataprep: - - normalization - styling: - - custom-colormap - - alpha-blending - - edge-highlighting diff --git a/plots/map-drilldown-geographic/metadata/python/altair.yaml b/plots/map-drilldown-geographic/metadata/python/altair.yaml deleted file mode 100644 index c5178c2e8d..0000000000 --- a/plots/map-drilldown-geographic/metadata/python/altair.yaml +++ /dev/null @@ -1,269 +0,0 @@ -library: altair -language: python -specification_id: map-drilldown-geographic -created: '2026-01-20T07:44:04Z' -updated: '2026-05-23T09:32:34Z' -generated_by: claude-sonnet -workflow_run: 26328623315 -issue: 3770 -language_version: 3.13.13 -library_version: 6.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/altair/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/altair/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/altair/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/altair/plot-dark.html -quality_score: 81 -review: - strengths: - - 'Excellent idiomatic Altair: topo_feature + albersUSA projection + transform_lookup - for geographic join — genuinely distinctive Vega-Lite features' - - Smart hierarchical composition with numbered drill levels (①②③), breadcrumb path - display, and opacity-based selection highlighting — clearly communicates the interactive - intent - - 'Full palette compliance: anyplot_seq (#009E73→#003D94) for choropleth, anyplot - categorical palette in canonical order for region bars, correct theme tokens (PAGE_BG, - INK, INK_SOFT, ELEVATED_BG) throughout both renders' - - Genuine interactivity via selection_point + transform_filter — drill-down works - in HTML without any simulated/fake UI elements - - Canvas gate passed; pad-only logic correctly avoids AR-09 crop risk - weaknesses: - - 'State bar chart (② Region → States) uses height=75 with up to 13 West-region - states — at scale_factor=4 that is 300 px for 13 items, causing severe label crowding/overlap. - Fix: increase height to 130–150, or add .encode(y=alt.Y(..., scale=alt.Scale(padding=0.05))) - with step=14 via alt.Step(14) to auto-size the height to content.' - - labelFontSize=10 in configure_axis is borderline for the compact sidebar bars; - because each bar row is only ~23 px tall at saved resolution, labels touch or - overlap. Increasing the chart height (above) will resolve this without changing - font size. - - Sidebar vconcat spacing=7 is tight for the breadcrumb + three charts + note; consider - spacing=10 for a touch more breathing room. - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct anyplot light surface, no pure white. - Chrome: Title "map-drilldown-geographic · python · altair · anyplot.ai" in dark INK (#1A1A17), clearly readable. Subtitle in lighter INK_SOFT. Axis labels "Sales ($K)" on all three bar charts readable. Breadcrumb "USA ▸ West ▸ California ▸ Cities" visible. Region legend (right side) legible. Choropleth legend "Sales ($K)" at bottom-left readable with gradient bar. - Data: US choropleth using anyplot_seq gradient (#009E73 → #003D94) — green for low sales, dark blue for high. Region bars use Imprint palette in correct order: West=#009E73, South=#9418DB, Midwest=#B71D27, Northeast=#16B8F3. City bars use #009E73 (single series, correct). Bar opacity highlights selected items (West region, California state highlighted at full opacity). - Issue: "② Region → States" chart uses height=75 for up to 13 West-region states — labels are severely crowded and partially overlapping at saved resolution. - Legibility verdict: PASS for overall title/axes/legends. FAIL for state bar labels in chart ② (crowding/overlap for 13 states in 75px chart height). - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct anyplot dark surface, no pure black. - Chrome: Title and subtitle render in light INK (#F0EFE8) against dark background — clearly readable. Axis labels, tick labels, legend text all appear in light INK_SOFT (#B8B7B0) — no dark-on-dark failures observed. Legend frames use ELEVATED_BG (#242420) with INK_SOFT stroke — correct. - Data: Data colors are identical to the light render — anyplot_seq choropleth and categorical region bars use the same hex values (positions 1–4 of Imprint palette unchanged between themes). Only chrome flips. - Issue: Same state bar crowding as light render — the layout constraint is structural, not theme-specific. - Legibility verdict: PASS for chrome elements. FAIL for state bar labels in chart ② (same crowding issue, theme-independent). - criteria_checklist: - visual_quality: - score: 22 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 5 - max: 8 - passed: false - comment: Title, axis labels, and legends all readable. State bar labels in - chart ② are too small/crowded (13 states in height=75) — borderline legible - at full res, unreadable at mobile scale. - - id: VQ-02 - name: No Overlap - score: 3 - max: 6 - passed: false - comment: State bar chart (②) has 13 West-region states in height=75; at scale_factor=4 - (~23px per row vs 40px label height) labels overlap significantly. - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: Choropleth, region bars, and city bars all clearly visible. Bar opacity - condition (1.0 selected / 0.55 unselected) works well. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Imprint palette is CVD-safe. anyplot_seq for continuous choropleth. - No red-green-only signaling. - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: 'Canvas 3200x1800 confirmed (gate passed). Layout well-organized: - map left, sidebar right. Sidebar is compact but functional. Breadcrumb + - note add useful context.' - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title follows required format. All bar charts labeled 'Sales ($K)'. - Sub-chart titles use descriptive numbered labels. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: Choropleth uses anyplot_seq (#009E73→#003D94). Region bars use anyplot - palette positions 1-4 in canonical order. PAGE_BG and chrome tokens correct - in both themes. - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Above default: numbered hierarchy levels (①②③), breadcrumb path, - opacity-based selection highlighting, sub-chart titles. Thoughtful composition - above a generic bar chart.' - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: true - comment: Subtle grids (gridOpacity=0.10). Legend frames themed. cornerRadius - on bars. Organized layout. Could improve sidebar spacing. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: 'Clear visual narrative: top-level map → regional drill → state drill - → city drill. Selection state and breadcrumb communicate current context - effectively.' - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: 'Correct: hierarchical choropleth map with drill-down sidebar bars - covering Country→Region→State→City levels.' - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Choropleth coloring, multiple hierarchy levels, breadcrumb, color - legend, tooltips (HTML), non-clickable note for cities present. Smooth zoom - transitions only work in HTML (acceptable for static render). - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X=Sales($K), Y=regions/states/cities. Geographic data correctly joined - via transform_lookup using state FIPS ids. AlbersUSA projection correct - for US map. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title exactly 'map-drilldown-geographic · python · altair · anyplot.ai'. - Region legend labeled. Choropleth legend labeled 'Sales ($K)'. - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: All three hierarchy levels shown. Region aggregation computed via - groupby. City leaf nodes for 4 major states. Hierarchical parent-child structure - clear. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: US state-level sales ($K) data is realistic and neutral. Values plausible - (CA=520, TX=485, NY=425, FL=380 as top states). City-level data for major - metros realistic. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: State values 25-520 ($K), city values 28-335 ($K), regional totals - in 1000-3500 range. All scales appropriate for hypothetical regional sales - data. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Flat script, no unnecessary functions or classes. Clean Altair composition. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Fully deterministic — hardcoded data, no randomness needed. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: 'All imports used: os, sys, altair, pandas, PIL.Image.' - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: No fake interactivity — selection_point + transform_filter is genuine - Altair interactivity. Code is appropriately complex for this chart type. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html. Current Altair 6.1.0 - API used. - library_mastery: - score: 9 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: 'Excellent: topo_feature for TopoJSON loading, transform_lookup for - geographic join, selection_point with default values, transform_filter for - reactive filtering, vconcat/hconcat composition, resolve_legend — all idiomatic - Vega-Lite/Altair patterns.' - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: 'albersUSA projection, topo_feature, linked brushing via selection_point - across hconcat panels — these are genuinely distinctive Altair capabilities. - Minor deduction: could use alt.Step() for auto-height on bar charts.' - verdict: APPROVED -impl_tags: - dependencies: - - pillow - techniques: - - hover-tooltips - - html-export - patterns: - - groupby-aggregation - dataprep: [] - styling: - - alpha-blending - - custom-colormap diff --git a/plots/map-drilldown-geographic/metadata/python/bokeh.yaml b/plots/map-drilldown-geographic/metadata/python/bokeh.yaml deleted file mode 100644 index d9bd2fe629..0000000000 --- a/plots/map-drilldown-geographic/metadata/python/bokeh.yaml +++ /dev/null @@ -1,275 +0,0 @@ -library: bokeh -language: python -specification_id: map-drilldown-geographic -created: '2026-01-20T07:42:35Z' -updated: '2026-05-23T10:17:27Z' -generated_by: claude-sonnet -workflow_run: 26329149186 -issue: 3770 -language_version: 3.13.13 -library_version: 3.9.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/bokeh/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/bokeh/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/bokeh/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/bokeh/plot-dark.html -quality_score: 88 -review: - strengths: - - Full 3-level drill-down hierarchy (World → Country → State → City) implemented - via TapTool + CustomJS with dynamic x_range/y_range zoom transitions - - Correct use of anyplot_seq continuous colormap with LinearColorMapper for choropleth - encoding - - Differentiated drillable vs leaf-country rendering (solid border + full alpha - vs dashed + 45% alpha) provides clear UX affordance - - 'Narrative annotation ★ United States leads — $850M in brand #009E73 creates a - clear focal point and visual hierarchy' - - Breadcrumb navigation Title element updates dynamically, and the Back button label - encodes navigation level for clean state management - - 'Theme-adaptive chrome fully wired: INK/INK_SOFT/ELEVATED_BG/PAGE_BG tokens applied - to all text renderers, backgrounds, and borders in both renders' - - OCEAN_BG background for the map area provides geographic context (ocean vs land) - as a distinctive design choice - weaknesses: - - 'CQ-04 slight repetition: each country level repeats the same ColumnDataSource - + patches + text renderer pattern 5 times with minor variation — a small shared - helper or loop would reduce about 120 lines while keeping the code flat' - - 'VQ-02 minor: overlapping rectangular country patches (Canada/US top boundary, - US/Mexico bottom boundary) create slight visual noise — consider adding a thin - INK_SOFT outline with higher z-order or a subtle separation at overlap zones' - - 'VQ-01 minor: instruction subtitle at 24pt (below) appears noticeably smaller - relative to the 50pt title and 42pt axis labels; increasing to ~28–30pt would - improve proportional balance' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) border/margin area; light ocean blue (#D6EAF8) for the geographic map region — a deliberate design choice to represent ocean vs land. - Chrome: Title "map-drilldown-geographic · python · bokeh · anyplot.ai" in dark INK (#1A1A17) at 50pt, centered — fully visible, ~70% of plot width as expected for the mandated title. Breadcrumb "📍 World" in dark text at top-left. Axis labels "Longitude" and "Latitude" in INK at 42pt, clearly readable. Tick labels in INK_SOFT at 34pt. Instruction subtitle at bottom in INK_SOFT at 24pt, readable. ColorBar on right with "Sales ($M)" label in dark text, clearly legible. - Data: Five rectangular country patches using anyplot_seq colormap (brand green #009E73 at low values through to dark azure for high values). United States (darkest, $850M), Canada (medium-light, $320M), Brazil (medium, $420M), Mexico (light green, $180M), Argentina (light green/teal, $95M). Country name labels and value labels visible with ELEVATED_BG (#FFFDF6) fill. "United States leads — $850M" annotation in #009E73 with green border. Leaf countries (Mexico, Brazil, Argentina) rendered with dashed borders and 45% opacity, signaling no drill-down. Drillable countries (US, Canada) at 85% opacity with solid borders. - Legibility verdict: PASS — all text elements are clearly readable against the light background, no light-on-light issues, text labels protected by ELEVATED_BG backgrounds. - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) border/margin area; dark ocean (#1C2833) for the geographic map region. - Chrome: Title in light INK (#F0EFE8), clearly visible against dark background. Breadcrumb "📍 World" in light text. Axis labels and tick labels in INK_SOFT (#B8B7B0), legible on dark surface. Instruction subtitle at bottom in INK_SOFT, readable. ColorBar labels in INK_SOFT, legible. - Data: Choropleth colors identical to light render — same anyplot_seq green-to-azure progression. Country labels visible with ELEVATED_BG (#242420) dark fill. Annotation "United States leads — $850M" remains in #009E73 (brand green holds on dark surface). Data colors are identical between both renders. - Legibility verdict: PASS — all text readable against the dark background; no dark-on-dark failures detected; INK/INK_SOFT tokens correctly applied throughout all text renderers. - criteria_checklist: - visual_quality: - score: 28 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: 'All font sizes explicitly set per style guide (50pt title, 42pt - axis labels, 34pt ticks). Readable in both themes. Minor: instruction subtitle - at 24pt is somewhat smaller relative to overall scale.' - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: Text labels protected by ELEVATED_BG fills, no text-on-text overlap. - Minor visual noise from overlapping rectangular country patches (US-Canada, - US-Mexico boundaries). - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Choropleth regions clearly visible. Country name and value labels - prominent with background fills. ColorBar fully legible. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: anyplot_seq (green-to-azure) is colorblind-safe. No red-green as - sole signal. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Canvas gate passed. min_border_* reservations properly accommodate - large fonts. Layout fills canvas well. ColorBar at right properly contained - within min_border_right=280. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: 'Descriptive axis labels: Longitude, Latitude. Title correctly formatted.' - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'anyplot_seq continuous colormap used (green #009E73 to dark azure - #003D94). Background #FAF8F1 light / #1A1A17 dark. Theme-adaptive chrome - fully wired. Data colors identical across both renders.' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Above default (4): custom anyplot_seq colormap, ocean background - for geographic context, narrative annotation in brand green, differentiated - drillable vs leaf rendering. Strong but not at publication-ready level.' - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: true - comment: 'Above default (2): ocean background differentiates land/sea, subtle - 10% alpha grid, elevated backgrounds for text labels, custom border styling. - Map frame (all spines) appropriate for geographic plot.' - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: 'Above default (2): clear focal point via annotation, color scale - creates natural hierarchy (darkest=highest value), breadcrumb+drill-down - provides UX narrative, leaf vs drillable distinction guides viewer.' - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: 'Correct: hierarchical drillable geographic choropleth with 3 levels - (world, country, state/province, city)' - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: 'All features present: click drill-down (TapTool+CustomJS), breadcrumb - navigation (dynamic Title), zoom transitions (x/y range updates), color - legend (ColorBar), tooltips (HoverTool), leaf node indication (dashed+low - alpha)' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Geographic coordinates correct (longitude/latitude). Values mapped - to color scale. All hierarchical levels accessible. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title: ''map-drilldown-geographic · python · bokeh · anyplot.ai'' - — correct format. ColorBar labeled ''Sales ($M)''.' - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Full 3-level hierarchy shown (World→Country, US→State, Canada→Province, - CA→City, BC→City). Both drillable and leaf nodes present. Value aggregation - from children to parents implicit in data. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Americas annual sales ($M) — real, neutral, comprehensible business - context. Real country/state/city names with plausible values. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: US highest at $850M, Canada $320M, Brazil $420M, Mexico $180M, Argentina - $95M — plausible proportions for Americas region sales. State/city values - appropriately sum to parent values. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'No functions or classes. Flat: imports → theme tokens → data → figure - → renderers → callbacks → save.' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set, though data is fully deterministic (no random - calls). - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports used. base64, os, time, pathlib, numpy, bokeh modules, - selenium — all required. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Real interactivity via CustomJS. Some repetition across country levels - is inherent to the hierarchical data structure and acceptable. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html. CDP screenshot for - exact canvas clipping. - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: 'Excellent idiomatic bokeh: ColumnDataSource, CDSView/BooleanFilter, - TapTool+CustomJS, HoverTool, ColorBar, Title layout element, dynamic Range - updates. Above default (3).' - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: 'Very distinctive: interactive drill-down via CustomJS callbacks, - CDSView/BooleanFilter for hierarchical layer management, TapTool for click - navigation, dynamic x_range/y_range zoom, Button widget integration — features - distinctly bokeh.' - verdict: APPROVED -impl_tags: - dependencies: - - selenium - techniques: - - colorbar - - hover-tooltips - - html-export - - annotations - patterns: - - data-generation - - columndatasource - dataprep: [] - styling: - - custom-colormap - - alpha-blending diff --git a/plots/map-drilldown-geographic/metadata/python/letsplot.yaml b/plots/map-drilldown-geographic/metadata/python/letsplot.yaml deleted file mode 100644 index a4c3a36bc9..0000000000 --- a/plots/map-drilldown-geographic/metadata/python/letsplot.yaml +++ /dev/null @@ -1,258 +0,0 @@ -library: letsplot -language: python -specification_id: map-drilldown-geographic -created: '2026-01-20T07:45:57Z' -updated: '2026-05-23T18:39:04Z' -generated_by: claude-sonnet -workflow_run: 26328800084 -issue: 3770 -language_version: 3.13.13 -library_version: 4.10.1 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/letsplot/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/letsplot/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/letsplot/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/letsplot/plot-dark.html -quality_score: 81 -review: - strengths: - - Correct anyplot_seq gradient (#009E73→#003D94) applied consistently across both - panels and both themes - - Theme-adaptive chrome implemented correctly — both light and dark renders pass - legibility checks with no dark-on-dark failures - - Purple border stroke (#9418DB) is an elegant visual signal for drillable vs. leaf-node - regions - - Two-panel gggrid layout effectively communicates the world→state drill-down hierarchy - with consistent color scale - - layer_tooltips() provides genuine interactivity (title + line format) in the HTML - export - - Breadcrumb subtitle follows spec requirement; drillable/non-drillable distinction - is explicit - - Clean KISS code structure with correct reproducibility (np.random.seed(42)) and - plausible neutral business data - weaknesses: - - US state drill-down panel title 'USA → State Level (drill-down)' does not follow - required format — should be 'map-drilldown-geographic · python · letsplot · anyplot.ai' - (breadcrumb can move to subtitle) - - Third administrative level (city) mentioned in spec is absent; inclusion would - improve DQ-01 feature coverage - - European country labels (UK, France, Germany, Italy, Spain) are geographically - compressed; increase label_offset distances to reduce crowding risk - - US drill-down panel is visually narrow at 1/3 canvas width; increase geom_text - size from 10→12 and US panel title size from 13→15 to improve mobile readability - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct anyplot light surface - Chrome: Title 'map-drilldown-geographic · python · letsplot · anyplot.ai' visible in dark INK at size=16; subtitle in INK_SOFT below. Country name labels (dark text) with custom offsets. State abbreviations in dark text. Legend 'Sales (M $)' readable. All text elements clearly readable against light background. - Data: Sequential green (#009E73) to dark-blue (#003D94) gradient encodes sales value on bubble fills. USA and Canada have purple (#9418DB) border stroke indicating drillability. Continent outlines in muted taupe. 16 country bubbles on world panel; 10 US state bubbles on right panel. Bubble sizes scaled by value (5-18px world, 5-16px states). - Legibility verdict: PASS — no light-on-light failures, all text readable - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct anyplot dark surface - Chrome: Title, country labels, state abbreviations all shift to light text (#F0EFE8 / #B8B7B0). Continent fills darken to #2E2E2A; border colors adapt. Legend background uses ELEVATED_BG #242420. All text readable against dark background — no dark-on-dark failures observed. - Data: Bubble fill gradient colors are visually identical to light render (#009E73→#003D94). Purple drillability strokes (#9418DB) remain visible. Data encoding consistent across themes. - Legibility verdict: PASS — no dark-on-dark failures, brand green clearly visible on dark surface - criteria_checklist: - visual_quality: - score: 25 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 6 - max: 8 - passed: true - comment: All font sizes explicitly set; readable in both themes. US panel - text slightly undersized due to narrow panel width. - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: Custom label offsets prevent collisions. European labels are dense - but individually readable. - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: Bubble sizes 5-18px well-adapted to data range. Small leaf countries - visible but small. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: anyplot_seq gradient is colorblind-safe. Purple drillability indicator - has adequate contrast. - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: 2:1 gggrid appropriate. Canvas ~65-70% utilized. Some whitespace - due to map aspect ratio constraints. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: theme_void appropriate for maps; legend title has units 'Sales (M - $)'. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'anyplot_seq (#009E73→#003D94). Backgrounds #FAF8F1/#1A1A17. Chrome - tokens correct in both renders.' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Custom continent outlines, purple drillability accent, theme_void, - two-panel hierarchy. Strong design above defaults. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: theme_void removes chart chrome; custom legend positioning; elevated - legend background; deliberate text hierarchy. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Purple border signals drillability; two-panel hierarchy explicit; - breadcrumb subtitle; consistent cross-level color scale. - spec_compliance: - score: 12 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 4 - max: 5 - passed: true - comment: Correct hierarchical bubble-choropleth map at two levels. Third level - (city) absent. Valid adaptation for semi-interactive library. - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Breadcrumb, color legend, tooltips, drillable distinction, consistent - color scale all present. True click drill-down and city level absent. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Lat/lon correct; fill and size both encode value; all data visible. - - id: SC-04 - name: Title & Legend - score: 2 - max: 3 - passed: true - comment: World panel title format correct. US panel title 'USA → State Level - (drill-down)' does not follow required format. - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: Two hierarchical levels shown; drillable vs. leaf distinction; value - variation across regions. Missing optional third level. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Sales performance by country/state — neutral business scenario with - real geographic names and plausible magnitudes. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Country and state sales values reflect plausible relative economic - scale; state totals approximately match US total. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear flow: imports → tokens → data → world plot → US plot → gggrid - → save. No functions/classes.' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set; data manually specified (deterministic). - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imported symbols are used; no extraneous imports. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean layered ggplot composition. Label offset dict is idiomatic. - No over-engineering. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html with correct naming. - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: ggplot layering, gggrid, coord_fixed, layer_tooltips chaining — all - idiomatic letsplot patterns. - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: layer_tooltips() with .title().line() chaining and simultaneous PNG+HTML - export leverage letsplot-specific capabilities. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - layer-composition - - hover-tooltips - - html-export - - subplots - patterns: - - data-generation - dataprep: [] - styling: - - minimal-chrome - - alpha-blending - - edge-highlighting - - custom-colormap diff --git a/plots/map-drilldown-geographic/metadata/python/matplotlib.yaml b/plots/map-drilldown-geographic/metadata/python/matplotlib.yaml deleted file mode 100644 index 8e17843cd3..0000000000 --- a/plots/map-drilldown-geographic/metadata/python/matplotlib.yaml +++ /dev/null @@ -1,269 +0,0 @@ -library: matplotlib -language: python -specification_id: map-drilldown-geographic -created: '2026-01-20T07:43:08Z' -updated: '2026-05-23T09:20:33Z' -generated_by: claude-sonnet -workflow_run: 26328465333 -issue: 3770 -language_version: 3.13.13 -library_version: 3.10.9 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/matplotlib/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/matplotlib/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 83 -review: - strengths: - - Three-level drill-down hierarchy clearly communicated via side-by-side panels - with directional arrows - - Consistent anyplot_seq colormap across all hierarchy levels enables cross-level - comparison - - FancyBboxPatch with rounded corners and HIGHLIGHT color (#9418DB) for selected - regions creates stylistic cohesion - - Breadcrumb navigation shown as static text at bottom of each panel - accurate - static adaptation of the interactive spec - - PatchCollection and FancyArrowPatch with fig.transFigure transform demonstrate - idiomatic matplotlib usage - - 'Perfect theme adaptation: warm off-white/near-black backgrounds, all chrome tokens - correctly applied in both renders' - - Colorbar with global normalization allows meaningful comparison across all three - administrative levels - weaknesses: - - Box text fontsize=6 for value labels may be challenging to read at mobile widths - (~400px scaled PNG) - - 'SC-01 deduction: geographic boxes instead of actual geographic shapes loses the - map character of the spec' - - Colorbar tick labels mix units inconsistently ($1.2M at bottom, $157B and $312B - at top) which may confuse readers - - 'DQ-03 minor: scale ratio of ~260,000x between Santa Monica ($1.2M) and Asia ($312B) - is extremely wide and somewhat unrealistic for a single company dataset' - - 'DE-01 not exceptional: design is polished but not reaching publication-ready - territory (no geographic visual metaphor)' - - Panel 1 layout slightly dense - 6 regions spread across 3 columns with some visual - gaps vs. panels 2-3 which are more compact - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct light theme surface, no pure white. - Chrome: Title "map-drilldown-geographic · python · matplotlib · anyplot.ai" at fontsize=12 reads clearly in dark ink (#1A1A17). Subtitle in INK_SOFT is legible. Panel sub-titles ("Level 1: World Regions", "Level 2: US States", "Level 3: CA Cities") in bold dark ink are prominent and well-readable. Breadcrumb texts at bottom of each panel ("World", "World ▶ United States", "World ▶ United States ▶ California") in INK_MUTED italic are visible. Colorbar label "Annual Sales" and tick labels ($1.2M, $157B, $312B) readable in INK_SOFT. - Data: Colored boxes using anyplot_seq (green #009E73 at low values → dark azure #003D94 at high values). Box labels (region abbreviations) and values in BOX_TEXT (#F0EFE8) provide good contrast against both green and blue box backgrounds. Purple (#9418DB) highlight borders mark selected regions (N.AM in panel 1, CA in panel 2). Purple arrows connect panels indicating drill-down direction. No axes, all panels use axes-off style appropriate for this layout. - Legibility verdict: PASS — all text clearly readable against light background. No light-on-light issues. - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct dark theme surface, no pure black. - Chrome: Title, panel titles, breadcrumbs, colorbar labels all render in light tones (INK = #F0EFE8 / INK_SOFT = #B8B7B0). No dark-on-dark failures detected. All text elements flip correctly to light on the dark surface. - Data: Colored boxes identical to light render — anyplot_seq colormap (green → azure) unchanged between themes. BOX_TEXT (#F0EFE8) on colored boxes maintains good readability. Purple highlights and arrows unchanged. Data colors are identical between themes — only chrome flips. - Legibility verdict: PASS — all text readable against dark background. No dark-on-dark failures observed. - criteria_checklist: - visual_quality: - score: 28 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 6 - max: 8 - passed: true - comment: All font sizes explicitly set (6-12pt). Readable at full resolution. - fontsize=6 for box values is a bit small for mobile scaling but acceptable - at 3200px. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text or elements detected in either render. Grid layout - prevents collisions. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Colored boxes clearly visible with good contrast. anyplot_seq provides - clear differentiation from green to dark azure. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: anyplot_seq is CVD-safe. Purple highlights contrast well. No red-green - sole signaling. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Canvas gate passed. Three-panel layout uses canvas area well. Colorbar - properly positioned. Good proportions. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: 'Axes off — panel titles are descriptive (''Level 1: World Regions'', - etc.). Colorbar labeled ''Annual Sales''.' - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: anyplot_seq used for continuous data. PAGE_BG correct for both themes. - All chrome tokens correctly applied. Data colors identical between light - and dark renders. - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Strong design: cohesive HIGHLIGHT color for selections and arrows, - consistent anyplot_seq scale, rounded corners add polish. Above defaults - but not publication-ready.' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Axes completely off (minimal chrome), no grid, rounded FancyBboxPatch, - subtle colorbar outline. Good refinement. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Drill-down narrative clearly communicated through panel progression, - arrows, highlights, and breadcrumbs. Clear focal points. - spec_compliance: - score: 12 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 3 - max: 5 - passed: true - comment: Correct hierarchical concept with choropleth coloring, but geographic - boxes instead of actual map shapes lose the geographic character of the - spec. - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: 'Breadcrumbs present, consistent color scale, colorbar shown. Inherently - missing: zoom transitions, hover tooltips (static library), and graceful - handling of leaf-node regions.' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Three-level hierarchy correctly shown. Color mapping consistently - encodes annual sales. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title matches required format exactly. Colorbar labeled 'Annual Sales'. - data_quality: - score: 13 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: 'Shows all three hierarchy levels with good regional/state/city coverage. - Missing: leaf-node regions with no sub-level data.' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Annual sales drill-down (World → North America → California) is a - standard and realistic business intelligence scenario. - - id: DQ-03 - name: Appropriate Scale - score: 3 - max: 4 - passed: true - comment: Regional proportions broadly plausible. Scale ratio of ~260,000x - between Santa Monica ($1.2M) and Asia ($312B) is very extreme for a single - dataset. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Clean Imports → Data → Plot → Save. No functions or classes. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: All data hardcoded — fully deterministic, no random elements. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: 'All imports used: os, mpatches, plt, PatchCollection, LinearSegmentedColormap, - Normalize.' - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Three parallel panel-building blocks are repetitive but appropriate - for the visualization structure. No fake interactive elements. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png, dpi=400, no bbox_inches='tight'. Correct. - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Uses add_gridspec, PatchCollection, FancyBboxPatch, FancyArrowPatch, - ScalarMappable, Normalize — strong mid-to-high-level matplotlib idioms. - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: FancyArrowPatch with transform=fig.transFigure for cross-panel arrows, - fig.patches.append() for figure-level elements, and PatchCollection for - batch patch rendering are distinctively matplotlib. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - colorbar - - patches - - subplots - - annotations - patterns: - - iteration-over-groups - - explicit-figure - dataprep: - - normalization - styling: - - custom-colormap - - minimal-chrome - - edge-highlighting diff --git a/plots/map-drilldown-geographic/metadata/python/plotly.yaml b/plots/map-drilldown-geographic/metadata/python/plotly.yaml deleted file mode 100644 index 618362d5ae..0000000000 --- a/plots/map-drilldown-geographic/metadata/python/plotly.yaml +++ /dev/null @@ -1,273 +0,0 @@ -library: plotly -language: python -specification_id: map-drilldown-geographic -created: '2026-01-20T07:41:27Z' -updated: '2026-05-23T09:16:56Z' -generated_by: claude-sonnet -workflow_run: 26328545263 -issue: 3770 -language_version: 3.13.13 -library_version: 6.7.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/plotly/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/plotly/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/plotly/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/plotly/plot-dark.html -quality_score: 88 -review: - strengths: - - 'Correct anyplot_seq colorscale for choropleth and city scatter with #009E73→#003D94 - gradient' - - 'Proper theme-adaptive chrome: warm off-white (#FAF8F1) light / near-black (#1A1A17) - dark backgrounds with all INK/INK_SOFT tokens threaded through title, colorbar, - dropdown, and spotlight labels' - - 'Clever spotlight annotation layer using a third Scattergeo trace with semantic - palette colors (#99B314 lime for top performers, #B71D27 red for lowest) — creates - an immediate data story focal point' - - 'Interactive drill-down via updatemenus dropdown: trace visibility toggling, geo.center - + geo.projection.scale for per-state zoom, and title updates mimicking breadcrumb - context' - - 'Idiomatic Plotly geo stack: go.Choropleth with locationmode=''USA-states'' + - go.Scattergeo city overlay + styled colorbar with bordered ELEVATED_BG fill' - - 'Clean flat code: imports → data → traces → layout → save — no functions or classes' - - 'Canvas dimensions correct: 3200×1800 via width=800, height=450, scale=4 with - autosize=False' - - Both HTML and PNG outputs generated correctly - weaknesses: - - 'Breadcrumb navigation (spec requirement): spec asks for clickable breadcrumb - segments (e.g. ''World > United States > California''). Implementation uses title - text updates (''United States > {State} | City-Level Sales'') and a dropdown — - close equivalent, but not true clickable breadcrumbs. A Plotly workaround is to - add a secondary updatemenus row styled as a breadcrumb bar with ''Back to All - States'' button.' - - Spotlight annotation text is small (textfont size=9, 36 source-px at scale=4). - For a 3200×1800 canvas this is borderline readable. Increasing to size=10 or 11 - would improve legibility without crowding the map. - - 'DQ-01: only two interactive levels (all-states choropleth → individual state - with city dots). Spec envisions three levels (world → country → state). Consider - adding a top-level global choropleth view with US highlighted as the first level - before the US-states view.' - - 'DE-01: design is above-average but not publication-quality. The map would benefit - from a subtle annotation box or summary stat (e.g. total US sales or regional - percentage) to elevate storytelling beyond the spotlight markers.' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct; geo land area rendered in a warm tan (rgb(228,224,210)), lakes in pale blue (rgb(197,220,245)), confirming all theme tokens applied - Chrome: Title "map-drilldown-geographic · python · plotly · anyplot.ai" at font=16 clearly readable in dark ink (#1A1A17). Subtitle "US Sales by State — use dropdown to drill into cities" readable. Colorbar "Sales ($K)" title and tick labels (1500/2000/2500) visible with INK_SOFT color. Dropdown button "All States" clearly readable. - Data: 10 US states colored by total sales via anyplot_seq (#009E73 green at low end → #003D94 dark blue at high end). California and Texas appear in dark blue (high sales), Michigan in green (lowest). Spotlight markers: lime star for "★ Top: CA $2,850K" and "★ 2nd: TX $2,790K"; red triangle for "▼ Low: Mi $1,340K" — all clearly visible. - Legibility verdict: PASS — all text elements are readable against the warm off-white background. No light-on-light failures. - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct; geo land area in dark warm tone (rgb(40,38,33)), lakes in dark navy (rgb(20,40,65)) — all theming applied. - Chrome: Title and subtitle rendered in cream/off-white (#F0EFE8 INK token) — clearly readable against dark background. Colorbar tick labels and title visible in light color. Dropdown button "All States" styled with ELEVATED_BG (#242420) fill — text readable. - Data: Choropleth colors are identical to light render — same anyplot_seq green-to-blue gradient confirms data palette stability across themes. Spotlight marker colors (#99B314 lime stars, #B71D27 red triangle) visually identical. Annotation text in light ink. - Legibility verdict: PASS — no dark-on-dark failures observed. All title, subtitle, colorbar, dropdown, and spotlight annotation text clearly readable on dark background. - criteria_checklist: - visual_quality: - score: 29 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: All font sizes explicitly set (title=16, colorbar title=12, ticks=10, - spotlight=9). Readable in both themes. Spotlight annotation text at size=9 - (36 source-px) is borderline small for the canvas but not a failure. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: Spotlight markers on different states (CA, TX, MI) — no text collision. - No city text visible in default view (cities hidden initially). - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Choropleth states clearly colored. Spotlight markers prominent. Colorbar - visible. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: anyplot_seq is colorblind-safe. Spotlight uses both shape (star vs - triangle) and color for distinction. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 3200×1800 correct. Map fills canvas well. Colorbar on right. Dropdown - top-left. No clipping. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title correct format. Colorbar labeled 'Sales ($K)' with units. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'anyplot_seq colorscale. Background #FAF8F1 light / #1A1A17 dark. - INK/INK_SOFT tokens applied throughout. Spotlight markers use Imprint palette - colors (#99B314, #B71D27).' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Above default: themed geo backgrounds (land/lakes/ocean), semantic - spotlight markers with palette colors, styled colorbar and dropdown. Not - publication-quality but clearly above generic defaults.' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Geographic map has no spines to remove. Colorbar has styled borders - (ELEVATED_BG fill, INK_SOFT border). Dropdown themed. Explicit margins set. - Above minimal customization. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: 'Spotlight markers for top-2 and lowest performers create clear focal - point. Drill-down hierarchy tells progressive story. Top: CA $2,850K / 2nd: - TX $2,790K / Low: MI $1,340K immediately visible.' - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Geographic choropleth map with drill-down interaction. Correct type. - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: 'Choropleth coloring ✓, drill-down navigation ✓, color legend ✓, - tooltips ✓. Missing: true clickable breadcrumb navigation — title text updates - used instead.' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: State values → choropleth color. City values → scatter size + color. - Geographic coordinates accurate. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title: ''map-drilldown-geographic · python · plotly · anyplot.ai''. - Colorbar labeled ''Sales ($K)''.' - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: Shows state choropleth + city scatter + spotlight annotations. Two - interactive levels (all-states → individual state zoom). Spec envisions - three levels. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: US regional sales data by state and city. Neutral, realistic, business - scenario. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: City sales $130K-$950K, state totals $1.3M-$2.85M. Realistic range - for regional sales data. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Flat code: imports → tokens → data → traces → layout → save.' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) present; all data values are hardcoded and deterministic. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only os, numpy, plotly.graph_objects — all used. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean Plotly code. Real interactive features via updatemenus — no - fake UI. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: plot-{THEME}.png and plot-{THEME}.html correctly saved. autosize=False - with explicit dimensions. - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: go.Choropleth with locationmode='USA-states', go.Scattergeo overlay, - updatemenus for interaction, geo dict for projection control. All idiomatic - geo patterns. - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: updatemenus with multi-trace visibility toggling. geo.center + geo.projection.scale - for state-level zoom. Three Scattergeo layers with conditional visibility. - Themed geo backgrounds (land/lake/ocean/border colors). - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - colorbar - - hover-tooltips - - html-export - patterns: - - iteration-over-groups - dataprep: [] - styling: - - custom-colormap - - edge-highlighting diff --git a/plots/map-drilldown-geographic/metadata/python/plotnine.yaml b/plots/map-drilldown-geographic/metadata/python/plotnine.yaml deleted file mode 100644 index 27ad8c901c..0000000000 --- a/plots/map-drilldown-geographic/metadata/python/plotnine.yaml +++ /dev/null @@ -1,251 +0,0 @@ -library: plotnine -language: python -specification_id: map-drilldown-geographic -created: '2026-01-20T07:44:29Z' -updated: '2026-05-23T09:40:02Z' -generated_by: claude-sonnet -workflow_run: 26328663425 -issue: 3770 -language_version: 3.13.13 -library_version: 0.15.4 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/plotnine/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/plotnine/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 81 -review: - strengths: - - Correct anyplot_seq colormap (green to azure) applied consistently across both - themes - - 'Top-3/bottom-3 performer highlights using Imprint palette colors (lime #99B314, - red #B71D27) create immediate visual storytelling' - - Breadcrumb annotation World > USA > States elegantly conveys hierarchical context - without faking interactivity - - 'Ocean panel background (#D6EAF8 / #1A2A3A) enhances the map aesthetic and theme-adapts - correctly' - - All chrome tokens (INK, INK_SOFT, ELEVATED_BG, PAGE_BG) correctly applied in both - light and dark renders - - Centroid nudging for crowded northeastern states reduces label overlap - - 'Perfect code quality: KISS structure, seed, clean imports, idiomatic plotnine - grammar' - weaknesses: - - 'SC-01/SC-02: Drill-down interactivity (click navigation, zoom transitions, hover - tooltips) cannot be implemented in plotnine — static snapshot only shows one hierarchy - level' - - 'DE-01: Design is above default but not publication-quality — outer spines could - be removed for a cleaner map aesthetic (panel_border=element_blank())' - - Minor label crowding in the northeastern US cluster (OH/PA at similar latitudes - ~40.8-40.9N) - - State polygon shapes are noticeably simplified (Colorado as a perfect rectangle) - reducing geographic realism - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 (plot background), light ocean-blue #D6EAF8 (panel/ocean) - Chrome: Title "map-drilldown-geographic · python · plotnine · anyplot.ai" in dark #1A1A17 — readable. Axis labels "Longitude"/"Latitude" in dark INK — readable. Tick labels (120°W, 25°N etc.) in #4A4A44 INK_SOFT — readable. Legend "Performance Score" in dark INK — readable. - Data: State polygons filled with anyplot_seq gradient (#009E73 green → #003D94 dark azure). Top-3 states (CA, VA, NY) have lime #99B314 outlines; bottom-3 (AZ, IN, FL) have red #B71D27 outlines. White bold abbreviation+value labels on each state centroid. - Legibility verdict: PASS — all text clearly readable against light background - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 (plot background), dark navy #1A2A3A (panel/ocean) - Chrome: Title in light #F0EFE8 — readable. Axis labels in light INK — readable. Tick labels in #B8B7B0 INK_SOFT — readable. Legend text in secondary ink — readable. No dark-on-dark failures observed. - Data: Colors identical to light render — anyplot_seq gradient, lime/red outlines, white state labels all unchanged. - Legibility verdict: PASS — all text clearly readable against dark background - criteria_checklist: - visual_quality: - score: 28 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: All font sizes explicitly set; state labels size=4 small but readable - as bold white on colored polygons - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: Minor crowding in northeastern US cluster (OH/PA); centroid nudges - help but labels are close - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: State polygons clearly visible; highlight outlines prominent; colorbar - well-defined - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: anyplot_seq gradient colorblind-safe; lime/red highlights distinguishable; - white labels adequate contrast - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 3200x1800 correct; map fills canvas well; legend right; no clipping - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Standard geographic labels Longitude/Latitude; degree info in tick - labels - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'anyplot_seq cmap used; backgrounds #FAF8F1/#1A1A17; all chrome tokens - theme-correct in both renders' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Above default: top/bottom outlines, ocean panel background, intentional - colormap. Not publication-quality.' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Panel border retained (ok for map); subtle grid; refined legend. - Could remove outer spines. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Clear story via lime/red highlights; breadcrumb context; visual hierarchy - present - spec_compliance: - score: 11 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 3 - max: 5 - passed: true - comment: Correct choropleth base type; drill-down impossible for static plotnine - — static snapshot per library rules - - id: SC-02 - name: Required Features - score: 2 - max: 4 - passed: true - comment: Static features present (choropleth, breadcrumb, legend); interactive - features absent (unavoidable) - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Longitude/Latitude/value correctly mapped; all 20 states visible - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title correct format; legend Performance Score with gradient - data_quality: - score: 12 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: Hierarchy via breadcrumb; full value range; top/bottom highlights; - geographic distribution - - id: DQ-02 - name: Realistic Context - score: 4 - max: 5 - passed: true - comment: Real US states; plausible performance scores; somewhat generic metric - name - - id: DQ-03 - name: Appropriate Scale - score: 3 - max: 4 - passed: true - comment: Score range 64-91 realistic; polygons simplified — Colorado is a - rectangle - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Clean Imports->Data->Plot->Save; no functions/classes - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean Pythonic code; centroid nudge dict is appropriate workaround - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png; current plotnine API - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Layered ggplot grammar; aes(group=state) for polygons; coord_fixed(); - multiple geom_polygon layers - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: geom_polygon with group aesthetic + coord_fixed() is distinctive - ggplot/plotnine geographic pattern - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - colorbar - - annotations - - layer-composition - - manual-ticks - patterns: - - data-generation - dataprep: [] - styling: - - custom-colormap - - alpha-blending diff --git a/plots/map-drilldown-geographic/metadata/python/pygal.yaml b/plots/map-drilldown-geographic/metadata/python/pygal.yaml deleted file mode 100644 index bdf6021281..0000000000 --- a/plots/map-drilldown-geographic/metadata/python/pygal.yaml +++ /dev/null @@ -1,287 +0,0 @@ -library: pygal -language: python -specification_id: map-drilldown-geographic -created: '2026-01-20T07:44:21Z' -updated: '2026-05-23T20:51:48Z' -generated_by: claude-sonnet -workflow_run: 26342208905 -issue: 3770 -language_version: 3.13.13 -library_version: 3.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/pygal/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/pygal/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/pygal/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/pygal/plot-dark.html -quality_score: 79 -review: - strengths: - - Correct anyplot_seq-derived choropleth palette (#009E73→#0A5AC4) applied to continuous - sales data; sequential coloring effectively encodes magnitude - - All font sizes explicitly set (title_font_size=66, label_font_size=56, legend_font_size=44, - value_font_size=36) and rendered in correct proportions for a 3200×1800 canvas - - HTML drilldown has full breadcrumb navigation with clickable crumb segments, Back - button, CSS fade transition, color scale legend gradient, and graceful leaf-level - handling - - All parent values correctly aggregate child values (US $2100M = CA $680M + TX - $520M + NY $580M + FL $320M; all levels verified consistent) - - Realistic, neutral business context (global sales by region) with real city/state/country - names; data structure accurately reflects geographic hierarchy - - pygal's native SVG interactivity (hover tooltips on every tile/bar) is preserved - and leveraged in the HTML embed without any manual simulation - weaknesses: - - Static PNG uses a Treemap rather than an actual geographic map — pygal supports - pygal.maps.world.World() and pygal.maps.us.States() which could show real country/state - shapes; the treemap captures size-by-value but loses geographic context entirely - for the static render - - 'Misleading comment on line 216: ''# print_values disabled to avoid dark-ink-on-dark-cell - contrast failure in dark theme'' — but print_values=True is actually set; the - comment describes the opposite of what the code does and should be removed or - corrected' - - Dark render value labels on deep-blue choropleth tiles (#0A5AC4, US cells) have - reduced contrast — dark ink (#1A1A17) on dark-blue yields ~2.5:1 contrast ratio, - below WCAG 4.5:1; consider forcing value_font_color to INK token (#F0EFE8) for - dark mode, or disabling print_values in dark mode as the comment intended - - Per-bar coloring workaround (creating N single-value series each with N-1 Nones) - is verbose — a shorter approach would build the palette from the style colors - directly; repeated Style instantiation inside the loop adds clutter - - HTML drilldown uses a fade transition (opacity 0→1) rather than a zoom transition - — the spec explicitly requests 'smooth zoom transitions when drilling down to - maintain spatial context'; no spatial continuity is preserved - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct per style guide; no pure white. - Chrome: Title "map-drilldown-geographic · python · pygal · anyplot.ai" renders in dark near-black ink at top-center; clearly readable. No axis labels (appropriate for treemap). Legend at bottom row shows five colored swatches with labels "US ($2100M)", "Germany ($580M)", "Japan ($850M)", "Brazil ($520M)", "Australia ($380M)" — all readable in dark ink on warm background. - Data: Treemap fills nearly the full canvas. US occupies the left half in dark azure-blue (#0A5AC4 range, high-value end of choropleth). Germany, Japan, Brazil, Australia occupy the right half in a range of teal-to-green (#009E73 range, lower values). Sub-tile values (state sales in millions) are printed at tile centers: 520, 320, 680, 580 (US states); 130, 180, 140, 60, 240, 150, 80, 210, 420, 280, 160 (other countries' states). Colors correctly reflect the sequential anyplot_seq scale from brand green (low) to dark azure (high). - Legibility verdict: PASS — all title, legend, and tile-value text is clearly readable against the warm off-white background and the medium-chromacity choropleth tiles. - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct per style guide; no pure black. - Chrome: Title renders in lighter cream-toned text, readable against the dark background. Legend entries ("US ($2100M)" etc.) appear in light-colored text on the dark strip below the chart — readable. - Data: Identical tile layout and choropleth colors to the light render — sequential scale is preserved between themes (correct: data colors positions 1–7 must not change across themes). The US deep-blue tiles and the right-side teal-green tiles match the light render exactly. - Legibility verdict: PARTIAL PASS — tile value labels on the darker blue US tiles (e.g., 520, 320, 680, 580) have reduced contrast in the dark render; dark ink on dark-blue (#0A5AC4) yields an estimated ~2.5:1 contrast ratio, which falls below WCAG 4.5:1. The values remain decipherable but are notably harder to read than in the light render. No dark-on-dark failures for title or legend text. Tile labels on the lighter teal-green cells have better contrast and are clearly readable. - criteria_checklist: - visual_quality: - score: 26 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 6 - max: 8 - passed: true - comment: 'Font sizes explicitly set (title=66, label=56, legend=44, value=36); - proportions correct for 3200x1800. Minor: dark-mode value labels on deep-blue - tiles have ~2.5:1 contrast ratio (dark ink on dark blue) — readable but - below WCAG AA.' - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: Treemap tiles are non-overlapping by construction; legend entries - cleanly spaced; no text collisions observed. - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: Tiles clearly visible; value numbers mostly readable. Very small - tiles (e.g., '60' in top-right corner) have cramped label placement. - - id: VQ-04 - name: Color Accessibility - score: 1 - max: 2 - passed: true - comment: Sequential green-to-blue is CVD-acceptable (no red-green as sole - distinguisher). Separating similarly-valued countries by hue alone is less - reliable under protanopia. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Canvas 3200x1800 correct. Plot fills ~85% of canvas. Legend positioned - cleanly at bottom. Generous margins. No content cut off. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title follows exact format. Treemap has no X/Y axes; legend serves - as category guide. HTML includes 'Sales ($M)' y-title on drill-down bar - charts. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Choropleth uses anyplot_seq-derived scale (#009E73→#0A5AC4, minor - endpoint adjustment from #003D94 for dark-theme contrast — acceptable). - Backgrounds #FAF8F1 / #1A1A17 correct. Data colors identical across themes. - Chrome adapts correctly.' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: true - comment: 'Well-configured: choropleth coloring is intentional and adds semantic - depth; legend labels include currency values; clean font hierarchy. Not - exceptional — treemap layout is functional but generic.' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: No axes/gridlines appropriate for treemap; clean margins; legend - columns balanced. Monochromatic choropleth could benefit from tile stroke - contrast in dark mode. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Dual encoding (tile area + color) immediately conveys that US dominates - global sales. Size hierarchy makes the big-picture insight obvious. HTML - drill-down enables exploration of the story at each level. - spec_compliance: - score: 12 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 3 - max: 5 - passed: true - comment: Spec calls for a geographic map (choropleth). Implementation uses - a Treemap — correct concept (hierarchical geographic data with choropleth - coloring) but wrong chart type. Pygal has pygal.maps.world.World() and us.States() - that could show actual geographic shapes. - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: 'Drilldown ✓, breadcrumb with clickable crumbs ✓, aggregate values - ✓, consistent color scale ✓, color legend ✓, hover tooltips ✓, graceful - leaf handling ✓. Missing: zoom transition with spatial context (only fade - opacity transition implemented).' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Tile area maps to total country value; sub-tile area maps to state - value; color encodes relative magnitude via sequential scale. All data visible - and consistently mapped. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title: ''map-drilldown-geographic · python · pygal · anyplot.ai'' - — exact format. Legend labels include country names and total values ($M). - HTML title matches.' - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: Three-level hierarchy (country→state→city) fully implemented. Five - countries, 3-4 states each, 2-3 cities per state. Variation in values demonstrates - the chart type. City level only accessible in HTML drilldown, not in PNG. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: 'Real-world business scenario: global sales performance in $M by - country/state/city. Real geographic names. Neutral business context. No - sensitive topics.' - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Child values correctly sum to parent values at all levels (US $2100M - = CA+TX+NY+FL; Japan $850M = Tokyo+Osaka+Aichi; etc.). Proportions plausible - for major global markets. - code_quality: - score: 8 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 2 - max: 3 - passed: true - comment: 'Two small helper functions (_lerp_hex, seq_color) break the no-functions - rule but serve a clear purpose. Structure is mostly linear: helpers → data - → PNG chart → HTML chart.' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: All data hardcoded in the hierarchy dict. 100% deterministic, no - random seed needed. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: json, os, sys, pygal, Style — all used. No unused imports. - - id: CQ-04 - name: Code Elegance - score: 1 - max: 2 - passed: true - comment: Misleading comment on line 216 says 'print_values disabled' but code - has print_values=True. Per-bar coloring workaround (N series each with N-1 - Nones) is verbose. Repeated Style() instantiation in the loop. No fake UI - elements. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html. Correct naming. Current - pygal API used. - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Correct use of pygal.Treemap, Style object with all theme tokens, - legend_at_bottom, legend_at_bottom_columns, render_to_png, render(is_unicode=True). - Idiomatic pygal patterns throughout. - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Uses Treemap (a distinctive pygal chart type) and leverages pygal's - native SVG output embedded in custom HTML for a multi-level drilldown. SVG-in-HTML - with JavaScript chart switching is a creative use of pygal's vector output. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - html-export - - hover-tooltips - patterns: - - iteration-over-groups - dataprep: [] - styling: - - custom-colormap diff --git a/plots/map-drilldown-geographic/metadata/python/seaborn.yaml b/plots/map-drilldown-geographic/metadata/python/seaborn.yaml deleted file mode 100644 index 6093c106e6..0000000000 --- a/plots/map-drilldown-geographic/metadata/python/seaborn.yaml +++ /dev/null @@ -1,290 +0,0 @@ -library: seaborn -language: python -specification_id: map-drilldown-geographic -created: '2026-01-20T07:43:15Z' -updated: '2026-05-23T20:05:00Z' -generated_by: claude-sonnet -workflow_run: 26328505142 -issue: 3770 -language_version: 3.13.13 -library_version: 0.13.2 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/seaborn/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/map-drilldown-geographic/python/seaborn/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 77 -review: - strengths: - - Creative three-panel layout elegantly conveys the drill-down hierarchy as a static - visualization — Level 1 (Countries) → Level 2 (German States) → Level 3 (Bavarian - Cities) with arrows between panels - - Shared anyplot_seq colorbar spanning all three levels provides consistent value - comparison across the hierarchy - - Highlighted drill path (Germany outlined in panel 1, Bavaria outlined in panel - 2) tells a coherent story about which region is being zoomed into - - Breadcrumb annotations ('Europe', 'Europe > Germany', 'Europe > Germany > Bavaria') - give clear hierarchical context in each panel - - 'Proper theme-adaptive chrome: all text, axis colors, backgrounds, and callout - boxes switch correctly between light (#FAF8F1) and dark (#1A1A17) themes — no - dark-on-dark failures' - - Seaborn scatterplot with size+hue encoding on centroids adds a redundant but useful - magnitude encoding on top of the rectangle choropleth - - Realistic sales revenue data (European countries, German states, Bavarian cities) - with plausible values and sensible geographic coordinates - weaknesses: - - 'Panel 1 (Countries) has significant text overlap: Germany and France rectangles - share geographic space, causing country name labels and value labels (€4200M, - €3100M, €2200M etc.) to overlap each other — consider reducing fontsize from 6.5pt - to 5pt for names and 6pt to 4.5pt for values in dense panels, or clip text to - within each rectangle''s bounds' - - 'Panel 2 (States) label density: NRW, Hesse, and Baden-Württemberg rectangles - are geographically adjacent and their text labels overlap at fontsize=6.5 — tighten - label offsets or use a smaller fontsize (5pt) for state names in panel 2' - - Scatter centroid dots add visual noise in panels 1 and 2 where rectangles are - already labeled — consider removing scatter from panels 1 and 2 and keeping it - only in panel 3 (Cities), where there are no rectangles and size+hue provides - genuine added value - - Seaborn usage is minimal — seaborn scatterplot is used only for centroid markers - while all core geographic rendering is done with matplotlib Rectangle patches; - the seaborn-specific features (size+hue bubble map) are a useful touch but the - library is barely leveraged beyond basic scatterplot - - np.random.seed(42) is set but no random data is actually generated (all data is - hardcoded dicts) — the seed line is dead code and should be removed - - Large bottom whitespace gap between the plot panels and the colorbar (subplots - bottom=0.28, colorbar at y=0.12-0.158) creates visual disconnect; reducing bottom - to 0.22 and raising cbar_ax y to 0.16 would tighten the layout - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) fills the entire figure and all three panel backgrounds — correct. - Chrome: Title "map-drilldown-geographic · python · seaborn · anyplot.ai" in dark ink, readable at fontsize=12. Panel subtitles "Level 1: Countries", "Level 2: States (Germany)", "Level 3: Cities (Bavaria)" in dark ink at fontsize=10. Axis labels "Longitude (°)" and "Latitude (°)" in dark ink at fontsize=8. Tick labels in INK_SOFT (#4A4A44) at fontsize=7. All text clearly visible against the warm off-white background. - Data: Three panels show geographic hierarchy. Panel 1: Five colored rectangles representing European countries (Germany teal-blue highlighted with ACCENT border, others ranging green→blue via anyplot_seq). Small scatter dots at centroids encode value via size and hue. Country names and values labeled (€1900M–€4200M). Panel 2: Six German state rectangles (Bavaria highlighted), labeled with state names and values. Panel 3: Six Bavarian cities as scatter bubbles sized by revenue, labeled with city names and values. ACCENT-green (→) arrows between panels. ACCENT-green breadcrumb boxes in each panel. Shared horizontal colorbar at bottom (green→blue, €0–€4200M). Notable overlap in panel 1 (Germany/France/Italy labels crowd each other) and panel 2 (NRW/Hesse/Bavaria text dense). - Legibility verdict: PASS — all text readable; minor overlap in panels 1 and 2 reduces clarity but text remains distinguishable. - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) fills the entire figure and all panel backgrounds — correct. - Chrome: Title and axis labels in light cream (#F0EFE8), clearly visible against dark background. Tick labels in INK_SOFT (#B8B7B0). Panel titles in correct light ink. Breadcrumb callout boxes use ELEVATED_BG (#242420) fill with ACCENT-green border — readable. Colorbar labels and ticks in correct light colors. No dark-on-dark failures observed. - Data: Data colors are identical to the light render — anyplot_seq green-to-blue gradient unchanged between themes. Rectangle fills and scatter dot colors match the light version. ACCENT green (#009E73) arrows and breadcrumb borders remain visible on the dark surface. - Legibility verdict: PASS — full theme adaptation successful; all chrome elements flip correctly to light-on-dark. - criteria_checklist: - visual_quality: - score: 23 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 6 - max: 8 - passed: true - comment: All text readable in both themes; smallest value labels at fontsize=5 - (city values) are marginal for mobile-scaled views; title, panel headers, - axis labels all clear - - id: VQ-02 - name: No Overlap - score: 3 - max: 6 - passed: false - comment: Notable label overlap in panel 1 (Germany/France/Italy rectangles - overlap geographically, causing text collisions) and panel 2 (NRW/Hesse/Baden-Württemberg - labels cramped); panel 3 (cities) is better managed with custom offsets - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: Rectangles, scatter dots, colorbar all clearly visible; some data - obscured by overlapping labels in dense panels - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: anyplot_seq colormap (green→blue) is CVD-safe; ACCENT green visible - in both themes - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: Canvas 3200×1800 correct; three-panel proportions good; large whitespace - gap between bottom of panels and colorbar (subplots bottom=0.28, cbar at - y=0.12-0.158) - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Axis labels include units (Longitude °, Latitude °); title matches - required format - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'anyplot_seq colormap used throughout; backgrounds #FAF8F1/#1A1A17 - correct; ACCENT #009E73 used consistently' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Above default — three-panel layout with drill-path highlighting is - a sophisticated static adaptation of an interactive concept; arrows and - breadcrumbs add professional polish - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: true - comment: Above default — top/right spines removed, semi-transparent fills - (alpha=0.72/0.92), boxed breadcrumb annotations with rounded corners; limited - whitespace refinement - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Above default — highlighted drill path (Germany→Bavaria) tells a - coherent story; arrows guide viewer through hierarchy; consistent colorbar - enables cross-level comparison - spec_compliance: - score: 11 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 3 - max: 5 - passed: true - comment: Geographic hierarchical choropleth concept captured, but uses rectangles - instead of actual map shapes; inherently static for an interactive spec - — valid seaborn approximation - - id: SC-02 - name: Required Features - score: 2 - max: 4 - passed: false - comment: 'Has: choropleth coloring, static breadcrumb text, color legend, - consistent color scale, hierarchical data at 3 levels. Missing: true drill-down - interaction, tooltips on hover, smooth zoom transitions (all impossible - in seaborn)' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Lon/lat mapped to axes, values mapped to color (anyplot_seq), three-level - hierarchy preserved - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title 'map-drilldown-geographic · python · seaborn · anyplot.ai' - correct; colorbar legend present with 'Sales Revenue (€M)' label - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: All three drill levels shown (countries/states/cities); choropleth - at each level; geographic coordinates correct; hierarchical containment - shown - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: 'European sales revenue data: realistic countries (Germany €4200M, - France €3100M, UK €2800M, Italy €2200M, Spain €1900M), German states (NRW, - Bavaria, Baden-Württemberg, Hesse, Saxony, Brandenburg), Bavarian cities - (Munich, Nuremberg, Augsburg, Regensburg, Ingolstadt, Würzburg) — all plausible - and neutral' - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: €M scale is sensible for sales revenue; values range €75M–€4200M - covering a realistic spread; geographic coordinates are accurate - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Flat structure, no functions or classes - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) present (though no actual random data is generated - — dead code, but harmless) - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: 'All imports used: os, matplotlib.pyplot, numpy, pandas, seaborn, - LinearSegmentedColormap, Rectangle' - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Reasonable complexity; no fake UI; some repetition between panels - is necessary for the three-panel structure - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png correctly; no bbox_inches='tight' - library_mastery: - score: 5 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 3 - max: 5 - passed: true - comment: Seaborn used for scatterplot with size+hue encoding; most geographic - rendering is matplotlib Rectangle patches — seaborn usage limited but idiomatic - where applied - - id: LM-02 - name: Distinctive Features - score: 2 - max: 5 - passed: true - comment: Above default — seaborn size+hue bubble encoding on geographic centroids - is a library-specific pattern; could leverage seaborn more deeply (e.g. - relplot, heatmap, FacetGrid) - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - subplots - - colorbar - - annotations - - patches - patterns: - - iteration-over-groups - - explicit-figure - dataprep: - - normalization - styling: - - custom-colormap - - alpha-blending - - edge-highlighting diff --git a/plots/map-drilldown-geographic/specification.md b/plots/map-drilldown-geographic/specification.md deleted file mode 100644 index 1645714822..0000000000 --- a/plots/map-drilldown-geographic/specification.md +++ /dev/null @@ -1,32 +0,0 @@ -# map-drilldown-geographic: Drillable Geographic Map - -## Description - -A hierarchical map with drill-down capability that navigates from country to state/province to city level, revealing progressively more geographic detail on click. Each region is colored according to a data value (choropleth style), and clicking a region zooms into its subdivisions with aggregated values. Breadcrumb navigation allows users to traverse back up the hierarchy. This visualization excels at exploring multi-level administrative data where users need both the big picture and granular detail. - -## Applications - -- Analyzing sales performance by region, drilling from country to state to city to identify top-performing areas -- Exploring population statistics at multiple administrative levels for demographic research -- Visualizing election results or survey responses with the ability to drill into specific regions -- Navigating hierarchical geographic reporting dashboards for business intelligence - -## Data - -- `country` (string) - Country name or ISO code (top level) -- `state` (string) - State or province name (second level) -- `city` (string, optional) - City name (third level, leaf nodes) -- `value` (numeric) - Data value at each level (e.g., sales, population, percentage) -- Size: 5-50 countries at top level, 5-30 states per country, 5-20 cities per state -- Structure: Hierarchical with parent-child relationships implied by geographic containment - -## Notes - -- Click any region to drill down to the next administrative level -- Display breadcrumb navigation (e.g., "World > United States > California") with clickable segments to navigate back up -- Smooth zoom transitions when drilling down or up to maintain spatial context -- Parent-level values should aggregate child values (sum, average, or weighted average as appropriate) -- Use a consistent color scale across all levels to enable meaningful comparison -- Show a color legend indicating the value range -- For interactive libraries, include tooltips showing region name and value on hover -- Handle regions with no sub-level data gracefully (show as non-clickable or with visual indicator) diff --git a/plots/map-drilldown-geographic/specification.yaml b/plots/map-drilldown-geographic/specification.yaml deleted file mode 100644 index f69aa64143..0000000000 --- a/plots/map-drilldown-geographic/specification.yaml +++ /dev/null @@ -1,29 +0,0 @@ -# Specification-level metadata for map-drilldown-geographic -# Auto-synced to PostgreSQL on push to main - -spec_id: map-drilldown-geographic -title: Drillable Geographic Map - -# Specification tracking -created: 2026-01-11T22:50:34Z -updated: null -issue: 3770 -suggested: MarkusNeusinger - -# Classification tags (applies to all library implementations) -# See docs/reference/tagging-system.md for detailed guidelines -tags: - plot_type: - - choropleth - - map - data_type: - - geospatial - - hierarchical - - numeric - domain: - - general - - business - features: - - interactive - - drilldown - - animated diff --git a/plots/pie-drilldown/implementations/python/altair.py b/plots/pie-drilldown/implementations/python/altair.py deleted file mode 100644 index 1d8d9465aa..0000000000 --- a/plots/pie-drilldown/implementations/python/altair.py +++ /dev/null @@ -1,185 +0,0 @@ -""" anyplot.ai -pie-drilldown: Drilldown Pie Chart with Click Navigation -Library: altair 6.1.0 | Python 3.13.13 -Quality: 80/100 | Updated: 2026-05-15 -""" - -import os -import sys - -import pandas as pd - - -# Set up path to load altair without shadowing -sys.path.insert(0, "/home/runner/work/anyplot/anyplot/.venv/lib/python3.13/site-packages") -try: - import altair as alt -finally: - sys.path.pop(0) - -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" - -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477"] - -hierarchy_data = [ - {"id": "root", "name": "All Departments", "value": 3200000, "parent": None}, - {"id": "engineering", "name": "Engineering", "value": 1755000, "parent": "root"}, - {"id": "marketing", "name": "Marketing", "value": 740000, "parent": "root"}, - {"id": "operations", "name": "Operations", "value": 485000, "parent": "root"}, - {"id": "hr", "name": "HR", "value": 220000, "parent": "root"}, - {"id": "eng_frontend", "name": "Frontend", "value": 510000, "parent": "engineering"}, - {"id": "eng_backend", "name": "Backend", "value": 745000, "parent": "engineering"}, - {"id": "eng_devops", "name": "DevOps", "value": 500000, "parent": "engineering"}, - {"id": "mkt_digital", "name": "Digital", "value": 295000, "parent": "marketing"}, - {"id": "mkt_content", "name": "Content", "value": 245000, "parent": "marketing"}, - {"id": "mkt_events", "name": "Events", "value": 200000, "parent": "marketing"}, - {"id": "ops_facilities", "name": "Facilities", "value": 325000, "parent": "operations"}, - {"id": "ops_it", "name": "IT Support", "value": 160000, "parent": "operations"}, - {"id": "hr_recruit", "name": "Recruiting", "value": 120000, "parent": "hr"}, - {"id": "hr_training", "name": "Training", "value": 100000, "parent": "hr"}, -] - -df = pd.DataFrame(hierarchy_data) - -color_map = { - "All Departments": INK, - "Engineering": IMPRINT[0], - "Marketing": IMPRINT[1], - "Operations": IMPRINT[2], - "HR": IMPRINT[3], - "Frontend": "#2ABCCD", - "Backend": IMPRINT[0], - "DevOps": "#4467A3", - "Digital": "#FFC863", - "Content": IMPRINT[1], - "Events": "#C475FD", - "Facilities": "#72C472", - "IT Support": IMPRINT[2], - "Recruiting": "#BC8FBC", - "Training": "#BD8233", -} - -df["color"] = df["name"].map(color_map) -df["parent_total"] = df.groupby("parent")["value"].transform("sum") -df["percentage"] = (df["value"] / df["parent_total"] * 100).fillna(0) -df["pct_label"] = df["percentage"].apply(lambda x: f"{x:.0f}%") -df["display_label"] = df["name"] + "\n" + df["pct_label"] - -selection = alt.selection_point(fields=["id"], empty=True, name="drill") - -breadcrumb_df = pd.DataFrame([{"breadcrumb": "All Departments"}]) -breadcrumb = ( - alt.Chart(breadcrumb_df) - .mark_text(fontSize=16, align="center", color=INK_SOFT) - .encode(text="breadcrumb:N") - .properties(width=600, height=30) -) - -root_view = df[df["parent"] == "root"] -main_pie = ( - alt.Chart(root_view) - .mark_arc(innerRadius=160, outerRadius=280, stroke=PAGE_BG, strokeWidth=4, cursor="pointer") - .encode( - theta=alt.Theta("value:Q", stack=True), - color=alt.Color( - "name:N", scale=alt.Scale(domain=list(color_map.keys()), range=list(color_map.values())), legend=None - ), - opacity=alt.condition(selection, alt.value(1.0), alt.value(0.85)), - tooltip=[ - alt.Tooltip("name:N", title="Department"), - alt.Tooltip("value:Q", title="Budget ($)", format=",.0f"), - alt.Tooltip("percentage:Q", title="Share (%)", format=".1f"), - ], - ) - .add_params(selection) -) - -main_labels = ( - alt.Chart(root_view) - .mark_text(radius=180, fontSize=13, align="center", baseline="middle") - .encode(theta=alt.Theta("value:Q", stack=True), text=alt.Text("display_label:N"), color=alt.value(INK)) -) - -main_center_df = pd.DataFrame([{"line1": "All Departments", "line2": "$3.20B"}]) -main_center = alt.layer( - alt.Chart(main_center_df).mark_text(fontSize=22, fontWeight="bold", dy=-20, color=INK).encode(text="line1:N"), - alt.Chart(main_center_df) - .mark_text(fontSize=32, fontWeight="bold", dy=15, color=IMPRINT[0]) - .encode(text="line2:N"), -) - -main_pie_chart = alt.layer(main_pie, main_labels, main_center).properties(width=600, height=600) - -drilldown_views = [] -dept_configs = [("engineering", "Engineering"), ("marketing", "Marketing"), ("operations", "Operations"), ("hr", "HR")] -for parent_id, parent_name in dept_configs: - dept_view = df[df["parent"] == parent_id] - if len(dept_view) == 0: - continue - - dept_total = df[df["id"] == parent_id]["value"].values[0] - - dept_pie = ( - alt.Chart(dept_view) - .mark_arc(innerRadius=160, outerRadius=280, stroke=PAGE_BG, strokeWidth=4) - .encode( - theta=alt.Theta("value:Q", stack=True), - color=alt.Color( - "name:N", scale=alt.Scale(domain=list(color_map.keys()), range=list(color_map.values())), legend=None - ), - tooltip=[ - alt.Tooltip("name:N", title="Team"), - alt.Tooltip("value:Q", title="Budget ($)", format=",.0f"), - alt.Tooltip("percentage:Q", title="Share (%)", format=".1f"), - ], - ) - .transform_filter(alt.datum.parent == parent_id) - ) - - dept_labels = ( - alt.Chart(dept_view) - .mark_text(radius=180, fontSize=12, align="center", baseline="middle", color=INK) - .encode(theta=alt.Theta("value:Q", stack=True), text=alt.Text("display_label:N")) - .transform_filter(alt.datum.parent == parent_id) - ) - - dept_center_df = pd.DataFrame([{"line1": f"← {parent_name}", "line2": f"${dept_total / 1e6:.2f}B"}]) - dept_center = alt.layer( - alt.Chart(dept_center_df) - .mark_text(fontSize=20, fontWeight="bold", dy=-15, color=INK_SOFT) - .encode(text="line1:N"), - alt.Chart(dept_center_df) - .mark_text(fontSize=28, fontWeight="bold", dy=15, color=color_map[parent_name]) - .encode(text="line2:N"), - ) - - dept_combined = ( - alt.layer(dept_pie, dept_labels, dept_center) - .transform_filter(selection) - .transform_filter(f"datum.id == '{parent_id}'") - .properties(width=600, height=600) - ) - drilldown_views.append(dept_combined) - -final_layer = alt.layer(main_pie_chart, *drilldown_views) - -final_chart = ( - alt.vconcat(breadcrumb, final_layer, spacing=20) - .properties( - title=alt.TitleParams(text="pie-drilldown · altair · anyplot.ai", fontSize=28, anchor="middle", color=INK), - background=PAGE_BG, - ) - .configure_view(fill=PAGE_BG, stroke=INK_SOFT, strokeWidth=0) - .configure_axis( - domainColor=INK_SOFT, tickColor=INK_SOFT, gridColor=INK, gridOpacity=0.10, labelColor=INK_SOFT, titleColor=INK - ) - .configure_title(color=INK) - .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK) -) - -final_chart.save(f"plot-{THEME}.png", scale_factor=3.0) -final_chart.save(f"plot-{THEME}.html") diff --git a/plots/pie-drilldown/implementations/python/bokeh.py b/plots/pie-drilldown/implementations/python/bokeh.py deleted file mode 100644 index 1b0aaa393a..0000000000 --- a/plots/pie-drilldown/implementations/python/bokeh.py +++ /dev/null @@ -1,363 +0,0 @@ -""" anyplot.ai -pie-drilldown: Drilldown Pie Chart with Click Navigation -Library: bokeh 3.9.0 | Python 3.13.13 -Quality: 85/100 | Updated: 2026-05-15 -""" - -import json -import os -import time -from math import pi -from pathlib import Path - -import numpy as np -from bokeh.io import output_file, save -from bokeh.layouts import column -from bokeh.models import ColumnDataSource, CustomJS, Div, Label, TapTool -from bokeh.plotting import figure -from bokeh.resources import INLINE -from selenium import webdriver -from selenium.webdriver.chrome.options import Options - - -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" - -# Hierarchical data: Company expense breakdown -hierarchy = { - "root": {"name": "Total Expenses", "children": ["operations", "marketing", "research", "hr"]}, - "operations": {"name": "Operations", "parent": "root", "children": ["facilities", "logistics", "it_infra"]}, - "marketing": {"name": "Marketing", "parent": "root", "children": ["digital", "events", "content"]}, - "research": {"name": "Research", "parent": "root", "children": ["lab_equip", "materials", "personnel"]}, - "hr": {"name": "Human Resources", "parent": "root", "children": ["recruitment", "training", "benefits"]}, - # Operations subcategories - "facilities": {"name": "Facilities", "parent": "operations", "children": ["rent", "utilities", "maintenance"]}, - "logistics": {"name": "Logistics", "parent": "operations", "children": ["shipping", "warehousing"]}, - "it_infra": {"name": "IT Infrastructure", "parent": "operations", "children": ["servers", "software", "support"]}, - # Marketing subcategories - "digital": {"name": "Digital Marketing", "parent": "marketing", "children": ["ads", "seo", "social"]}, - "events": {"name": "Events", "parent": "marketing", "value": 180000}, - "content": {"name": "Content", "parent": "marketing", "value": 120000}, - # Research subcategories - "lab_equip": {"name": "Lab Equipment", "parent": "research", "value": 350000}, - "materials": {"name": "Materials", "parent": "research", "value": 200000}, - "personnel": {"name": "Personnel", "parent": "research", "value": 450000}, - # HR subcategories - "recruitment": {"name": "Recruitment", "parent": "hr", "value": 150000}, - "training": {"name": "Training", "parent": "hr", "value": 100000}, - "benefits": {"name": "Benefits", "parent": "hr", "value": 400000}, - # Facilities leaf nodes - "rent": {"name": "Rent", "parent": "facilities", "value": 300000}, - "utilities": {"name": "Utilities", "parent": "facilities", "value": 80000}, - "maintenance": {"name": "Maintenance", "parent": "facilities", "value": 60000}, - # Logistics leaf nodes - "shipping": {"name": "Shipping", "parent": "logistics", "value": 250000}, - "warehousing": {"name": "Warehousing", "parent": "logistics", "value": 180000}, - # IT Infrastructure leaf nodes - "servers": {"name": "Servers", "parent": "it_infra", "value": 200000}, - "software": {"name": "Software", "parent": "it_infra", "value": 150000}, - "support": {"name": "Support", "parent": "it_infra", "value": 100000}, - # Digital Marketing leaf nodes - "ads": {"name": "Advertising", "parent": "digital", "value": 400000}, - "seo": {"name": "SEO", "parent": "digital", "value": 80000}, - "social": {"name": "Social Media", "parent": "digital", "value": 120000}, -} - -# Okabe-Ito palette - first series ALWAYS #009E73 -colors = [ - "#009E73", # Teal Green (Operations) - "#C475FD", # Vermillion (Marketing) - "#4467A3", # Blue (Research) - "#BD8233", # Pink/Magenta (HR) - "#AE3030", # Orange - "#2ABCCD", # Sky Blue - "#954477", # Yellow -] - -# Calculate values for root level children -root_children = hierarchy["root"]["children"] -names = [] -values = [] -ids = [] -has_children_list = [] - - -def get_value(node_id): - node = hierarchy[node_id] - if "value" in node: - return node["value"] - if "children" not in node: - return 0 - return sum(get_value(child_id) for child_id in node["children"]) - - -for child_id in root_children: - child = hierarchy[child_id] - names.append(child["name"]) - ids.append(child_id) - has_children_list.append("children" in child) - values.append(get_value(child_id)) - -total = sum(values) -percentages = [v / total * 100 for v in values] - -# Calculate angles for pie wedges (clockwise from 12 o'clock) -angles = [v / total * 2 * pi for v in values] -start_angles = [pi / 2 - sum(angles[:i]) for i in range(len(angles))] -end_angles = [pi / 2 - sum(angles[: i + 1]) for i in range(len(angles))] - -# Assign distinct colors to each slice -slice_colors = colors[: len(names)] - -# Create source data for wedges -source = ColumnDataSource( - data={ - "names": names, - "values": values, - "ids": ids, - "has_children": has_children_list, - "start_angle": start_angles, - "end_angle": end_angles, - "color": slice_colors, - "percentage": percentages, - "label": [f"{n}\n${v / 1000:.0f}K\n({p:.1f}%)" for n, v, p in zip(names, values, percentages, strict=True)], - } -) - -# Label source for the center of each wedge -mid_angles = [(s + e) / 2 for s, e in zip(start_angles, end_angles, strict=True)] -label_radius_values = [0.55 if p >= 15 else 0.65 for p in percentages] -label_x = [r * np.cos(a) for r, a in zip(label_radius_values, mid_angles, strict=True)] -label_y = [r * np.sin(a) for r, a in zip(label_radius_values, mid_angles, strict=True)] - -label_texts = [] -for n, v, p in zip(names, values, percentages, strict=True): - if p >= 15: - label_texts.append(f"{n}\n${v / 1000:.0f}K\n({p:.1f}%)") - else: - label_texts.append(f"{n}\n${v / 1000:.0f}K ({p:.1f}%)") - -label_source = ColumnDataSource(data={"x": label_x, "y": label_y, "text": label_texts}) - -# Create figure with theme-adaptive styling -p = figure( - width=3600, - height=3600, - title="pie-drilldown · bokeh · anyplot.ai", - tools="tap,reset", - toolbar_location=None, - x_range=(-1.5, 1.5), - y_range=(-1.6, 1.7), -) - -# Style the figure with theme-adaptive chrome -p.title.text_font_size = "48pt" -p.title.align = "center" -p.title.text_color = INK -p.axis.visible = False -p.grid.visible = False -p.outline_line_color = None -p.background_fill_color = PAGE_BG - -# Draw wedges with refined colors -wedges = p.wedge( - x=0, - y=0, - radius=0.9, - start_angle="start_angle", - end_angle="end_angle", - fill_color="color", - line_color=PAGE_BG, - line_width=4, - source=source, -) - -# Add text shadow/outline for better readability -label_shadow = p.text( - x="x", - y="y", - text="text", - source=label_source, - text_font_size="30pt", - text_align="center", - text_baseline="middle", - text_color="#222222" if THEME == "light" else "#EEEEEE", - text_font_style="bold", - text_outline_color="#222222" if THEME == "light" else "#EEEEEE", -) - -# Add main labels -labels = p.text( - x="x", - y="y", - text="text", - source=label_source, - text_font_size="30pt", - text_align="center", - text_baseline="middle", - text_color="white" if THEME == "light" else "#F0EFE8", - text_font_style="bold", -) - -# Add breadcrumb navigation label -breadcrumb_label = Label( - x=0, - y=1.45, - text="Total Expenses", - text_font_size="36pt", - text_font_style="bold", - text_color=INK, - text_align="center", - text_baseline="middle", -) -p.add_layout(breadcrumb_label) - -# Add clickable indicator text -click_indicator = Label( - x=0, - y=-1.35, - text="Click a slice to drill down", - text_font_size="28pt", - text_color=INK_SOFT, - text_align="center", - text_baseline="middle", -) -p.add_layout(click_indicator) - -# Breadcrumb navigation div for HTML version -breadcrumb = Div( - text=f'
' - f'Total Expenses' - f' | Click a slice to drill down' - f"
", - width=3600, - height=100, -) - -# Store hierarchy data as JSON for JavaScript -hierarchy_json = json.dumps(hierarchy) -colors_json = json.dumps(colors) - -# JavaScript callback for drilling down on click -callback = CustomJS( - args={ - "source": source, - "label_source": label_source, - "breadcrumb": breadcrumb, - "hierarchy_json": hierarchy_json, - "colors_json": colors_json, - }, - code=""" - const hierarchy = JSON.parse(hierarchy_json); - const colors = JSON.parse(colors_json); - - if (!window.nav_path) { - window.nav_path = ['root']; - } - - const indices = source.selected.indices; - if (indices.length === 0) return; - - const clicked_id = source.data['ids'][indices[0]]; - const clicked_node = hierarchy[clicked_id]; - - if (!clicked_node.children || clicked_node.children.length === 0) { - return; - } - - function getValue(node_id) { - const node = hierarchy[node_id]; - if (node.value !== undefined) return node.value; - if (!node.children) return 0; - return node.children.reduce((sum, child) => sum + getValue(child), 0); - } - - const children = clicked_node.children; - const names = children.map(id => hierarchy[id].name); - const values = children.map(id => getValue(id)); - const total = values.reduce((a, b) => a + b, 0); - const percentages = values.map(v => v / total * 100); - const has_children = children.map(id => hierarchy[id].children !== undefined); - - const angles = values.map(v => v / total * 2 * Math.PI); - const start_angles = []; - const end_angles = []; - let cumsum = Math.PI / 2; - for (let i = 0; i < angles.length; i++) { - start_angles.push(cumsum); - cumsum -= angles[i]; - end_angles.push(cumsum); - } - - source.data['names'] = names; - source.data['values'] = values; - source.data['ids'] = children; - source.data['has_children'] = has_children; - source.data['start_angle'] = start_angles; - source.data['end_angle'] = end_angles; - source.data['color'] = colors.slice(0, names.length); - source.data['percentage'] = percentages; - source.data['label'] = names.map((n, i) => - n + '\\n$' + (values[i]/1000).toFixed(0) + 'K\\n(' + percentages[i].toFixed(1) + '%)' - ); - - const mid_angles = start_angles.map((s, i) => (s + end_angles[i]) / 2); - const label_radii = percentages.map(p => p >= 15 ? 0.55 : 0.65); - label_source.data['x'] = mid_angles.map((a, i) => label_radii[i] * Math.cos(a)); - label_source.data['y'] = mid_angles.map((a, i) => label_radii[i] * Math.sin(a)); - label_source.data['text'] = names.map((n, i) => - percentages[i] >= 15 - ? n + '\\n$' + (values[i]/1000).toFixed(0) + 'K\\n(' + percentages[i].toFixed(1) + '%)' - : n + '\\n$' + (values[i]/1000).toFixed(0) + 'K (' + percentages[i].toFixed(1) + '%)' - ); - - window.nav_path.push(clicked_id); - - let breadcrumb_html = '
'; - for (let i = 0; i < window.nav_path.length; i++) { - const node_id = window.nav_path[i]; - const node = hierarchy[node_id]; - if (i > 0) breadcrumb_html += ' › '; - breadcrumb_html += '' + node.name + ''; - } - breadcrumb_html += '
'; - breadcrumb.text = breadcrumb_html; - - source.change.emit(); - label_source.change.emit(); - source.selected.indices = []; -""", -) - -# Add tap tool with callback -p.select(type=TapTool).callback = callback - -# Create layout -layout = column(breadcrumb, p) - -# Save HTML with full interactivity -output_file(f"plot-{THEME}.html") -save(layout, resources=INLINE, title="pie-drilldown · bokeh · anyplot.ai") - -# Screenshot with headless Chrome -W, H = 3600, 3600 -opts = Options() -for arg in ( - "--headless=new", - "--no-sandbox", - "--disable-dev-shm-usage", - "--disable-gpu", - f"--window-size={W},{H}", - "--hide-scrollbars", -): - opts.add_argument(arg) -driver = webdriver.Chrome(options=opts) -driver.set_window_size(W, H) -driver.get(f"file://{Path(f'plot-{THEME}.html').resolve()}") -time.sleep(3) -driver.save_screenshot(f"plot-{THEME}.png") -driver.quit() diff --git a/plots/pie-drilldown/implementations/python/letsplot.py b/plots/pie-drilldown/implementations/python/letsplot.py deleted file mode 100644 index 0905a7b8fa..0000000000 --- a/plots/pie-drilldown/implementations/python/letsplot.py +++ /dev/null @@ -1,631 +0,0 @@ -""" anyplot.ai -pie-drilldown: Drilldown Pie Chart with Click Navigation -Library: letsplot 4.9.0 | Python 3.13.13 -Quality: 85/100 | Updated: 2026-05-15 -""" - -import json -import os - -import pandas as pd -from lets_plot import * # noqa: F403 -from lets_plot.export import ggsave as export_ggsave - - -LetsPlot.setup_html() # noqa: F405 - -# 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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -# Okabe-Ito palette (using position 1 as first series) -IMPRINT = [ - "#009E73", # bluish green (brand) - "#C475FD", # vermillion - "#4467A3", # blue - "#BD8233", # reddish purple -] - -# Hierarchical data: Company budget breakdown by department -hierarchy_data = { - "root": {"name": "All Departments", "children": ["engineering", "marketing", "operations", "hr"]}, - "engineering": { - "name": "Engineering", - "parent": "root", - "value": 450000, - "children": ["eng_salaries", "eng_tools", "eng_cloud", "eng_training"], - }, - "eng_salaries": {"name": "Salaries", "parent": "engineering", "value": 280000}, - "eng_tools": {"name": "Tools & Software", "parent": "engineering", "value": 75000}, - "eng_cloud": {"name": "Cloud Services", "parent": "engineering", "value": 65000}, - "eng_training": {"name": "Training", "parent": "engineering", "value": 30000}, - "marketing": { - "name": "Marketing", - "parent": "root", - "value": 280000, - "children": ["mkt_digital", "mkt_content", "mkt_events", "mkt_brand"], - }, - "mkt_digital": {"name": "Digital Ads", "parent": "marketing", "value": 120000}, - "mkt_content": {"name": "Content Creation", "parent": "marketing", "value": 65000}, - "mkt_events": {"name": "Events", "parent": "marketing", "value": 55000}, - "mkt_brand": {"name": "Brand Design", "parent": "marketing", "value": 40000}, - "operations": { - "name": "Operations", - "parent": "root", - "value": 180000, - "children": ["ops_facilities", "ops_equipment", "ops_supplies"], - }, - "ops_facilities": {"name": "Facilities", "parent": "operations", "value": 95000}, - "ops_equipment": {"name": "Equipment", "parent": "operations", "value": 55000}, - "ops_supplies": {"name": "Supplies", "parent": "operations", "value": 30000}, - "hr": { - "name": "Human Resources", - "parent": "root", - "value": 90000, - "children": ["hr_recruiting", "hr_benefits", "hr_development"], - }, - "hr_recruiting": {"name": "Recruiting", "parent": "hr", "value": 35000}, - "hr_benefits": {"name": "Benefits Admin", "parent": "hr", "value": 30000}, - "hr_development": {"name": "Development", "parent": "hr", "value": 25000}, -} - -# Color scheme: Okabe-Ito for main departments, with shade variations for subcategories -colors = { - "Engineering": IMPRINT[0], # #009E73 - "Marketing": IMPRINT[1], # #C475FD - "Operations": IMPRINT[2], # #4467A3 - "Human Resources": IMPRINT[3], # #BD8233 - # Engineering sub-colors (green shades) - "Salaries": "#4A8BBE", - "Tools & Software": "#6BA3D6", - "Cloud Services": "#8CBBEE", - "Training": "#ADD3F5", - # Marketing sub-colors (orange/red shades) - "Digital Ads": "#E89A3C", - "Content Creation": "#E6A84E", - "Events": "#E3B660", - "Brand Design": "#E0C472", - # Operations sub-colors (blue shades) - "Facilities": "#5A9BD4", - "Equipment": "#75AAE0", - "Supplies": "#90B9EC", - # HR sub-colors (purple shades) - "Recruiting": "#D8A7C7", - "Benefits Admin": "#DFB5D3", - "Development": "#E6C3DF", -} - -# Create data for root level (main departments) -root_children = hierarchy_data["root"]["children"] -categories = [hierarchy_data[child_id]["name"] for child_id in root_children] -values = [hierarchy_data[child_id]["value"] for child_id in root_children] -total = sum(values) -percentages = [(v / total) * 100 for v in values] - -# Format value labels -value_labels = [f"${v // 1000}K" for v in values] -df = pd.DataFrame({"category": categories, "value": values, "pct": percentages, "value_label": value_labels}) - -# Preserve category order -df["category"] = pd.Categorical(df["category"], categories=categories, ordered=True) - -# Define colors in order -slice_colors = [colors[cat] for cat in categories] - -# Create labels for display -df["pct_label"] = [f"{p:.1f}%" for p in percentages] -df["combined_label"] = [ - f"{cat}: {lbl} ({pct:.0f}%)" for cat, lbl, pct in zip(categories, value_labels, percentages, strict=True) -] - -# Create main pie chart for static PNG -anyplot_theme = ( # noqa: F405 - theme( # noqa: F405 - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), # noqa: F405 - panel_background=element_rect(fill=PAGE_BG), # noqa: F405 - axis_title=element_text(color=INK), # noqa: F405 - axis_text=element_text(color=INK_SOFT), # noqa: F405 - plot_title=element_text(color=INK, size=24), # noqa: F405 - plot_subtitle=element_text(color=INK_SOFT, size=16), # noqa: F405 - legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT), # noqa: F405 - legend_text=element_text(color=INK_SOFT, size=14), # noqa: F405 - legend_title=element_text(color=INK, size=16), # noqa: F405 - ) -) - -plot = ( # noqa: F405 - ggplot(df) # noqa: F405 - + geom_pie( # noqa: F405 - aes(slice="value", fill="category"), # noqa: F405 - stat="identity", - size=50, - hole=0.35, - stroke=3, - color=PAGE_BG, - spacer_width=0.3, - labels=layer_labels().line("@{pct_label}").size(18), # noqa: F405 - ) - + scale_fill_manual(values=slice_colors) # noqa: F405 - + labs( # noqa: F405 - title="pie-drilldown · letsplot · anyplot.ai", - subtitle="Company Budget Breakdown · Click slices to explore (interactive HTML available)", - fill="Department", - ) - + ggsize(1600, 900) # noqa: F405 - + guides(fill=guide_legend(ncol=1)) # noqa: F405 - + anyplot_theme - + theme_void() # noqa: F405 - + theme( # noqa: F405 - plot_title=element_text(size=24, hjust=0.5, face="bold"), # noqa: F405 - plot_subtitle=element_text(size=16, hjust=0.5), # noqa: F405 - legend_position="right", - plot_margin=[40, 60, 40, 60], - ) -) - -# Save static PNG (scale 3 for 4800x2700) -export_ggsave(plot, filename=f"plot-{THEME}.png", path=".", scale=3) - -# Prepare data for all levels as JSON (for HTML interactivity) -levels_data = {} -for level_id in ["root", "engineering", "marketing", "operations", "hr"]: - level_data = hierarchy_data[level_id] - if "children" in level_data: - children_ids = level_data["children"] - cats = [hierarchy_data[cid]["name"] for cid in children_ids] - vals = [hierarchy_data[cid]["value"] for cid in children_ids] - tot = sum(vals) - pcts = [(v / tot) * 100 for v in vals] - levels_data[level_id] = { - "name": hierarchy_data[level_id]["name"], - "categories": cats, - "values": vals, - "percentages": pcts, - "colors": [colors.get(cat, IMPRINT[0]) for cat in cats], - "children": hierarchy_data[level_id].get("children", []), - "parent": hierarchy_data[level_id].get("parent"), - } - -# HTML template with embedded JavaScript for drilldown -html_bg = "#FAF8F1" if THEME == "light" else "#1A1A17" -html_text = "#1A1A17" if THEME == "light" else "#F0EFE8" -html_text_soft = "#4A4A44" if THEME == "light" else "#B8B7B0" -html_elevated = "#FFFDF6" if THEME == "light" else "#242420" - -html_content = f""" - - - - pie-drilldown · letsplot · anyplot.ai - - - -
-

pie-drilldown · letsplot · anyplot.ai

-

Company Budget Breakdown with Interactive Navigation

- - - -
- -
- -
- -
- Total: $1,000,000 -
- -

Click on a slice to drill down into subcategories

-
- - - -""" - -with open(f"plot-{THEME}.html", "w") as f: - f.write(html_content) diff --git a/plots/pie-drilldown/implementations/python/plotly.py b/plots/pie-drilldown/implementations/python/plotly.py deleted file mode 100644 index 09f979af8d..0000000000 --- a/plots/pie-drilldown/implementations/python/plotly.py +++ /dev/null @@ -1,287 +0,0 @@ -""" anyplot.ai -pie-drilldown: Drilldown Pie Chart with Click Navigation -Library: plotly 6.7.0 | Python 3.13.13 -Quality: 95/100 | Updated: 2026-05-15 -""" - -import json -import os - -import plotly.graph_objects as go - - -# Theme tokens -THEME = os.getenv("ANYPLOT_THEME", "light") -PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" -ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" -ELEVATED_DARK = "#F5F3ED" if THEME == "light" else "#3A3935" -INK = "#1A1A17" if THEME == "light" else "#F0EFE8" -INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" -ACCENT = "#009E73" - -# Okabe-Ito palette (first series must be #009E73) -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477"] - -# Hierarchical data for sales regions -hierarchy = { - "All": {"children": ["North America", "Europe", "Asia Pacific", "Latin America"], "parent": None}, - "North America": {"children": ["USA", "Canada", "Mexico"], "parent": "All"}, - "Europe": {"children": ["UK", "Germany", "France"], "parent": "All"}, - "Asia Pacific": {"children": ["Japan", "Australia", "India"], "parent": "All"}, - "Latin America": {"children": ["Brazil", "Argentina", "Chile"], "parent": "All"}, - # Leaf nodes with values (in millions) - "USA": {"value": 2400, "parent": "North America"}, - "Canada": {"value": 680, "parent": "North America"}, - "Mexico": {"value": 420, "parent": "North America"}, - "UK": {"value": 1200, "parent": "Europe"}, - "Germany": {"value": 980, "parent": "Europe"}, - "France": {"value": 750, "parent": "Europe"}, - "Japan": {"value": 1600, "parent": "Asia Pacific"}, - "Australia": {"value": 890, "parent": "Asia Pacific"}, - "India": {"value": 560, "parent": "Asia Pacific"}, - "Brazil": {"value": 640, "parent": "Latin America"}, - "Argentina": {"value": 380, "parent": "Latin America"}, - "Chile": {"value": 320, "parent": "Latin America"}, -} - -# Compute parent values (sum of children) -hierarchy["North America"]["value"] = sum(hierarchy[c]["value"] for c in hierarchy["North America"]["children"]) -hierarchy["Europe"]["value"] = sum(hierarchy[c]["value"] for c in hierarchy["Europe"]["children"]) -hierarchy["Asia Pacific"]["value"] = sum(hierarchy[c]["value"] for c in hierarchy["Asia Pacific"]["children"]) -hierarchy["Latin America"]["value"] = sum(hierarchy[c]["value"] for c in hierarchy["Latin America"]["children"]) -total_value = sum(hierarchy[c]["value"] for c in hierarchy["All"]["children"]) - -# Find top region for visual emphasis -top_region = max(hierarchy["All"]["children"], key=lambda x: hierarchy[x]["value"]) -top_value = hierarchy[top_region]["value"] -top_percent = 100 * top_value / total_value - -# Color mapping using Okabe-Ito (first series is always #009E73 for brand) -colors = { - "North America": IMPRINT[0], # #009E73 - brand green - "Europe": IMPRINT[1], # #C475FD - vermillion - "Asia Pacific": IMPRINT[2], # #4467A3 - blue - "Latin America": IMPRINT[3], # #BD8233 - reddish purple -} - -# Lighter shades for subcategories (derived from main colors with opacity in hover) -sub_colors = { - # North America shades - "USA": IMPRINT[0], - "Canada": IMPRINT[0], - "Mexico": IMPRINT[0], - # Europe shades - "UK": IMPRINT[1], - "Germany": IMPRINT[1], - "France": IMPRINT[1], - # Asia Pacific shades - "Japan": IMPRINT[2], - "Australia": IMPRINT[2], - "India": IMPRINT[2], - # Latin America shades - "Brazil": IMPRINT[3], - "Argentina": IMPRINT[3], - "Chile": IMPRINT[3], -} - -# Get data for top level -current_level = "All" -children = hierarchy[current_level]["children"] -values = [hierarchy[child]["value"] for child in children] -slice_colors = [colors[child] for child in children] - -# Create pie chart with enhanced styling -fig = go.Figure() - -fig.add_trace( - go.Pie( - labels=children, - values=values, - hole=0.35, - textinfo="label+percent", - textposition="outside", - textfont={"size": 22, "color": INK, "family": "Arial, sans-serif"}, - hovertemplate="%{label}
" - + "Sales: $%{value:.0f}M
" - + "Share: %{percent}
" - + "↻ Click to explore", - marker={"colors": slice_colors, "line": {"color": PAGE_BG, "width": 4}}, - pull=[0.08] * len(children), - sort=False, - ) -) - -# Enhanced center annotation with key insight -fig.add_annotation( - text="Global Sales
by Region", - x=0.5, - y=0.52, - font={"size": 26, "color": INK, "family": "Arial, sans-serif"}, - showarrow=False, - xref="paper", - yref="paper", -) - -# Top region insight annotation (visual storytelling) -fig.add_annotation( - text=f"🏆 Leading Region
{top_region}
${top_value:.0f}M ({top_percent:.1f}%)", - x=0.5, - y=0.35, - font={"size": 16, "color": INK}, - showarrow=False, - xref="paper", - yref="paper", - bgcolor=ELEVATED_DARK, - bordercolor=ACCENT, - borderwidth=2, - borderpad=12, - opacity=0.95, -) - -# Enhanced breadcrumb navigation with styling -fig.add_annotation( - text="📍 Navigate
All Regions", - x=0.02, - y=0.18, - xanchor="left", - yanchor="middle", - font={"size": 18, "color": INK, "family": "Arial, sans-serif"}, - showarrow=False, - xref="paper", - yref="paper", - bgcolor=ELEVATED_BG, - bordercolor=INK_SOFT, - borderwidth=2, - borderpad=12, - align="left", -) - -# Improved click instruction with visual emphasis -fig.add_annotation( - text="💡 Click any slice to drill down and explore by country", - x=0.5, - y=0.01, - xanchor="center", - yanchor="bottom", - font={"size": 16, "color": INK_SOFT}, - showarrow=False, - xref="paper", - yref="paper", -) - -fig.update_layout( - title={ - "text": "Global Sales Distribution by Region
plotly · anyplot.ai", - "font": {"size": 32, "color": INK, "family": "Arial, sans-serif"}, - "x": 0.5, - "xanchor": "center", - }, - showlegend=True, - legend={ - "orientation": "v", - "yanchor": "middle", - "y": 0.5, - "xanchor": "left", - "x": 1.02, - "font": {"size": 18, "color": INK_SOFT, "family": "Arial, sans-serif"}, - "title": {"text": "Sales Regions", "font": {"size": 20, "color": INK}}, - "bgcolor": ELEVATED_BG, - "bordercolor": INK_SOFT, - "borderwidth": 2, - "tracegroupgap": 10, - }, - paper_bgcolor=PAGE_BG, - plot_bgcolor=PAGE_BG, - font={"color": INK, "family": "Arial, sans-serif"}, - margin={"l": 80, "r": 240, "t": 120, "b": 100}, - height=900, - width=1600, -) - -# JavaScript for drilldown (only in HTML) -hierarchy_json = json.dumps(hierarchy) -colors_json = json.dumps(colors) -sub_colors_json = json.dumps(sub_colors) - -drilldown_js = f""" - -""" - -# Save PNG -fig.write_image(f"plot-{THEME}.png", width=1600, height=900, scale=3) - -# Save HTML with drilldown interactivity -html_content = fig.to_html( - full_html=True, - include_plotlyjs="cdn", - div_id="plotly-chart", - config={ - "displayModeBar": True, - "modeBarButtonsToRemove": ["lasso2d", "select2d"], - "toImageButtonOptions": {"format": "png", "width": 4800, "height": 2700}, - }, -) - -html_content = html_content.replace("", drilldown_js + "") - -with open(f"plot-{THEME}.html", "w") as f: - f.write(html_content) diff --git a/plots/pie-drilldown/implementations/python/pygal.py b/plots/pie-drilldown/implementations/python/pygal.py deleted file mode 100644 index 2118895e3b..0000000000 --- a/plots/pie-drilldown/implementations/python/pygal.py +++ /dev/null @@ -1,480 +0,0 @@ -""" anyplot.ai -pie-drilldown: Drilldown Pie Chart with Click Navigation -Library: pygal 3.1.0 | Python 3.13.13 -Quality: 82/100 | Updated: 2026-05-15 -""" - -import os -import sys - - -_script_dir = os.path.dirname(os.path.abspath(__file__)) -if _script_dir in sys.path: - sys.path.remove(_script_dir) - -import pygal # noqa: E402 -from pygal.style import Style # noqa: E402 - - -if _script_dir not in sys.path: - sys.path.insert(0, _script_dir) - -os.chdir(_script_dir) - -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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -IMPRINT = ("#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477") - -# Sales data hierarchy: region > country > product category -data = { - "root": {"name": "Global Sales", "children": ["americas", "europe", "asia_pacific"]}, - "americas": {"name": "Americas", "parent": "root", "value": 2800000, "children": ["us", "canada", "latam"]}, - "us": { - "name": "United States", - "parent": "americas", - "value": 1800000, - "children": ["us_electronics", "us_software", "us_services"], - }, - "us_electronics": {"name": "Electronics", "parent": "us", "value": 900000}, - "us_software": {"name": "Software", "parent": "us", "value": 650000}, - "us_services": {"name": "Services", "parent": "us", "value": 250000}, - "canada": { - "name": "Canada", - "parent": "americas", - "value": 650000, - "children": ["ca_electronics", "ca_software", "ca_services"], - }, - "ca_electronics": {"name": "Electronics", "parent": "canada", "value": 320000}, - "ca_software": {"name": "Software", "parent": "canada", "value": 220000}, - "ca_services": {"name": "Services", "parent": "canada", "value": 110000}, - "latam": { - "name": "Latin America", - "parent": "americas", - "value": 350000, - "children": ["la_electronics", "la_software", "la_services"], - }, - "la_electronics": {"name": "Electronics", "parent": "latam", "value": 170000}, - "la_software": {"name": "Software", "parent": "latam", "value": 115000}, - "la_services": {"name": "Services", "parent": "latam", "value": 65000}, - "europe": {"name": "Europe", "parent": "root", "value": 2200000, "children": ["uk", "germany", "france"]}, - "uk": { - "name": "United Kingdom", - "parent": "europe", - "value": 950000, - "children": ["uk_electronics", "uk_software", "uk_services"], - }, - "uk_electronics": {"name": "Electronics", "parent": "uk", "value": 480000}, - "uk_software": {"name": "Software", "parent": "uk", "value": 320000}, - "uk_services": {"name": "Services", "parent": "uk", "value": 150000}, - "germany": { - "name": "Germany", - "parent": "europe", - "value": 780000, - "children": ["de_electronics", "de_software", "de_services"], - }, - "de_electronics": {"name": "Electronics", "parent": "germany", "value": 390000}, - "de_software": {"name": "Software", "parent": "germany", "value": 260000}, - "de_services": {"name": "Services", "parent": "germany", "value": 130000}, - "france": { - "name": "France", - "parent": "europe", - "value": 470000, - "children": ["fr_electronics", "fr_software", "fr_services"], - }, - "fr_electronics": {"name": "Electronics", "parent": "france", "value": 235000}, - "fr_software": {"name": "Software", "parent": "france", "value": 160000}, - "fr_services": {"name": "Services", "parent": "france", "value": 75000}, - "asia_pacific": { - "name": "Asia Pacific", - "parent": "root", - "value": 1900000, - "children": ["japan", "china", "india"], - }, - "japan": { - "name": "Japan", - "parent": "asia_pacific", - "value": 850000, - "children": ["jp_electronics", "jp_software", "jp_services"], - }, - "jp_electronics": {"name": "Electronics", "parent": "japan", "value": 425000}, - "jp_software": {"name": "Software", "parent": "japan", "value": 300000}, - "jp_services": {"name": "Services", "parent": "japan", "value": 125000}, - "china": { - "name": "China", - "parent": "asia_pacific", - "value": 680000, - "children": ["cn_electronics", "cn_software", "cn_services"], - }, - "cn_electronics": {"name": "Electronics", "parent": "china", "value": 340000}, - "cn_software": {"name": "Software", "parent": "china", "value": 230000}, - "cn_services": {"name": "Services", "parent": "china", "value": 110000}, - "india": { - "name": "India", - "parent": "asia_pacific", - "value": 370000, - "children": ["in_electronics", "in_software", "in_services"], - }, - "in_electronics": {"name": "Electronics", "parent": "india", "value": 185000}, - "in_software": {"name": "Software", "parent": "india", "value": 130000}, - "in_services": {"name": "Services", "parent": "india", "value": 55000}, -} - -custom_style = Style( - background=PAGE_BG, - plot_background=PAGE_BG, - foreground=INK, - foreground_strong=INK, - foreground_subtle=INK_MUTED, - colors=IMPRINT, - title_font_size=28, - label_font_size=22, - major_label_font_size=18, - legend_font_size=16, - value_font_size=14, - tooltip_font_size=14, - stroke_width=2, -) - - -def create_pygal_chart(level_id, level_data): - """Create a pygal pie chart for a specific level.""" - children_ids = level_data.get("children", []) - if not children_ids: - return None - - total = sum(data[cid]["value"] for cid in children_ids) - - chart = pygal.Pie( - width=4800, - height=2700, - style=custom_style, - title=f"{level_data['name']} · pie-drilldown · pygal · anyplot.ai", - legend_at_bottom=True, - print_values=True, - print_labels=True, - ) - - def format_val(value): - pct = (value / total) * 100 - return f"${value:,.0f} ({pct:.1f}%)" - - chart.value_formatter = format_val - - for cid in children_ids: - child = data[cid] - chart.add(child["name"], [{"value": child["value"], "label": child["name"]}]) - - return chart.render(is_unicode=True) - - -# Generate SVG for all levels -svg_data = {} -for level_id in list(data.keys()): - level_svg = create_pygal_chart(level_id, data[level_id]) - if level_svg: - svg_data[level_id] = level_svg - -html_content = ( - """ - - - - pie-drilldown · pygal · anyplot.ai - - - -
-

Global Sales Drilldown · pygal · anyplot.ai

-
- - -
-
-

Click on a slice to explore regional sales breakdown

-
- - - -""" -) - -# Save theme-specific files -with open(f"plot-{THEME}.html", "w") as f: - f.write(html_content) - -# Render static PNG -root_svg = create_pygal_chart("root", data["root"]) -if root_svg: - # Create a temporary chart to render PNG - root_children = data["root"]["children"] - root_total = sum(data[cid]["value"] for cid in root_children) - - pie_chart = pygal.Pie( - width=4800, - height=2700, - style=custom_style, - title="pie-drilldown · pygal · anyplot.ai", - legend_at_bottom=True, - print_values=True, - print_labels=True, - ) - - def format_val(value): - pct = (value / root_total) * 100 - return f"${value:,.0f} ({pct:.1f}%)" - - pie_chart.value_formatter = format_val - - for child_id in root_children: - child = data[child_id] - pie_chart.add(child["name"], [{"value": child["value"], "label": child["name"]}]) - - pie_chart.render_to_png(f"plot-{THEME}.png") diff --git a/plots/pie-drilldown/metadata/python/altair.yaml b/plots/pie-drilldown/metadata/python/altair.yaml deleted file mode 100644 index 31cefb1888..0000000000 --- a/plots/pie-drilldown/metadata/python/altair.yaml +++ /dev/null @@ -1,235 +0,0 @@ -library: altair -language: python -specification_id: pie-drilldown -created: '2025-12-31T14:08:47Z' -updated: '2026-05-15T17:24:14Z' -generated_by: claude-haiku -workflow_run: 25930173306 -issue: 3072 -python_version: 3.13.13 -library_version: 6.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/python/altair/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/python/altair/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/python/altair/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/python/altair/plot-dark.html -quality_score: 80 -review: - strengths: - - 'Excellent data quality: realistic, plausible corporate budget scenario with good - distribution' - - Clean code structure following KISS principle with proper Altair API usage - - Good visual layout with well-balanced margins and spacing - - Proper theme-adaptive chrome (INK/INK_SOFT tokens) for both renders - - Donut chart design with center callout is visually refined - weaknesses: - - 'Palette non-compliance: Custom colors (#FFC863, #72C472, #BC8FBC) used for subcategories - violate the style guide rule against custom hexes when Okabe-Ito palette covers - the need' - - 'Value discrepancy in dark render: shows ''$0.22B'' instead of ''$3.20B'' (code - hardcodes correct value)' - - 'Spec-to-PNG gap: Interactive drilldown and dynamic breadcrumbs specified but - not visible in static PNG; only work in HTML version' - - 'Limited visual storytelling: chart displays data but lacks visual emphasis or - narrative emphasis' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) - correct - Title: "pie-drilldown · altair · anyplot.ai" - clear, dark text - Breadcrumb: "All Departments" - readable - Chrome: All axis, labels, tick text dark (#1A1A17) and readable against light surface - Data: Engineering (green #009E73), Marketing (orange #D55E00), Operations (blue #0072B2), HR (purple #CC79A7) - Okabe-Ito positions correct - Center text: "All Departments" in dark text, "$3.20B" in brand green - excellent contrast and readability - Legibility verdict: PASS - all text readable, no light-on-light issues - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) - correct - Title: "pie-drilldown · altair · anyplot.ai" - clear, light text - Breadcrumb: "All Departments" - visible and readable - Chrome: All labels in light colors (INK_SOFT #B8B7B0) - readable against dark surface - Data: Colors identical to light render (green #009E73, orange, blue, purple) - correctly theme-independent - Center text: "$0.22B" in cyan/teal - DISCREPANCY with light render which shows "$3.20B". This value (~220K) corresponds to HR department total, suggesting possible filtered/drilldown state or rendering artifact - Legibility verdict: PASS (except value mismatch) - all text readable, no dark-on-dark failures, but data inconsistency between renders is a concern - criteria_checklist: - visual_quality: - score: 24 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 5 - max: 8 - passed: true - comment: All explicitly set sizes, readable but some smaller labels could - optimize for large canvas - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: Slice labels well-positioned with no collisions - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: All slices clearly visible and distinguishable - - id: VQ-04 - name: Color Accessibility - score: 1 - max: 2 - passed: false - comment: Good contrast but custom colors reduce palette compliance - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Excellent use of canvas with balanced margins - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title and labels appropriate for chart type - - id: VQ-07 - name: Palette Compliance - score: 1 - max: 2 - passed: false - comment: Main categories correct Okabe-Ito; subcategories use non-compliant - custom hexes - design_excellence: - score: 9 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: false - comment: Well-configured defaults, competent but not exceptional - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: false - comment: Some refinement with donut styling, minimal additional customization - - id: DE-03 - name: Data Storytelling - score: 2 - max: 6 - passed: false - comment: Data displayed but lacks visual emphasis or narrative - spec_compliance: - score: 13 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct pie/donut chart type - - id: SC-02 - name: Required Features - score: 2 - max: 4 - passed: false - comment: Code has drilldown but not visible in static PNG; breadcrumbs static - only - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Pie slices correctly represent hierarchical values - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title format correct, legend appropriately omitted - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Shows all hierarchy levels with varied scales - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Corporate budget realistic and neutral - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Factually correct proportions - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Linear flow, no unnecessary functions - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Deterministic hardcoded data - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only used imports - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Well-organized, no fake functionality - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Correct output format - library_mastery: - score: 9 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: Proper use of Chart, mark_arc, layer, selection patterns - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: false - comment: Uses selection_point() and transform_filter() effectively - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - layer-composition - - hover-tooltips - - html-export - patterns: - - data-generation - - iteration-over-groups - dataprep: [] - styling: [] diff --git a/plots/pie-drilldown/metadata/python/bokeh.yaml b/plots/pie-drilldown/metadata/python/bokeh.yaml deleted file mode 100644 index 1a55a33cc6..0000000000 --- a/plots/pie-drilldown/metadata/python/bokeh.yaml +++ /dev/null @@ -1,252 +0,0 @@ -library: bokeh -language: python -specification_id: pie-drilldown -created: '2025-12-31T14:08:22Z' -updated: '2026-05-15T17:23:50Z' -generated_by: claude-haiku -workflow_run: 25930270446 -issue: 3072 -python_version: 3.13.13 -library_version: 3.9.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/python/bokeh/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/python/bokeh/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/python/bokeh/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/python/bokeh/plot-dark.html -quality_score: 85 -review: - strengths: - - Interactive drilldown functionality works correctly via CustomJS callbacks - - Theme adaptation (light and dark) is well implemented; text legible in both themes - - Clear, readable labels with contextual information (value and percentage) - - 'Complete spec compliance: pie chart with breadcrumb navigation, drill-down capability, - and click indicators' - - Realistic, hierarchical data that demonstrates all plot features across multiple - levels - - Clean code structure with proper use of Bokeh's distinctive patterns (CustomJS, - ColumnDataSource) - - Good visual hierarchy through adaptive label positioning based on slice size - weaknesses: - - First categorical series is not brand teal green (#009E73) but appears pink/magenta - (VQ-07 palette compliance failure) - - Design aesthetics use generic defaults rather than sophisticated refinement - - Visual refinement is minimal with no distinctive treatment of grid, whitespace, - or styling - - No visual storytelling or emphasis techniques (color contrast, size variation, - focal points) - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) - correct theme color - Chrome: Title "pie-drilldown · bokeh · anyplot.ai" centered and dark; breadcrumb "Total Expenses" at top; click indicator "Click a slice to drill down" at bottom - all clearly readable - Data: Four pie slices with labels showing name, dollar amount, percentage. Labels are white text positioned adaptively based on slice size. All legible. - Legibility verdict: PASS (text rendering is excellent; all elements readable) - Palette issue: FAIL - First series (Operations, largest slice) is pink/magenta, NOT brand teal green #009E73 - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) - correct theme color - Chrome: Title, breadcrumb, and indicator all in light text colors, clearly readable against dark background - Data: Same four slices with identical data colors to light render (only chrome changed as expected). All labels legible in light text. - Legibility verdict: PASS (text contrast is adequate in dark theme; no dark-on-dark failures) - Palette issue: FAIL - Same issue as light render: first series is pink/magenta, NOT #009E73 - - Both renders demonstrate good theme adaptation for chrome elements and text legibility. However, the critical palette compliance failure (VQ-07) is present in both renders: the first categorical series must always be #009E73 (brand teal green) according to the style guide, but the Operations slice renders in pink/magenta instead. - criteria_checklist: - visual_quality: - score: 28 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All font sizes explicitly set (48pt title, 36pt breadcrumb, 30pt - labels); perfectly readable in both themes - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: Label positioning adapts to slice size; no overlapping elements - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Pie slices well-sized and visually distinct; wedge outlines provide - definition - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Good contrast between data elements and background; CVD-safe palette - (Okabe-Ito) - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Pie centered with balanced margins; breadcrumb and indicator well-placed; - ~50% canvas utilization - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title format correct (spec-id · library · anyplot.ai); breadcrumb - is descriptive - - id: VQ-07 - name: Palette Compliance - score: 0 - max: 2 - passed: false - comment: 'CRITICAL FAILURE: First categorical series must be #009E73 (brand - teal green) but renders as pink/magenta in both light and dark renders' - design_excellence: - score: 9 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: false - comment: Well-configured library defaults with theme-adaptive colors, but - palette error undermines polish; no custom refinement beyond defaults - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: false - comment: Wedge outlines add subtle definition; minimal customization beyond - library defaults - - id: DE-03 - name: Data Storytelling - score: 2 - max: 6 - passed: false - comment: Data displayed clearly (hierarchical expense breakdown); no visual - emphasis or insight highlighting - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct pie chart type with all hierarchical features present - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: 'All features implemented: pie chart, hierarchical data, click-triggered - drilldown, breadcrumb navigation, value/percentage labels, click indicator' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Slices correctly sized by value; all data visible and properly mapped - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title format follows spec (pie-drilldown · bokeh · anyplot.ai); breadcrumb - updates correctly on navigation - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'Hierarchical structure complete: root → 4 categories → subcategories - → leaf nodes; shows all plot type aspects' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Company expense breakdown (operations, marketing, research, HR) is - realistic, plausible, and neutral - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Values range from ~$60K to ~$450K (realistic for business context); - proportions and relationships are factually coherent - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Imperative code: imports → data setup → figure creation → styling - → labels → callbacks → save; no unnecessary functions' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Hardcoded hierarchical data structure ensures deterministic output - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports (json, os, time, numpy, bokeh modules, selenium) are - used; no unnecessary dependencies - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean, Pythonic code; CustomJS callback appropriately implements - interactive drilldown without fake UI elements - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Correct output files (plot-{THEME}.png and plot-{THEME}.html); uses - current Bokeh API - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: High-level Bokeh API (figure, wedge, text, ColumnDataSource, TapTool); - CustomJS pattern is standard for interactivity - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: 'Uses Bokeh-specific features: CustomJS callbacks for drilldown, - ColumnDataSource for dynamic updates, Selenium headless rendering' - verdict: APPROVED -impl_tags: - dependencies: - - selenium - techniques: - - hover-tooltips - - html-export - patterns: - - data-generation - - columndatasource - dataprep: [] - styling: [] diff --git a/plots/pie-drilldown/metadata/python/letsplot.yaml b/plots/pie-drilldown/metadata/python/letsplot.yaml deleted file mode 100644 index cc1c354ce5..0000000000 --- a/plots/pie-drilldown/metadata/python/letsplot.yaml +++ /dev/null @@ -1,247 +0,0 @@ -library: letsplot -language: python -specification_id: pie-drilldown -created: '2025-12-31T21:35:57Z' -updated: '2026-05-15T17:14:10Z' -generated_by: claude-haiku -workflow_run: 25930557762 -issue: 3072 -python_version: 3.13.13 -library_version: 4.9.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/python/letsplot/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/python/letsplot/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/python/letsplot/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/python/letsplot/plot-dark.html -quality_score: 85 -review: - strengths: - - Excellent theme adaptation with data colors identical across light/dark renders - - 'Perfect visual quality: all text legible, no overlaps, proper layout and proportions' - - Correct Okabe-Ito palette usage with brand green as first series - - Clean, well-organized code with proper theme token definitions - - Realistic professional data context (company budgets) with accurate proportions - - Accurate percentage labels and legend values - - Correct title format and descriptive subtitle - weaknesses: - - Design lacks visual storytelling - shows proportions but doesn't emphasize key - insights - - Limited use of distinctive letsplot features; implementation is fairly generic - for the library - - PNG shows only root level due to static format; interactive drilldown is in HTML - only (acceptable but spec features not visually demonstrated in primary output) - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) - correctly rendered, not pure white - Chrome: Title "pie-drilldown · letsplot · anyplot.ai" in dark text (#1A1A17), subtitle in secondary color (#4A4A44), both clearly readable. Legend on right with department names and color swatches. All text properly sized and legible. - Data: Donut chart with four slices using Okabe-Ito palette - green (#009E73) 45.0%, orange (#D55E00) 28.0%, blue (#0072B2) 18.0%, pink (#CC79A7) 9.0%. Percentage labels clearly visible on each slice. White stroke separates slices clearly. - Legibility verdict: PASS - all elements clearly readable against light background. Excellent contrast and spacing. - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) - correctly rendered, not pure black - Chrome: Title in light text (#F0EFE8), subtitle/legend in secondary light (#B8B7B0). All text clearly visible against dark surface. No dark-on-dark failures. - Data: Identical donut colors to light render - green, orange, blue, pink remain exactly the same (only chrome adapted to theme). Percentage labels visible and readable. Dark stroke on donut appropriately themed. - Legibility verdict: PASS - both renders have identical data colors (theme-independent positions 1-7), chrome properly flipped to light colors for readability. - verdict: APPROVED - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All font sizes explicitly set (title 24pt, subtitle 16pt, legend - 14pt). Perfectly readable in both light and dark themes. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text. Legend positioned cleanly to right, labels well-spaced, - percentage labels clear on slices. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Donut slices clearly visible and distinct. Center hole well-proportioned. - All data elements properly sized and visible. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette is colorblind-safe. All colors provide sufficient - contrast and are distinguishable. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 'Perfect layout: chart centered, legend appropriately positioned, - balanced margins. Good use of canvas area.' - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Descriptive title with library/source attribution. Subtitle provides - context about interactivity. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series correctly #009E73. Multi-series follows Okabe-Ito: - #D55E00, #0072B2, #CC79A7. Backgrounds #FAF8F1 (light) and #1A1A17 (dark). - Chrome correctly theme-adaptive in both renders.' - design_excellence: - score: 10 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: false - comment: Well-configured library defaults with clean minimal aesthetic. Uses - Okabe-Ito correctly and theme_void() appropriately. No exceptional design - choices beyond defaults. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: false - comment: 'Good refinement: theme_void() removes unnecessary axes, generous - spacing, clean colors. Could use more custom styling.' - - id: DE-03 - name: Data Storytelling - score: 2 - max: 6 - passed: false - comment: Data displayed straightforwardly without visual hierarchy. Shows - proportions but doesn't emphasize insights like 'Engineering dominates'. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct donut/pie chart implementation. - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Root-level pie shown with percentage labels. Hierarchical data structure - present. HTML provides full drilldown/breadcrumbs/click interaction. Static - PNG is appropriate for letsplot; interactive version available. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Slice sizes correctly represent budget proportions. All departments - visible with accurate percentages. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title format correct: ''pie-drilldown · letsplot · anyplot.ai''. - Legend labels match department data.' - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'Shows all four main departments with realistic variation: Engineering - 45%, Marketing 28%, Operations 18%, HR 9%.' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Professional company budget scenario. Departments are realistic and - neutral. Real-world plausible distribution. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: 'Budget proportions are realistic: Engineering typically largest, - followed by Marketing, Operations, then HR support. Factually plausible.' - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Clean linear flow: imports → data → plot → export. No unnecessary - functions or classes.' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Hardcoded deterministic data. No random elements requiring seed. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only necessary imports (json, os, pandas, lets_plot). All are actually - used in the code. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean, Pythonic code. Proper theme token definitions. No fake functionality - or over-engineering. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Correctly outputs plot-{THEME}.png and plot-{THEME}.html using export_ggsave - function. - library_mastery: - score: 5 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: false - comment: Correct ggplot API usage (geom_pie, scale_fill_manual, theme customization). - Follows letsplot patterns well but fairly standard approach. - - id: LM-02 - name: Distinctive Features - score: 1 - max: 5 - passed: false - comment: Uses layer_labels() for slice labeling (letsplot-specific), but this - is basic. No advanced library-specific features beyond standard pie chart. -impl_tags: - dependencies: [] - techniques: - - html-export - patterns: - - data-generation - dataprep: [] - styling: - - minimal-chrome diff --git a/plots/pie-drilldown/metadata/python/plotly.yaml b/plots/pie-drilldown/metadata/python/plotly.yaml deleted file mode 100644 index d079a6157e..0000000000 --- a/plots/pie-drilldown/metadata/python/plotly.yaml +++ /dev/null @@ -1,263 +0,0 @@ -library: plotly -language: python -specification_id: pie-drilldown -created: '2025-12-31T14:04:59Z' -updated: '2026-05-15T17:01:46Z' -generated_by: claude-haiku -workflow_run: 25929983648 -issue: 3072 -python_version: 3.13.13 -library_version: 6.7.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/python/plotly/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/python/plotly/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/python/plotly/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/python/plotly/plot-dark.html -quality_score: 95 -review: - strengths: - - Professional polish with brand green as primary color and intentional hierarchy - - Complete implementation of all drilldown spec requirements (breadcrumb, click - navigation, animation, labels) - - Excellent use of plotly-specific features (Pie trace with hole, Plotly.animate - events, rich hover templates) - - Flawless theme adaptation—both light and dark renders pass legibility checks with - no dark-on-dark failures - - Thoughtful data storytelling via 'Leading Region' highlight that guides viewer - focus - - Clean, reproducible code structure with proper separation of data, theme tokens, - and styling logic - - Okabe-Ito palette correctly applied with first series as brand green (#009E73), - maintained consistently across both themes - weaknesses: [] - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1), not pure white — correct - Chrome: Title, axis labels (region names), tick labels (percentages), legend, annotations all in dark ink (#1A1A17) — fully readable against light background - Data: Four slices in Okabe-Ito order — North America green (#009E73), Europe vermillion, Asia Pacific blue, Latin America reddish purple. First series is brand green. Slices clearly separated with white borders (4px). - Styling: Center annotation "Global Sales by Region", "Leading Region" callout box (with accent border), "Navigate All Regions" breadcrumb, bottom instruction text. All elements have proper contrast and sizing (16-32px range). - Legibility verdict: PASS — all text elements are clearly readable at full resolution. - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17), not pure black — correct - Chrome: Title, axis labels, tick labels, legend, annotations all in light ink (#F0EFE8) — fully readable against dark background. No dark-on-dark failures. - Data: Slice colors are IDENTICAL to light render — North America green, Europe orange, Asia Pacific blue, Latin America reddish purple. Only chrome adapted to dark theme. - Styling: "Leading Region" callout displays light text on elevated dark background with accent border. Breadcrumb and instruction text in appropriate soft ink color for dark theme. All elements remain legible. - Legibility verdict: PASS — both data and chrome are fully legible in dark theme. Theme adaptation is correct and complete. - - Comparison: Data colors are identical; only chrome (backgrounds, text colors, border colors) adapted to theme. Both renders pass all legibility checks. - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All text (title, labels, annotations, legend) clearly readable in - both light and dark themes with proper contrast and sizing (16-32px range) - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: All text elements properly positioned with no collisions — slices - labeled outside pie, legend separate on right, annotations in distinct areas - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: All markers, slices, borders, and text clearly visible — white 4px - slice borders ensure definition, pull effect (0.08) adds separation - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette is CVD-safe for deuteranopia/protanopia — all four - colors distinctly different, no red-green dependence - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 1600x900 at 3x scale = 4800x2700px as specified — nothing cut off, - balanced composition with generous margins (80, 240, 120, 100) - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title 'Global Sales Distribution by Region' is descriptive, subtitle - identifies plotly library and anyplot.ai source - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First categorical series is #009E73 (brand green) — correct. Multi-series - follows Okabe-Ito positions 1-4. Backgrounds are #FAF8F1 (light) / #1A1A17 - (dark). Both renders theme-correct.' - design_excellence: - score: 15 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 6 - max: 8 - passed: true - comment: Professional polish with intentional hierarchy — brand green primary, - thoughtful 'Leading Region' highlight, multiple annotation layers create - visual storytelling. Not generic. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Spines removed by default (pie chart), legend has refined styling - with background/border, generous margins, slice pull effect (0.08) adds - visual interest. Could be more refined. - - id: DE-03 - name: Data Storytelling - score: 5 - max: 6 - passed: true - comment: Clear visual hierarchy — center annotation guides attention, 'Leading - Region' callout creates focal point ($3500M, 32.3%), breadcrumb element - enables exploration narrative. Excellent for hierarchical data. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Donut pie chart (hole=0.35) with drilldown capability — correct chart - type for spec - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: 'All spec features implemented: breadcrumb trail (Navigate box), - click drilldown (JavaScript handlers), animation (Plotly.animate 500ms), - percentage labels (label+percent), value labels (hover), consistent colors - (Okabe-Ito by region)' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Regions mapped to slice sizes by value, percentages calculated, hierarchy - structure (parent-child) correctly implemented in data and JavaScript - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title format: ''Global Sales Distribution by Region · plotly · anyplot.ai'' - (correct). Legend labeled ''Sales Regions'' with all four region names.' - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'Pie chart shows all aspects of type: slices (regions), percentages, - values, hierarchy depth (4 regions -> 12 countries), consistent color scheme - by branch' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Sales data is real-world plausible ($2.4B USA, $680M Canada, etc.), - neutral region names (no bias), sensible distribution (North America 32%, - Europe 27%, Asia Pacific 28%, Latin America 12%) - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Values in millions of dollars — appropriate scale for regional sales. - Hierarchy depth (2 levels shown, 3 total in data) matches 'drilldown' spec - requirement. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: No functions or classes — straightforward script. Simple dict-based - hierarchy, clear data flow. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Deterministic data (fixed hierarchy, no randomness), environment - variable for theme selection. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: 'Only used imports: json (hierarchy serialization), os (theme from - env), plotly.graph_objects (figure creation).' - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Appropriate complexity, no fake UI elements, code is readable and - logical. JavaScript for drilldown is well-structured. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: 'Correct output format: plot-{THEME}.png and plot-{THEME}.html (not - bare plot.png). Current plotly API.' - library_mastery: - score: 10 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: High-level API (go.Figure, go.Pie), proper use of update_layout, - correct annotation pattern, idiomatic HTML export with CDN. Best practices - throughout. - - id: LM-02 - name: Distinctive Features - score: 5 - max: 5 - passed: true - comment: 'Leverages plotly-specific capabilities: Pie with hole parameter - (donut), rich hovertemplate with HTML styling, Plotly.animate for transitions, - plotly_click/plotly_doubleclick events, custom annotation styling with borders/backgrounds.' - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - annotations - - hover-tooltips - - html-export - patterns: - - data-generation - dataprep: [] - styling: - - publication-ready diff --git a/plots/pie-drilldown/metadata/python/pygal.yaml b/plots/pie-drilldown/metadata/python/pygal.yaml deleted file mode 100644 index 3f54895cfb..0000000000 --- a/plots/pie-drilldown/metadata/python/pygal.yaml +++ /dev/null @@ -1,234 +0,0 @@ -library: pygal -language: python -specification_id: pie-drilldown -created: '2025-12-31T14:11:12Z' -updated: '2026-05-15T17:09:06Z' -generated_by: claude-haiku -workflow_run: 25930365882 -issue: 3072 -python_version: 3.13.13 -library_version: 3.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/python/pygal/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/python/pygal/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/python/pygal/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/python/pygal/plot-dark.html -quality_score: 82 -review: - strengths: - - 'Perfect theme handling: both light and dark renders correctly adapted with identical - data colors and theme-correct chrome' - - 'Clear data visualization: Okabe-Ito palette professional and CVD-safe with no - legibility issues in either theme' - - 'Strong data quality: realistic neutral business scenario with appropriate scale - and full hierarchy demonstration' - - 'Clean implementation: linear code structure, idiomatic pygal usage, proper theme-token - system' - - 'Comprehensive feature demonstration: HTML file shows interactive drilldown with - breadcrumbs, back button, and click navigation as specified' - weaknesses: - - 'Title format non-compliant: currently ''Global Sales Distribution · pie-drilldown'', - should be ''pie-drilldown · pygal · anyplot.ai'' per spec format requirement' - - 'Generic design: no intentional aesthetic choices beyond Okabe-Ito defaults; missing - custom refinement that would elevate from well-configured to professionally designed' - - 'No visual storytelling: static PNG displays three equal-ish regions with no hierarchy - or emphasis; lacks narrative or insight focus' - - 'Minimal library mastery in PNG: while interactive HTML showcases pygal capabilities, - the static PNG doesn''t leverage distinctive pygal features beyond basic charting' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1), correctly theme-adapted - Chrome: Title "Global Sales Distribution · pie-drilldown" in dark text (28px), clearly readable. Legend at bottom in 16px with region names (Americas, Europe, Asia Pacific). All text is dark-on-light and fully legible. - Data: Pie chart with three slices using Okabe-Ito colors—green (#009E73) for Americas, orange (#D55E00) for Europe, blue (#0072B2) for Asia Pacific. Each slice labeled with currency value and percentage (e.g., "$1.9M (39.8%)") at 14px. All markers clearly distinguishable. First series correctly uses brand green #009E73. - Legibility verdict: PASS — All text is readable against the light background; no contrast failures. - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17), correctly theme-adapted - Chrome: Title and legend now rendered in lighter tones for dark background. Title remains readable. Legend text light-colored against dark background. All chrome elements correctly theme-adapted with no dark-on-dark failures. - Data: The three pie slices are IDENTICAL in color to the light render (green #009E73, orange #D55E00, blue #0072B2). Only the chrome has flipped themes. Slice labels appear in appropriate contrast colors. All data colors remain constant across both renders as required. - Legibility verdict: PASS — All text is readable against the dark background; data colors are identical to light render; excellent theme implementation with no dark-on-dark failures. - - Summary: Both renders pass all theme-readability checks. Data colors are theme-independent; chrome is correctly adapted. Theme handling is exemplary. - criteria_checklist: - visual_quality: - score: 28 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: Title, legend, and value labels explicitly sized and readable in - both themes; slight margin for potential contrast refinement - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: All text fully readable; no overlapping elements - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Three slices perfectly proportioned and visible; colors distinct - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette CVD-safe; good contrast in both themes - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Plot fills 60-70% of canvas; balanced margins; excellent utilization - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Descriptive title - - id: VQ-07 - name: Palette Compliance - score: 1 - max: 2 - passed: false - comment: Okabe-Ito colors correct; backgrounds theme-correct; BUT title missing - library name and website suffix - design_excellence: - score: 8 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: false - comment: Well-configured defaults but no intentional design choices beyond - requirements - - id: DE-02 - name: Visual Refinement - score: 2 - max: 6 - passed: false - comment: Clean but minimal customization; library defaults - - id: DE-03 - name: Data Storytelling - score: 2 - max: 6 - passed: false - comment: Three equal regions with no hierarchy or visual emphasis; data displayed - without narrative - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct pie chart - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: 'All spec features present: breadcrumbs, back button, click navigation, - labels, colors' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Correct hierarchy and values - - id: SC-04 - name: Title & Legend - score: 2 - max: 3 - passed: false - comment: Legend correct; title format incorrect—missing '· pygal · anyplot.ai' - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'Full hierarchy: 3 regions → 3 countries → 3 categories' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Real, neutral business scenario - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Plausible values and proportions - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Linear flow; no over-engineering - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Static data; fully reproducible - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only necessary imports - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean and idiomatic - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Correct output format - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Uses pygal.Pie and Style correctly; standard API usage - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: false - comment: Uses Style and value_formatter; modest leverage of library-specific - features - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - html-export - patterns: - - iteration-over-groups - dataprep: [] - styling: [] diff --git a/plots/pie-drilldown/specification.md b/plots/pie-drilldown/specification.md deleted file mode 100644 index c3debeeb2a..0000000000 --- a/plots/pie-drilldown/specification.md +++ /dev/null @@ -1,30 +0,0 @@ -# pie-drilldown: Drilldown Pie Chart with Click Navigation - -## Description - -A pie chart with hierarchical drilldown functionality that displays one level of data at a time. Clicking on any slice reveals a detailed breakdown of that category's subcategories as a new pie chart. Breadcrumb navigation allows users to traverse back up the hierarchy. This interactive visualization is ideal for exploring multi-level categorical data without the visual complexity of showing all levels simultaneously. - -## Applications - -- Sales data exploration by region, then country, then city -- Budget breakdown from department to team to individual expense categories -- Website analytics drilling from traffic source to campaign to keyword -- Product category navigation from main category to subcategory to product line - -## Data - -- `id` (string) - unique identifier for each node -- `name` (string) - display label for the category -- `value` (numeric) - value for leaf nodes (determines slice size) -- `parent` (string) - id of parent node (null/empty for root level) -- Size: 3-8 categories per level, 2-4 hierarchy levels -- Structure: hierarchical/tree with parent-child relationships - -## Notes - -- Show breadcrumb trail at top (e.g., "All > Electronics > Phones") -- Include a "back" button or clickable breadcrumbs to navigate up -- Animate transitions when drilling down or up -- Display percentage and value labels on slices -- Use consistent color schemes within branches when possible -- Show a visual indicator (icon or cursor change) that slices are clickable diff --git a/plots/pie-drilldown/specification.yaml b/plots/pie-drilldown/specification.yaml deleted file mode 100644 index 196f3c61e0..0000000000 --- a/plots/pie-drilldown/specification.yaml +++ /dev/null @@ -1,29 +0,0 @@ -# Specification-level metadata for pie-drilldown -# Auto-synced to PostgreSQL on push to main - -spec_id: pie-drilldown -title: Drilldown Pie Chart with Click Navigation - -# Specification tracking -created: 2025-12-31T13:39:43Z -updated: 2026-05-15T16:42:18Z -issue: 3072 -suggested: MarkusNeusinger - -# Classification tags (applies to all library implementations) -# See docs/reference/tagging-system.md for detailed guidelines -tags: - plot_type: - - pie - data_type: - - categorical - - hierarchical - - numeric - domain: - - general - - business - features: - - interactive - - drilldown - - navigation - - animated diff --git a/plots/pie-portfolio-interactive/implementations/julia/makie.jl b/plots/pie-portfolio-interactive/implementations/julia/makie.jl deleted file mode 100644 index d8853c6a39..0000000000 --- a/plots/pie-portfolio-interactive/implementations/julia/makie.jl +++ /dev/null @@ -1,117 +0,0 @@ -# anyplot.ai -# pie-portfolio-interactive: Interactive Portfolio Allocation Chart -# Library: makie 0.22.10 | Julia 1.11.9 -# Quality: 83/100 | Created: 2026-05-27 - -using CairoMakie -using Colors -using Random - -Random.seed!(42) - -# Theme tokens -const THEME = get(ENV, "ANYPLOT_THEME", "light") -const PAGE_BG = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17" -const ELEVATED_BG = THEME == "light" ? colorant"#FFFDF6" : colorant"#242420" -const INK = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8" -const INK_SOFT = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0" -const INK_MUTED = THEME == "light" ? colorant"#6B6A63" : colorant"#A8A79F" - -const IMPRINT_PALETTE = [ - colorant"#009E73", # 1 — brand green (Equities) - colorant"#C475FD", # 2 — lavender (Fixed Income) - colorant"#4467A3", # 3 — blue (Alternatives) - colorant"#BD8233", # 4 — ochre (Cash) -] - -# Data — sample $100M multi-asset portfolio -categories = ["Equities", "Fixed Income", "Alternatives", "Cash"] -weights = [40.0, 30.0, 20.0, 10.0] # percentages, sum = 100 - -# Cumulative angles: clockwise from 12 o'clock (π/2) -cum_angles = [π / 2] -for w in weights - push!(cum_angles, last(cum_angles) - w / 100.0 * 2π) -end - -# Donut geometry -r_inner = 0.38 -r_outer = 1.00 -r_label = 1.22 -n_arc = 80 - -# Title (54 chars < 67 — no scaling needed, titlesize = 20 is fine) -title_str = "pie-portfolio-interactive · julia · makie · anyplot.ai" - -# Figure — square canvas (2400 × 2400 at px_per_unit=2) -fig = Figure( - size = (1200, 1200), - fontsize = 14, - backgroundcolor = PAGE_BG, -) - -ax = Axis( - fig[1, 1]; - title = title_str, - titlesize = 20, - titlecolor = INK, - aspect = DataAspect(), - backgroundcolor = PAGE_BG, -) -hidedecorations!(ax) -hidespines!(ax) -xlims!(ax, -1.42, 1.42) -ylims!(ax, -1.42, 1.42) - -# Donut slices and percentage labels -for i in 1:length(categories) - θ = range(cum_angles[i], cum_angles[i + 1]; length = n_arc) - outer_pts = [Point2f(cos(t) * r_outer, sin(t) * r_outer) for t in θ] - inner_pts = [Point2f(cos(t) * r_inner, sin(t) * r_inner) for t in reverse(θ)] - poly!(ax, vcat(outer_pts, inner_pts); - color = IMPRINT_PALETTE[i], - strokewidth = 4, - strokecolor = PAGE_BG) - - mid_θ = (cum_angles[i] + cum_angles[i + 1]) / 2 - abs_val = Int(round(weights[i])) # $1M per percentage point of $100M total - label_text = string(abs_val) * "%\n\$" * string(abs_val) * "M" - # Slightly larger label for the dominant slice to create focal-point emphasis - label_size = i == 1 ? 17 : 15 - text!(ax, cos(mid_θ) * r_label, sin(mid_θ) * r_label; - text = label_text, - fontsize = label_size, - color = INK, - align = (:center, :center)) -end - -# Center labels in the donut hole -text!(ax, 0.0, 0.09; - text = "Portfolio", - fontsize = 18, - color = INK, - align = (:center, :center)) -text!(ax, 0.0, -0.10; - text = "\$100M AUM", - fontsize = 14, - color = INK_SOFT, - align = (:center, :center)) - -# Horizontal legend below the chart -legend_entries = [PolyElement(color = IMPRINT_PALETTE[i], strokewidth = 0) for i in 1:length(categories)] -legend_labels = [categories[i] * " " * string(Int(round(weights[i]))) * "% \$" * string(Int(round(weights[i]))) * "M" for i in 1:length(categories)] - -Legend( - fig[2, 1], legend_entries, legend_labels; - orientation = :horizontal, - framevisible = false, - labelcolor = INK, - labelsize = 14, - backgroundcolor = PAGE_BG, - tellwidth = false, - padding = (0, 0, 6, 6), -) - -rowgap!(fig.layout, 1, 4) - -save("plot-$(THEME).png", fig; px_per_unit = 2) diff --git a/plots/pie-portfolio-interactive/implementations/python/altair.py b/plots/pie-portfolio-interactive/implementations/python/altair.py deleted file mode 100644 index b2776bf5e6..0000000000 --- a/plots/pie-portfolio-interactive/implementations/python/altair.py +++ /dev/null @@ -1,178 +0,0 @@ -""" anyplot.ai -pie-portfolio-interactive: Interactive Portfolio Allocation Chart -Library: altair 6.1.0 | Python 3.13.13 -Quality: 89/100 | Updated: 2026-05-27 -""" - -import os - -import altair as alt -import pandas as pd -from PIL import Image - - -# 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" -# Use INK_SOFT in dark mode so ring gaps remain visible against dark arc segments -RING_STROKE = PAGE_BG if THEME == "light" else INK_SOFT - -IMPRINT_PALETTE = ["#009E73", "#C475FD", "#4467A3"] - -# Portfolio data -holdings = pd.DataFrame( - { - "asset": [ - "Apple Inc.", - "Microsoft", - "Amazon", - "Nvidia", - "US Treasury 10Y", - "Corporate Bonds", - "Municipal Bonds", - "Gold ETF", - "Real Estate Fund", - "Commodities", - ], - "weight": [15.0, 12.0, 10.0, 8.0, 18.0, 12.0, 5.0, 8.0, 7.0, 5.0], - "category": [ - "Equities", - "Equities", - "Equities", - "Equities", - "Fixed Income", - "Fixed Income", - "Fixed Income", - "Alternatives", - "Alternatives", - "Alternatives", - ], - } -) - -# Category totals for outer ring — is_dominant flags Equities (45%) for visual emphasis -category_totals = ( - holdings.groupby("category").agg(total_weight=("weight", "sum"), num_holdings=("asset", "count")).reset_index() -) -category_totals["is_dominant"] = (category_totals["category"] == "Equities").astype(int) - -# Color scale using Imprint palette (green=equities growth, purple=fixed income, blue=alternatives) -category_domain = ["Equities", "Fixed Income", "Alternatives"] -color_scale = alt.Scale(domain=category_domain, range=IMPRINT_PALETTE) - -# Category selection drives drill-down of the inner ring -category_sel = alt.selection_point(fields=["category"], empty=True) - -# Outer ring: category-level totals — click to drill down -# Equities (dominant at 45%) gets a thicker stroke for subtle visual emphasis -outer_ring = ( - alt.Chart(category_totals) - .mark_arc(innerRadius=175, outerRadius=248, stroke=RING_STROKE) - .encode( - theta=alt.Theta("total_weight:Q", stack=True), - color=alt.Color( - "category:N", - scale=color_scale, - legend=alt.Legend( - title="Asset Class", - titleFontSize=14, - labelFontSize=12, - orient="right", - symbolSize=200, - titleColor=INK, - labelColor=INK_SOFT, - fillColor=ELEVATED_BG, - strokeColor=INK_SOFT, - padding=8, - cornerRadius=4, - symbolStrokeWidth=0, - ), - ), - strokeWidth=alt.condition(alt.datum.is_dominant == 1, alt.value(5), alt.value(2)), - opacity=alt.condition(category_sel, alt.value(1.0), alt.value(0.25)), - tooltip=[ - alt.Tooltip("category:N", title="Asset Class"), - alt.Tooltip("total_weight:Q", title="Allocation (%)", format=".1f"), - alt.Tooltip("num_holdings:Q", title="Holdings"), - ], - ) - .add_params(category_sel) -) - -# Inner ring: individual holdings filtered by selection (drill-down detail view) -# When no category selected → shows all 10 holdings; click outer ring → shows only that category -inner_ring = ( - alt.Chart(holdings) - .transform_filter(category_sel) - .mark_arc(innerRadius=81, outerRadius=170, stroke=RING_STROKE, strokeWidth=2) - .encode( - theta=alt.Theta("weight:Q", stack=True), - color=alt.Color("category:N", scale=color_scale, legend=None), - opacity=alt.value(0.85), - tooltip=[ - alt.Tooltip("asset:N", title="Holding"), - alt.Tooltip("weight:Q", title="Portfolio Weight (%)", format=".1f"), - alt.Tooltip("category:N", title="Asset Class"), - ], - ) -) - -# Center text — typographic hierarchy: large primary stat + smaller supporting label -center_pct = ( - alt.Chart(pd.DataFrame({"label": ["100%"]})) - .mark_text(fontSize=28, fontWeight="bold", align="center", baseline="middle", color=INK, dy=-16) - .encode(text="label:N") -) -center_sub = ( - alt.Chart(pd.DataFrame({"label": ["10 Holdings"]})) - .mark_text(fontSize=14, fontWeight="normal", align="center", baseline="middle", color=INK_SOFT, dy=14) - .encode(text="label:N") -) - -# Title — canonical format with language token -title_str = "pie-portfolio-interactive · python · altair · anyplot.ai" -subtitle_str = "Click outer ring to drill into category holdings · click again to reset" - -chart = ( - alt.layer(outer_ring, inner_ring, center_pct, center_sub) - .properties( - width=366, - height=406, - background=PAGE_BG, - title=alt.Title( - title_str, - fontSize=16, - subtitle=subtitle_str, - subtitleFontSize=12, - anchor="middle", - color=INK, - subtitleColor=INK_SOFT, - ), - ) - .configure_view(stroke=None, fill=PAGE_BG) - .configure_legend(padding=8, cornerRadius=4, symbolStrokeWidth=0) - .configure_axis( - domainColor=INK_SOFT, tickColor=INK_SOFT, gridColor=INK, gridOpacity=0.15, labelColor=INK_SOFT, titleColor=INK - ) -) - -# Save -chart.save(f"plot-{THEME}.png", scale_factor=4.0) -chart.save(f"plot-{THEME}.html") - -# PAD to exact 2400×2400 (square target for pie/donut charts) -TW, TH = 2400, 2400 -_img = Image.open(f"plot-{THEME}.png").convert("RGB") -_w, _h = _img.size -if _w > TW or _h > TH: - raise SystemExit( - f"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. " - f"Shrink chart .properties(width=, height=) values and re-render." - ) -if _w < TW or _h < TH: - _canvas = Image.new("RGB", (TW, TH), PAGE_BG) - _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2)) - _canvas.save(f"plot-{THEME}.png") diff --git a/plots/pie-portfolio-interactive/implementations/python/bokeh.py b/plots/pie-portfolio-interactive/implementations/python/bokeh.py deleted file mode 100644 index c3839d8193..0000000000 --- a/plots/pie-portfolio-interactive/implementations/python/bokeh.py +++ /dev/null @@ -1,387 +0,0 @@ -""" anyplot.ai -pie-portfolio-interactive: Interactive Portfolio Allocation Chart -Library: bokeh 3.9.0 | Python 3.13.13 -Quality: 84/100 | Updated: 2026-05-27 -""" - -import os -import sys -import time -from pathlib import Path - - -# Remove the script's own directory from sys.path so "bokeh" resolves to the -# installed package, not this file (which shares the name). -_this_dir = os.path.dirname(os.path.abspath(__file__)) -sys.path = [p for p in sys.path if os.path.abspath(p) != _this_dir] - -import numpy as np -from bokeh.io import output_file, save -from bokeh.models import ColumnDataSource, CustomJS, HoverTool, LabelSet, Legend, LegendItem -from bokeh.plotting import figure -from selenium import webdriver -from selenium.webdriver.chrome.options import Options - - -# 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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -IMPRINT_PALETTE = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477", "#99B314"] - -# Semantic color mapping: green=growth, blue=stable, ochre=commodity, muted=neutral cash -category_colors = { - "Equities": IMPRINT_PALETTE[0], # #009E73 green - "Fixed Income": IMPRINT_PALETTE[2], # #4467A3 blue - "Alternatives": IMPRINT_PALETTE[3], # #BD8233 ochre - "Cash": INK_MUTED, # muted neutral -} - -# Portfolio data — 10 holdings across 4 asset classes -np.random.seed(42) -holdings = { - "Equities": { - "asset": ["Apple Inc.", "Microsoft", "Google", "Int'l Equities"], - "weight": [18, 15, 12, 10], - "value": [180000, 150000, 120000, 100000], - }, - "Fixed Income": {"asset": ["US Treasury 10Y", "Corporate Bonds"], "weight": [15, 10], "value": [150000, 100000]}, - "Alternatives": { - "asset": ["Gold ETF", "Real Estate Fund", "Commodities"], - "weight": [5, 8, 3], - "value": [50000, 80000, 30000], - }, - "Cash": {"asset": ["Cash"], "weight": [4], "value": [40000]}, -} - -total_portfolio_value = sum(sum(v["value"]) for v in holdings.values()) # 1,000,000 -categories = list(holdings.keys()) - -INNER_R = 0.42 -OUTER_R = 0.82 -LABEL_R = 1.04 # labels outside ring for both overview and detail - -# --- Category overview data --- -cat_weights = np.array([sum(holdings[c]["weight"]) for c in categories]) -cat_values = np.array([sum(holdings[c]["value"]) for c in categories]) -cat_angles = cat_weights / cat_weights.sum() * 2 * np.pi -cat_end = np.cumsum(cat_angles) - np.pi / 2 -cat_start = cat_end - cat_angles -cat_mid = (cat_start + cat_end) / 2 - -overview_source = ColumnDataSource( - data={ - "category": categories, - "weight": cat_weights.tolist(), - "value": cat_values.tolist(), - "color": [category_colors[c] for c in categories], - "start_angle": cat_start.tolist(), - "end_angle": cat_end.tolist(), - "label_x": (LABEL_R * np.cos(cat_mid)).tolist(), - "label_y": (LABEL_R * np.sin(cat_mid)).tolist(), - "label_text": [f"{c}\n{w}%" for c, w in zip(categories, cat_weights.tolist(), strict=True)], - } -) - -# --- Pre-computed per-category holdings data (passed to JS) --- -detail_data_js = {} -for cat in categories: - ws = np.array(holdings[cat]["weight"]) - angs = ws / ws.sum() * 2 * np.pi - ends = np.cumsum(angs) - np.pi / 2 - starts = ends - angs - mids = (starts + ends) / 2 - detail_data_js[cat] = { - "asset": holdings[cat]["asset"], - "weight": ws.tolist(), - "value": holdings[cat]["value"], - "category": [cat] * len(ws), - "color": [category_colors[cat]] * len(ws), - "start_angle": starts.tolist(), - "end_angle": ends.tolist(), - "label_x": (LABEL_R * np.cos(mids)).tolist(), - "label_y": (LABEL_R * np.sin(mids)).tolist(), - "total_value": sum(holdings[cat]["value"]), - "total_weight": int(ws.sum()), - } - -# --- Detail source (initially hidden, populated by JS) --- -detail_source = ColumnDataSource( - data={ - "asset": [""], - "weight": [0.0], - "value": [0], - "category": [""], - "color": [category_colors["Equities"]], - "start_angle": [0.0], - "end_angle": [0.0], - "label_x": [0.0], - "label_y": [0.0], - } -) - -# Center label sources — updated by JS callback on drill-down -center_header_source = ColumnDataSource(data={"x": [0], "y": [0.12], "text": ["Total Portfolio"]}) -center_value_source = ColumnDataSource(data={"x": [0], "y": [-0.08], "text": [f"${total_portfolio_value:,.0f}"]}) - -# --- Figure --- -W, H = 2400, 2400 -title = "pie-portfolio-interactive · python · bokeh · anyplot.ai" -n = len(title) -ratio = 67 / n if n > 67 else 1.0 -title_pt = max(34, round(50 * ratio)) - -p = figure( - width=W, height=H, title=title, x_range=(-1.38, 1.38), y_range=(-1.38, 1.38), tools="tap", toolbar_location=None -) - -p.background_fill_color = PAGE_BG -p.border_fill_color = PAGE_BG -p.outline_line_color = None -p.axis.visible = False -p.grid.visible = False - -p.title.text_font_size = f"{title_pt}pt" -p.title.text_color = INK -p.title.align = "center" - -# --- Overview layer: 4 category wedges --- -overview_glyph = p.annular_wedge( - x=0, - y=0, - inner_radius=INNER_R, - outer_radius=OUTER_R, - start_angle="start_angle", - end_angle="end_angle", - color="color", - alpha=0.92, - source=overview_source, - line_color=PAGE_BG, - line_width=6, - hover_fill_alpha=1.0, - hover_line_color=INK, - hover_line_width=8, - name="overview_wedge", -) - -overview_labels = LabelSet( - x="label_x", - y="label_y", - text="label_text", - source=overview_source, - text_font_size="34pt", - text_align="center", - text_baseline="middle", - text_color=INK, -) -p.add_layout(overview_labels) - -# HoverTool scoped to overview wedge -overview_hover = HoverTool( - renderers=[overview_glyph], - tooltips=[("Asset Class", "@category"), ("Total Weight", "@weight{0.0}%"), ("Total Value", "$$@value{0,0}")], - mode="mouse", -) -p.add_tools(overview_hover) - -# --- Detail layer: individual holdings of selected category --- -detail_glyph = p.annular_wedge( - x=0, - y=0, - inner_radius=INNER_R, - outer_radius=OUTER_R, - start_angle="start_angle", - end_angle="end_angle", - color="color", - alpha=0.92, - source=detail_source, - line_color=PAGE_BG, - line_width=6, - hover_fill_alpha=1.0, - hover_line_color=INK, - hover_line_width=8, - name="detail_wedge", - visible=False, -) - -detail_labels = LabelSet( - x="label_x", - y="label_y", - text="asset", - source=detail_source, - text_font_size="34pt", - text_align="center", - text_baseline="middle", - text_color=INK, - visible=False, -) -p.add_layout(detail_labels) - -detail_hover = HoverTool( - renderers=[detail_glyph], - tooltips=[("Asset", "@asset"), ("Category", "@category"), ("Weight", "@weight{0.0}%"), ("Value", "$$@value{0,0}")], - mode="mouse", -) -p.add_tools(detail_hover) - -# Center label: "Total Portfolio" header + value -p.add_layout( - LabelSet( - x="x", - y="y", - text="text", - source=center_header_source, - text_font_size="26pt", - text_align="center", - text_baseline="middle", - text_color=INK_SOFT, - ) -) -p.add_layout( - LabelSet( - x="x", - y="y", - text="text", - source=center_value_source, - text_font_size="34pt", - text_align="center", - text_baseline="middle", - text_color=INK, - text_font_style="bold", - ) -) - -# --- Legend (inside plot, top-right) --- -legend_items = [] -for cat, color in category_colors.items(): - dummy = ColumnDataSource(data={"x": [9999], "y": [9999]}) - r = p.scatter(x="x", y="y", size=20, color=color, source=dummy, marker="square") - legend_items.append(LegendItem(label=cat, renderers=[r])) - -legend = Legend(items=legend_items, location="top_right") -legend.label_text_font_size = "34pt" -legend.label_text_color = INK_SOFT -legend.glyph_width = 44 -legend.glyph_height = 44 -legend.spacing = 18 -legend.padding = 28 -legend.background_fill_color = ELEVATED_BG -legend.background_fill_alpha = 0.92 -legend.border_line_color = INK_SOFT -legend.border_line_width = 1 -p.add_layout(legend) - -# --- JS Callback: click overview category → drill into its holdings --- -drill_callback = CustomJS( - args={ - "overview_source": overview_source, - "detail_source": detail_source, - "center_header": center_header_source, - "center_value": center_value_source, - "overview_glyph": overview_glyph, - "overview_labels": overview_labels, - "detail_glyph": detail_glyph, - "detail_labels": detail_labels, - "all_details": detail_data_js, - "total_portfolio": total_portfolio_value, - }, - code=""" - const indices = overview_source.selected.indices; - if (indices.length === 0) return; - const idx = indices[0]; - const cat = overview_source.data["category"][idx]; - overview_source.selected.indices = []; - - const det = all_details[cat]; - - // Populate detail source with holdings of selected category - detail_source.data = { - "asset": det.asset, - "weight": det.weight, - "value": det.value, - "category": det.category, - "color": det.color, - "start_angle": det.start_angle, - "end_angle": det.end_angle, - "label_x": det.label_x, - "label_y": det.label_y, - }; - detail_source.change.emit(); - - // Switch to detail view - overview_glyph.visible = false; - overview_labels.visible = false; - detail_glyph.visible = true; - detail_labels.visible = true; - - // Update center labels to show category totals - center_header.data = {"x": [0], "y": [0.12], "text": [cat]}; - center_value.data = {"x": [0], "y": [-0.08], "text": ["$" + det.total_value.toLocaleString() + " (" + det.total_weight + "%)"]}; - center_header.change.emit(); - center_value.change.emit(); - - // Show or create the Back to Overview button - let btn = document.getElementById('ap-back-btn'); - if (!btn) { - btn = document.createElement('button'); - btn.id = 'ap-back-btn'; - btn.textContent = '← Back to Overview'; - btn.style.cssText = [ - 'position:fixed', 'top:24px', 'left:24px', - 'background:#009E73', 'color:#fff', - 'border:none', 'border-radius:6px', - 'padding:10px 22px', 'font-size:17px', - 'font-weight:600', 'cursor:pointer', - 'z-index:1000', 'font-family:system-ui,sans-serif', - 'box-shadow:0 3px 12px rgba(0,0,0,0.22)' - ].join(';'); - document.body.appendChild(btn); - - btn.addEventListener('click', function() { - // Restore category overview - overview_glyph.visible = true; - overview_labels.visible = true; - detail_glyph.visible = false; - detail_labels.visible = false; - - center_header.data = {"x": [0], "y": [0.12], "text": ["Total Portfolio"]}; - center_value.data = {"x": [0], "y": [-0.08], "text": ["$" + total_portfolio.toLocaleString()]}; - center_header.change.emit(); - center_value.change.emit(); - - detail_source.selected.indices = []; - detail_source.change.emit(); - - btn.style.display = 'none'; - }); - } - btn.style.display = 'block'; -""", -) -overview_source.selected.js_on_change("indices", drill_callback) - -# Save interactive HTML -output_file(f"plot-{THEME}.html", title="Interactive Portfolio Allocation Chart") -save(p) - -# Screenshot with headless Chrome — use driver.save_screenshot() per library guide -opts = Options() -for arg in ( - "--headless=new", - "--no-sandbox", - "--disable-dev-shm-usage", - "--disable-gpu", - f"--window-size={W},{H}", - "--hide-scrollbars", -): - opts.add_argument(arg) -driver = webdriver.Chrome(options=opts) -driver.set_window_size(W, H) -driver.get(f"file://{Path(f'plot-{THEME}.html').resolve()}") -time.sleep(3) -driver.save_screenshot(f"plot-{THEME}.png") -driver.quit() diff --git a/plots/pie-portfolio-interactive/implementations/python/letsplot.py b/plots/pie-portfolio-interactive/implementations/python/letsplot.py deleted file mode 100644 index 0dc2ee4d94..0000000000 --- a/plots/pie-portfolio-interactive/implementations/python/letsplot.py +++ /dev/null @@ -1,189 +0,0 @@ -""" anyplot.ai -pie-portfolio-interactive: Interactive Portfolio Allocation Chart -Library: letsplot 4.10.1 | Python 3.13.13 -Quality: 89/100 | Updated: 2026-05-27 -""" - -import os - -import pandas as pd -from lets_plot import ( - LetsPlot, - aes, - element_rect, - element_text, - geom_label, - geom_pie, - ggbunch, - ggplot, - ggsize, - labs, - layer_labels, - layer_tooltips, - scale_fill_manual, - theme, - theme_void, -) -from lets_plot.export import ggsave - - -LetsPlot.setup_html() - -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" - -# Imprint palette — semantic mapping for asset classes -# Equities → green (growth/profit), Fixed Income → blue (safe/conservative) -# Alternatives → purple, Cash → ochre (store of value) -CATEGORY_COLORS = {"Equities": "#009E73", "Fixed Income": "#4467A3", "Alternatives": "#C475FD", "Cash": "#BD8233"} - -# Imprint palette positions 1→8 for individual holdings -HOLDING_COLORS = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477", "#99B314"] - -# Portfolio data — institutional multi-asset allocation -portfolio_data = { - "asset": [ - "Apple Inc.", - "Microsoft Corp.", - "Amazon.com", - "NVIDIA Corp.", - "US Treasury 10Y", - "Corporate Bonds AAA", - "Municipal Bonds", - "Real Estate Fund", - "Gold ETF", - "Private Equity", - "Cash Reserves", - ], - "weight": [12.0, 10.0, 8.0, 7.0, 18.0, 12.0, 8.0, 10.0, 6.0, 5.0, 4.0], - "category": [ - "Equities", - "Equities", - "Equities", - "Equities", - "Fixed Income", - "Fixed Income", - "Fixed Income", - "Alternatives", - "Alternatives", - "Alternatives", - "Cash", - ], -} -df = pd.DataFrame(portfolio_data) - -# Aggregate by category -category_weights = df.groupby("category", as_index=False)["weight"].sum() -category_weights = category_weights.sort_values("weight", ascending=False) -category_order = category_weights["category"].tolist() - -# Enrich for tooltips -category_weights["holdings_count"] = category_weights["category"].apply(lambda c: len(df[df["category"] == c])) -category_weights["holdings_preview"] = category_weights["category"].apply( - lambda c: ", ".join(df[df["category"] == c]["asset"].tolist()) -) -# Suppress slice labels for very small segments (< 5%) to avoid cramped text -category_weights["pct_label"] = category_weights["weight"].apply(lambda w: f"{w:.1f}%" if w >= 5.0 else "") - -center_df = pd.DataFrame({"x": [0.0], "y": [0.0], "label": ["Portfolio\n100%"]}) - -# Shared theme — theme_void base + anyplot chrome tokens -chart_theme = theme( - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - plot_title=element_text(size=13, hjust=0.5, face="bold", color=INK), - plot_subtitle=element_text(size=10, hjust=0.5, color=INK_SOFT), - legend_title=element_text(size=11, color=INK), - legend_text=element_text(size=10, color=INK_SOFT), - legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT), - legend_position="right", - plot_caption=element_text(size=9, hjust=0.5, face="bold", color="#4467A3"), - plot_margin=[20, 15, 10, 15], -) - -# Overview pie — all asset classes, interactive tooltips with holdings preview -plot_overview = ( - ggplot(category_weights) - + geom_pie( - aes(slice="weight", fill="category"), - stat="identity", - size=45, - hole=0.35, - stroke=1.5, - color=PAGE_BG, - tooltips=layer_tooltips() - .title("@category") - .line("Allocation: @weight%") - .line("Holdings: @holdings_count assets") - .line("@holdings_preview") - .format("weight", ".1f"), - labels=layer_labels().line("@pct_label").size(7), - ) - + geom_label( - aes(x="x", y="y", label="label"), data=center_df, size=9, fill=ELEVATED_BG, color=INK, label_padding=0.5 - ) - + scale_fill_manual(values=list(CATEGORY_COLORS.values()), limits=list(CATEGORY_COLORS.keys())) - + labs( - title="pie-portfolio-interactive · python · letsplot · anyplot.ai", - subtitle="Hover for details · Drill-down panels in HTML", - caption="★ Fixed Income leads at 38% — the largest asset class in this portfolio", - fill="Asset Class", - ) - + ggsize(600, 600) - + theme_void() - + chart_theme -) - -# Drill-down plots — one per asset class, showing individual holdings -drill_down_plots = {} -for cat in category_order: - cat_df = df[df["category"] == cat].copy().sort_values("weight", ascending=False) - cat_total = cat_df["weight"].sum() - cat_df["relative_weight"] = (cat_df["weight"] / cat_total * 100).round(1) - n = len(cat_df) - cat_colors = HOLDING_COLORS[:n] - center_cat_df = pd.DataFrame({"x": [0.0], "y": [0.0], "label": [f"{cat}\n{cat_total:.0f}%"]}) - - drill_down_plots[cat] = ( - ggplot(cat_df) - + geom_pie( - aes(slice="weight", fill="asset"), - stat="identity", - size=18, - hole=0.35, - stroke=1.5, - color=PAGE_BG, - tooltips=layer_tooltips() - .title("@asset") - .line("Portfolio weight: @weight%") - .line("Category share: @relative_weight%") - .format("weight", ".1f") - .format("relative_weight", ".1f"), - labels=layer_labels().line("@weight%").format("weight", ".1f").size(7), - ) - + geom_label( - aes(x="x", y="y", label="label"), data=center_cat_df, size=9, fill=ELEVATED_BG, color=INK, label_padding=0.5 - ) - + scale_fill_manual(values=cat_colors, limits=cat_df["asset"].tolist()) - + labs(title=f"{cat} Holdings", subtitle=f"Total: {cat_total:.0f}% of portfolio", fill="Holding") - + ggsize(600, 600) - + theme_void() - + chart_theme - ) - -# PNG — static overview, square canvas: 600×600 × scale=4 → 2400×2400 -ggsave(plot_overview, filename=f"plot-{THEME}.png", path=".", scale=4) - -# HTML — overview + all four drill-down panels in a ggbunch layout -# Overview left (40%), drill-downs in 2×2 grid on right (30%+30%) -plots = [plot_overview] + list(drill_down_plots.values()) -regions = [(0.0, 0.0, 0.4, 1.0)] -for i in range(len(drill_down_plots)): - col = i % 2 - row = i // 2 - regions.append((0.4 + col * 0.3, row * 0.5, 0.3, 0.5)) - -bunch = ggbunch(plots, regions) + ggsize(1200, 700) -ggsave(bunch, filename=f"plot-{THEME}.html", path=".") diff --git a/plots/pie-portfolio-interactive/implementations/python/matplotlib.py b/plots/pie-portfolio-interactive/implementations/python/matplotlib.py deleted file mode 100644 index d2e057d5e0..0000000000 --- a/plots/pie-portfolio-interactive/implementations/python/matplotlib.py +++ /dev/null @@ -1,125 +0,0 @@ -""" anyplot.ai -pie-portfolio-interactive: Interactive Portfolio Allocation Chart -Library: matplotlib 3.10.9 | Python 3.13.13 -Quality: 88/100 | Updated: 2026-05-27 -""" - -import os - -import matplotlib.pyplot as plt -from matplotlib.patches import Patch - - -# 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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" -PCT_COLOR = "#FFFDF6" if THEME == "light" else "#F0EFE8" - -IMPRINT_PALETTE = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477", "#99B314"] - -# Data — diversified balanced fund allocation -PORTFOLIO_TOTAL = 1_000_000 - -categories = { - "Equities": {"US Large Cap": 25.0, "International": 15.0, "Emerging Markets": 8.0}, - "Fixed Income": {"Government Bonds": 18.0, "Corporate Bonds": 12.0}, - "Alternatives": {"Real Estate": 10.0, "Commodities": 7.0}, - "Cash": {"Money Market": 5.0}, -} - -cat_labels = list(categories.keys()) -cat_weights = [sum(assets.values()) for assets in categories.values()] -cat_colors = IMPRINT_PALETTE[:4] - -holdings = [] -holding_weights = [] -holding_cat_idx = [] - -for i, (_cat, assets) in enumerate(categories.items()): - for asset, weight in assets.items(): - holdings.append(asset) - holding_weights.append(weight) - holding_cat_idx.append(i) - -holding_colors = [cat_colors[i] for i in holding_cat_idx] - -# Plot — square canvas for pie/donut chart -fig, ax = plt.subplots(figsize=(6, 6), dpi=400, facecolor=PAGE_BG) -ax.set_facecolor(PAGE_BG) -fig.subplots_adjust(left=0.0, right=0.52, top=0.92, bottom=0.02) - -# Inner ring — asset class totals -inner_wedges, _, inner_pcts = ax.pie( - cat_weights, - labels=None, - colors=cat_colors, - autopct=lambda pct: f"{pct:.0f}%", - pctdistance=0.70, - startangle=90, - radius=0.56, - wedgeprops={"linewidth": 2.0, "edgecolor": PAGE_BG, "width": 0.32}, - textprops={"fontsize": 8, "fontweight": "bold"}, -) -for txt in inner_pcts: - txt.set_color(PCT_COLOR) - -# Outer ring — individual holdings (slightly transparent to suggest sub-level) -outer_wedges, _, outer_pcts = ax.pie( - holding_weights, - labels=None, - colors=holding_colors, - autopct=lambda pct: f"{pct:.0f}%" if pct >= 8 else "", - pctdistance=0.84, - startangle=90, - radius=1.0, - wedgeprops={"linewidth": 1.5, "edgecolor": PAGE_BG, "width": 0.44}, - textprops={"fontsize": 7}, -) -for wedge in outer_wedges: - wedge.set_alpha(0.72) -for txt in outer_pcts: - txt.set_color(PCT_COLOR) - -# Center label -ax.text(0, 0, "Portfolio\nAllocation", ha="center", va="center", fontsize=10, fontweight="bold", color=INK) - -# Legend — categories with indented sub-holdings -legend_handles = [] -legend_labels = [] -for i, cat in enumerate(cat_labels): - legend_handles.append(Patch(facecolor=cat_colors[i], edgecolor=INK_MUTED, linewidth=0.8)) - cat_value = cat_weights[i] / 100 * PORTFOLIO_TOTAL - legend_labels.append(f"{cat} {cat_weights[i]:.0f}% ${cat_value:,.0f}") - for j in range(len(holdings)): - if holding_cat_idx[j] == i: - legend_handles.append(Patch(facecolor=cat_colors[i], alpha=0.60, edgecolor=INK_MUTED, linewidth=0.5)) - holding_value = holding_weights[j] / 100 * PORTFOLIO_TOTAL - legend_labels.append(f" {holdings[j]} {holding_weights[j]:.0f}% ${holding_value:,.0f}") - -leg = ax.legend( - legend_handles, - legend_labels, - loc="center left", - bbox_to_anchor=(1.06, 0.5), - fontsize=8, - frameon=True, - title="Asset Allocation", - title_fontsize=8, - borderpad=1.0, - labelspacing=0.55, -) -leg.get_frame().set_facecolor(ELEVATED_BG) -leg.get_frame().set_edgecolor(INK_SOFT) -plt.setp(leg.get_texts(), color=INK_SOFT) -leg.get_title().set_color(INK) - -# Title — centered on full figure width (not just the pie axes) to prevent clipping -title = "pie-portfolio-interactive · python · matplotlib · anyplot.ai" -title_fontsize = max(8, round(12 * 67 / len(title))) if len(title) > 67 else 12 -fig.text(0.50, 0.96, title, ha="center", va="top", fontsize=title_fontsize, fontweight="medium", color=INK) - -fig.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG) diff --git a/plots/pie-portfolio-interactive/implementations/python/plotly.py b/plots/pie-portfolio-interactive/implementations/python/plotly.py deleted file mode 100644 index d3e9c7fb6b..0000000000 --- a/plots/pie-portfolio-interactive/implementations/python/plotly.py +++ /dev/null @@ -1,194 +0,0 @@ -""" anyplot.ai -pie-portfolio-interactive: Interactive Portfolio Allocation Chart -Library: plotly 6.7.0 | Python 3.13.13 -Quality: 91/100 | Updated: 2026-05-27 -""" - -import os - -import pandas as pd -import plotly.graph_objects as go - - -# 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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -# Imprint palette — semantic color mapping for ESG portfolio categories -# Sustainable Equities → brand green (environmental) -# Technology → blue (tech/stability) -# Green Fixed Income → lavender (debt markets, calm) -# Real Assets → ochre (earth, tangible) -CATEGORY_COLORS = { - "Sustainable Equities": "#009E73", - "Technology": "#4467A3", - "Green Fixed Income": "#C475FD", - "Real Assets": "#BD8233", -} - -# Data — ESG-focused portfolio (technology-forward, climate-aligned) -portfolio_data = { - "asset": [ - # Sustainable Equities - "Ørsted A/S", - "NextEra Energy", - "Vestas Wind", - "Brookfield Renewable", - # Technology - "Microsoft Corp.", - "Alphabet Inc.", - "Schneider Electric", - # Green Fixed Income - "Green Bonds AAA", - "Climate Bonds", - # Real Assets - "Clean Water ETF", - "Timber REIT", - ], - "weight": [9.0, 11.0, 7.0, 8.0, 10.0, 8.0, 7.0, 15.0, 12.0, 8.0, 5.0], - "category": [ - "Sustainable Equities", - "Sustainable Equities", - "Sustainable Equities", - "Sustainable Equities", - "Technology", - "Technology", - "Technology", - "Green Fixed Income", - "Green Fixed Income", - "Real Assets", - "Real Assets", - ], -} - -df = pd.DataFrame(portfolio_data) -colors = [CATEGORY_COLORS[cat] for cat in df["category"]] - -# Category summary for "By Category" toggle view -category_summary = df.groupby("category")["weight"].sum().reset_index() -category_summary = category_summary.sort_values("weight", ascending=False).reset_index(drop=True) -category_summary_colors = [CATEGORY_COLORS[cat] for cat in category_summary["category"]] - -# Title — length 56 chars, under 67-char baseline → no scaling needed -title_text = "pie-portfolio-interactive · python · plotly · anyplot.ai" -title_fontsize = 16 - -# Figure -fig = go.Figure() - -# Main donut trace — all individual holdings -fig.add_trace( - go.Pie( - labels=df["asset"], - values=df["weight"], - hole=0.42, - marker=dict(colors=colors, line=dict(color=PAGE_BG, width=3)), - textinfo="percent", - textposition="inside", - textfont=dict(size=13, color=PAGE_BG), - insidetextorientation="horizontal", - hovertemplate=("%{label}
Weight: %{value:.1f}%
Category: %{customdata}
"), - customdata=df["category"], - pull=[0.02] * len(df), - rotation=90, - ) -) - -# Center annotation -fig.add_annotation(text="ESG
Portfolio", x=0.5, y=0.5, font=dict(size=22, color=INK), showarrow=False) - -# Toggle buttons — All Holdings vs By Category -fig.update_layout( - updatemenus=[ - dict( - type="buttons", - direction="right", - x=0.5, - y=1.10, - xanchor="center", - buttons=[ - dict( - label="All Holdings", - method="update", - args=[ - { - "labels": [df["asset"].tolist()], - "values": [df["weight"].tolist()], - "marker": [dict(colors=colors, line=dict(color=PAGE_BG, width=3))], - "customdata": [df["category"].tolist()], - }, - {"annotations[0].text": "ESG
Portfolio"}, - ], - ), - dict( - label="By Category", - method="update", - args=[ - { - "labels": [category_summary["category"].tolist()], - "values": [category_summary["weight"].tolist()], - "marker": [dict(colors=category_summary_colors, line=dict(color=PAGE_BG, width=3))], - "customdata": [category_summary["category"].tolist()], - }, - {"annotations[0].text": "Asset Class
Overview"}, - ], - ), - ], - font=dict(size=13, color=INK), - bgcolor=ELEVATED_BG, - bordercolor=INK_SOFT, - ) - ] -) - -# Layout -fig.update_layout( - autosize=False, - paper_bgcolor=PAGE_BG, - plot_bgcolor=PAGE_BG, - font=dict(color=INK), - title=dict(text=title_text, font=dict(size=title_fontsize, color=INK), x=0.5, y=0.97, xanchor="center"), - showlegend=True, - legend=dict( - title=dict(text="Asset Class", font=dict(size=13, color=INK)), - font=dict(size=12, color=INK_SOFT), - bgcolor=ELEVATED_BG, - bordercolor=INK_SOFT, - borderwidth=1, - orientation="h", - yanchor="top", - y=-0.04, - xanchor="center", - x=0.5, - itemsizing="constant", - ), - margin=dict(t=140, b=160, l=60, r=60), - xaxis=dict(visible=False), - yaxis=dict(visible=False), -) - -# Custom legend entries — asset classes with total weights -for category, color in CATEGORY_COLORS.items(): - category_weight = df[df["category"] == category]["weight"].sum() - fig.add_trace( - go.Scatter( - x=[None], - y=[None], - mode="markers", - marker=dict(size=18, color=color), - name=f"{category} ({category_weight:.1f}%)", - showlegend=True, - hoverinfo="skip", - ) - ) - -# Hide individual pie legend entries — only category legend shows -fig.update_traces(showlegend=False, selector=dict(type="pie")) - -# Save — square canvas for symmetric pie chart -fig.write_image(f"plot-{THEME}.png", width=600, height=600, scale=4) -fig.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn") diff --git a/plots/pie-portfolio-interactive/implementations/python/plotnine.py b/plots/pie-portfolio-interactive/implementations/python/plotnine.py deleted file mode 100644 index c9bf91f27b..0000000000 --- a/plots/pie-portfolio-interactive/implementations/python/plotnine.py +++ /dev/null @@ -1,242 +0,0 @@ -""" anyplot.ai -pie-portfolio-interactive: Interactive Portfolio Allocation Chart -Library: plotnine 0.15.4 | Python 3.13.13 -Quality: 85/100 | Updated: 2026-05-27 -""" - -import os -import sys - - -_here = __file__.replace("\\", "/").rsplit("/", 1)[0] -sys.path = [p for p in sys.path if p not in ("", ".", _here)] - -import numpy as np # noqa: E402 -import pandas as pd # noqa: E402 -from plotnine import ( # noqa: E402 - aes, - arrow, - coord_fixed, - element_blank, - element_rect, - element_text, - geom_polygon, - geom_segment, - geom_text, - ggplot, - labs, - scale_fill_identity, - scale_x_continuous, - scale_y_continuous, - theme, -) - - -# Theme tokens -THEME = os.getenv("ANYPLOT_THEME", "light") -PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" -INK = "#1A1A17" if THEME == "light" else "#F0EFE8" -INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" - -# anyplot categorical palette — hybrid-v3 order -IMPRINT_PALETTE = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477", "#99B314"] - -# Portfolio data — Imprint palette with semantic assignments -np.random.seed(42) - -TOTAL_PORTFOLIO = 1_200_000 - -MAIN_COLORS = { - "Equities": IMPRINT_PALETTE[0], # #009E73 green — growth / equities - "Fixed Income": IMPRINT_PALETTE[2], # #4467A3 blue — stability / bonds - "Alternatives": IMPRINT_PALETTE[1], # #C475FD lavender — non-standard assets - "Cash": IMPRINT_PALETTE[3], # #BD8233 ochre — value / currency -} -MAIN_WEIGHTS = {"Equities": 45, "Fixed Income": 30, "Alternatives": 15, "Cash": 10} - -# Equities sub-holdings — semantic colors within holding categories -HOLDINGS = [ - {"name": "Tech Stocks", "weight": 18, "color": IMPRINT_PALETTE[5]}, # #2ABCCD cyan — tech - {"name": "Healthcare", "weight": 12, "color": IMPRINT_PALETTE[6]}, # #954477 rose — health - {"name": "Financials", "weight": 10, "color": IMPRINT_PALETTE[2]}, # #4467A3 blue — finance - {"name": "Energy", "weight": 5, "color": IMPRINT_PALETTE[3]}, # #BD8233 ochre — commodity -] - -# Geometry constants -OUTER_R = 80 -INNER_R = 40 -OUTER_R_D = 62 -INNER_R_D = 30 -CX_MAIN = -95 -CX_DETAIL = 90 -CY = 0 -GAP = 0.025 -N_PTS = 50 - -# Build main donut polygons (asset classes) — inline, no function -rows_main = [] -label_rows_main_pct = [] -label_rows_main_usd = [] -angle = np.pi / 2 - -for asset_class, weight in MAIN_WEIGHTS.items(): - color = MAIN_COLORS[asset_class] - sweep = (weight / 100) * 2 * np.pi - end_angle = angle - sweep - arcs = np.linspace(end_angle + GAP, angle - GAP, N_PTS) - outer_pts = list(zip(CX_MAIN + OUTER_R * np.cos(arcs), CY + OUTER_R * np.sin(arcs), strict=False)) - inner_pts = list(zip(CX_MAIN + INNER_R * np.cos(arcs[::-1]), CY + INNER_R * np.sin(arcs[::-1]), strict=False)) - all_pts = outer_pts + inner_pts + [outer_pts[0]] - for order, (x, y) in enumerate(all_pts): - rows_main.append({"x": x, "y": y, "segment": asset_class, "order": order, "fill": color}) - mid_a = (angle + end_angle) / 2 - pct_r = (INNER_R + OUTER_R) / 2 + 7 - usd_r = (INNER_R + OUTER_R) / 2 - 7 - dollar_val = weight / 100 * TOTAL_PORTFOLIO - dollar_str = f"${dollar_val / 1000:.0f}K" - label_rows_main_pct.append( - {"x": CX_MAIN + pct_r * float(np.cos(mid_a)), "y": CY + pct_r * float(np.sin(mid_a)), "label": f"{weight}%"} - ) - label_rows_main_usd.append( - {"x": CX_MAIN + usd_r * float(np.cos(mid_a)), "y": CY + usd_r * float(np.sin(mid_a)), "label": dollar_str} - ) - angle = end_angle - -main_df = pd.DataFrame(rows_main) -main_label_pct_df = pd.DataFrame(label_rows_main_pct) -main_label_usd_df = pd.DataFrame(label_rows_main_usd) - -# Build detail donut polygons (Equities breakdown) — inline, no function -rows_detail = [] -label_rows_detail_pct = [] -label_rows_detail_usd = [] -angle = np.pi / 2 -equities_total = MAIN_WEIGHTS["Equities"] - -for holding in HOLDINGS: - name = holding["name"] - weight = holding["weight"] - color = holding["color"] - sweep = (weight / equities_total) * 2 * np.pi - end_angle = angle - sweep - arcs = np.linspace(end_angle + GAP, angle - GAP, N_PTS) - outer_pts = list(zip(CX_DETAIL + OUTER_R_D * np.cos(arcs), CY + OUTER_R_D * np.sin(arcs), strict=False)) - inner_pts = list(zip(CX_DETAIL + INNER_R_D * np.cos(arcs[::-1]), CY + INNER_R_D * np.sin(arcs[::-1]), strict=False)) - all_pts = outer_pts + inner_pts + [outer_pts[0]] - for order, (x, y) in enumerate(all_pts): - rows_detail.append({"x": x, "y": y, "segment": name, "order": order, "fill": color}) - mid_a = (angle + end_angle) / 2 - pct_r = (INNER_R_D + OUTER_R_D) / 2 + 5 - usd_r = (INNER_R_D + OUTER_R_D) / 2 - 5 - dollar_val = weight / 100 * TOTAL_PORTFOLIO - dollar_str = f"${dollar_val / 1000:.0f}K" - label_rows_detail_pct.append( - {"x": CX_DETAIL + pct_r * float(np.cos(mid_a)), "y": CY + pct_r * float(np.sin(mid_a)), "label": f"{weight}%"} - ) - label_rows_detail_usd.append( - {"x": CX_DETAIL + usd_r * float(np.cos(mid_a)), "y": CY + usd_r * float(np.sin(mid_a)), "label": dollar_str} - ) - angle = end_angle - -detail_df = pd.DataFrame(rows_detail) -detail_label_pct_df = pd.DataFrame(label_rows_detail_pct) -detail_label_usd_df = pd.DataFrame(label_rows_detail_usd) - -# Connecting arrow — drill-down indicator from main to detail donut -arrow_df = pd.DataFrame([{"x": CX_MAIN + OUTER_R + 7, "y": 16, "xend": CX_DETAIL - OUTER_R_D - 7, "yend": 16}]) - -# Section titles — tightened y to reduce corner whitespace -TITLE_Y = 100 -titles_df = pd.DataFrame( - [ - {"x": CX_MAIN, "y": TITLE_Y, "label": "Portfolio Allocation"}, - {"x": CX_DETAIL, "y": TITLE_Y, "label": "Equities Breakdown"}, - ] -) - -# Center hole labels — detail donut shows dollar value of equities allocation -center_df = pd.DataFrame( - [ - {"x": CX_MAIN, "y": 7, "label": "Total"}, - {"x": CX_MAIN, "y": -10, "label": "$1.2M"}, - {"x": CX_DETAIL, "y": 5, "label": "$540K"}, - {"x": CX_DETAIL, "y": -10, "label": "Equities"}, - ] -) - -# Legend at bottom — color boxes + text labels -leg_items = list(MAIN_WEIGHTS.keys()) -LEG_X_START = -152 -LEG_Y = -106 -LEG_SPACING = 80 -BOX_SIZE = 10 - -legend_text_df = pd.DataFrame( - [{"x": LEG_X_START + i * LEG_SPACING + BOX_SIZE + 3, "y": LEG_Y, "label": name} for i, name in enumerate(leg_items)] -) - -box_rows = [] -for i, name in enumerate(leg_items): - x0 = LEG_X_START + i * LEG_SPACING - y0 = LEG_Y - pts = [ - (x0, y0 - BOX_SIZE / 2), - (x0 + BOX_SIZE, y0 - BOX_SIZE / 2), - (x0 + BOX_SIZE, y0 + BOX_SIZE / 2), - (x0, y0 + BOX_SIZE / 2), - (x0, y0 - BOX_SIZE / 2), - ] - for order, (x, y) in enumerate(pts): - box_rows.append({"x": x, "y": y, "segment": f"box_{name}", "order": order, "fill": MAIN_COLORS[name]}) -legend_box_df = pd.DataFrame(box_rows) - -# Title — scale fontsize if longer than 67-char baseline -title_str = "pie-portfolio-interactive · python · plotnine · anyplot.ai" -n_chars = len(title_str) -title_fs = max(8, round(12 * (67 / n_chars if n_chars > 67 else 1.0))) - -# Plot -plot = ( - ggplot() - + geom_polygon(aes(x="x", y="y", group="segment", fill="fill"), data=main_df, color=PAGE_BG, size=0.8, alpha=0.97) - + geom_polygon(aes(x="x", y="y", group="segment", fill="fill"), data=detail_df, color=PAGE_BG, size=0.8, alpha=0.97) - + geom_text(aes(x="x", y="y", label="label"), data=main_label_pct_df, size=3.5, fontweight="bold", color="#FFFFFF") - + geom_text(aes(x="x", y="y", label="label"), data=main_label_usd_df, size=3.0, color="#FFFFFF") - + geom_text( - aes(x="x", y="y", label="label"), data=detail_label_pct_df, size=3.2, fontweight="bold", color="#FFFFFF" - ) - + geom_text(aes(x="x", y="y", label="label"), data=detail_label_usd_df, size=2.8, color="#FFFFFF") - + geom_segment( - aes(x="x", xend="xend", y="y", yend="yend"), - data=arrow_df, - color=INK_SOFT, - size=1.0, - linetype="dashed", - arrow=arrow(length=0.10, type="closed"), - ) - + geom_text(aes(x="x", y="y", label="label"), data=titles_df, size=5.2, fontweight="bold", color=INK) - + geom_text(aes(x="x", y="y", label="label"), data=center_df, size=3.8, color=INK_SOFT) - + geom_polygon(aes(x="x", y="y", group="segment", fill="fill"), data=legend_box_df, color=INK_SOFT, size=0.3) - + geom_text(aes(x="x", y="y", label="label"), data=legend_text_df, size=2.8, ha="left", color=INK_SOFT) - + scale_fill_identity() - + coord_fixed(ratio=1) - + scale_x_continuous(limits=(-195, 185)) - + scale_y_continuous(limits=(-118, 113)) - + labs(title=title_str) - + theme( - figure_size=(8, 4.5), - plot_title=element_text(size=title_fs, ha="center", color=INK, weight="bold"), - axis_title=element_blank(), - axis_text=element_blank(), - axis_ticks=element_blank(), - axis_line=element_blank(), - panel_grid_major=element_blank(), - panel_grid_minor=element_blank(), - panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - legend_position="none", - ) -) - -# Save -plot.save(f"plot-{THEME}.png", dpi=400, width=8, height=4.5, units="in") diff --git a/plots/pie-portfolio-interactive/implementations/python/pygal.py b/plots/pie-portfolio-interactive/implementations/python/pygal.py deleted file mode 100644 index fdffdd807d..0000000000 --- a/plots/pie-portfolio-interactive/implementations/python/pygal.py +++ /dev/null @@ -1,129 +0,0 @@ -""" anyplot.ai -pie-portfolio-interactive: Interactive Portfolio Allocation Chart -Library: pygal 3.1.0 | Python 3.13.13 -Quality: 85/100 | Updated: 2026-05-27 -""" - -import os -import re -import sys - - -# "pygal.py" shadows the installed pygal package — remove this file's directory -# from sys.path before importing so Python finds the installed package. -_here = os.path.dirname(os.path.abspath(__file__)) -sys.path = [p for p in sys.path if os.path.realpath(p or ".") != os.path.realpath(_here)] -del _here - -import cairosvg -import pygal -from pygal.style import Style - - -THEME = os.getenv("ANYPLOT_THEME", "light") -PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" -INK = "#1A1A17" if THEME == "light" else "#F0EFE8" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -IMPRINT_PALETTE = ("#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477", "#99B314") - -# Data — balanced multi-asset portfolio with individual holdings -holdings = { - "Equities": {"US Large Cap": 25.0, "US Mid Cap": 10.0, "International Developed": 12.0, "Emerging Markets": 8.0}, - "Fixed Income": {"US Treasury": 15.0, "Corporate Bonds": 8.0, "Municipal Bonds": 5.0}, - "Alternatives": {"Real Estate": 7.0, "Commodities": 5.0, "Private Equity": 5.0}, -} - -category_totals = {cat: sum(assets.values()) for cat, assets in holdings.items()} -n_holdings = sum(len(v) for v in holdings.values()) -dominant_cat = max(category_totals, key=category_totals.get) -dominant_pct = category_totals[dominant_cat] - -title = "pie-portfolio-interactive · python · pygal · anyplot.ai" -n = len(title) -ratio = 67 / n if n > 67 else 1.0 -title_font_size = max(44, round(66 * ratio)) - -custom_style = Style( - background=PAGE_BG, - plot_background=PAGE_BG, - foreground=INK, - foreground_strong=INK, - foreground_subtle=INK_MUTED, - colors=IMPRINT_PALETTE, - title_font_size=title_font_size, - label_font_size=56, - major_label_font_size=44, - legend_font_size=44, - value_font_size=38, - tooltip_font_size=36, -) - -# Wide donut (inner_radius=0.60) for modern look and center annotation space -chart = pygal.Pie( - width=2400, - height=2400, - style=custom_style, - inner_radius=0.60, - show_legend=True, - legend_at_bottom=True, - legend_at_bottom_columns=3, - legend_box_size=40, - title=title, - print_values=True, - value_formatter=lambda x: f"{x:.1f}%", - margin=80, - margin_bottom=200, - spacing=20, -) - -# Legend labels include category totals for data storytelling -for category, assets in holdings.items(): - total = category_totals[category] - chart.add( - f"{category} · {total:.1f}%", - [{"value": weight, "label": f"{name}: {weight:.1f}%"} for name, weight in assets.items()], - ) - -svg_str = chart.render().decode("utf-8") - -# Locate pie center: scan donut arc paths (M x y A ...) to find starting points, -# then average them — all arcs share the same geometric center. -arc_starts = re.findall(r'\bd="M\s+([\d.]+)[, ]+([\d.]+)\s+A\b', svg_str) -if arc_starts: - pts = [(float(x), float(y)) for x, y in arc_starts[:20]] - # The arithmetic mean of arc start points approximates the center - avg_x = sum(p[0] for p in pts) / len(pts) - avg_y = sum(p[1] for p in pts) / len(pts) - # For a donut, arc starts lie on the inner circle; the center is their centroid - cx, cy = avg_x, avg_y -else: - # Fallback: estimated center for 2400×2400 with given margins + title - cx = 1200.0 - cy = (80 + title_font_size * 2 + (2400 - 80 - 200 - 80)) / 2 - -# Inject center annotation highlighting the dominant asset class -center_svg = ( - f'' - f'' - f"{dominant_pct:.0f}%" - f"" - f'' - f"{dominant_cat}" - f"" - f'' - f"{n_holdings} holdings" - f"" - f"" -) -annotated_svg = (svg_str.replace("", f"{center_svg}\n")).encode("utf-8") - -# PNG: render annotated SVG via cairosvg (same pipeline as render_to_png) -cairosvg.svg2png(bytestring=annotated_svg, write_to=f"plot-{THEME}.png") - -# HTML: embed annotated SVG (center annotation visible in both static and interactive views) -with open(f"plot-{THEME}.html", "wb") as f: - f.write(annotated_svg) diff --git a/plots/pie-portfolio-interactive/implementations/python/seaborn.py b/plots/pie-portfolio-interactive/implementations/python/seaborn.py deleted file mode 100644 index 876379fd1b..0000000000 --- a/plots/pie-portfolio-interactive/implementations/python/seaborn.py +++ /dev/null @@ -1,212 +0,0 @@ -""" anyplot.ai -pie-portfolio-interactive: Interactive Portfolio Allocation Chart -Library: seaborn 0.13.2 | Python 3.13.13 -Quality: 84/100 | Updated: 2026-05-27 -""" - -import os - -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd -import seaborn as sns - - -# Theme -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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -# Imprint palette — semantic: green=equities (growth), blue=fixed income (stability), ochre=alternatives -CATEGORY_BASES = {"Equities": "#009E73", "Fixed Income": "#4467A3", "Alternatives": "#BD8233"} - -sns.set_theme( - style="ticks", - rc={ - "figure.facecolor": PAGE_BG, - "axes.facecolor": PAGE_BG, - "axes.edgecolor": INK_SOFT, - "axes.labelcolor": INK, - "text.color": INK, - "xtick.color": INK_SOFT, - "ytick.color": INK_SOFT, - "grid.color": INK, - "grid.alpha": 0.15, - "legend.facecolor": ELEVATED_BG, - "legend.edgecolor": INK_SOFT, - }, -) - -# Data — diversified investment portfolio -portfolio_data = pd.DataFrame( - { - "asset": [ - "US Large Cap Equity", - "International Equity", - "Emerging Markets", - "US Treasury Bonds", - "Corporate Bonds", - "Municipal Bonds", - "REITs", - "Gold", - "Private Equity", - ], - "weight": [28.0, 15.0, 7.0, 18.0, 12.0, 5.0, 6.0, 4.0, 5.0], - "category": [ - "Equities", - "Equities", - "Equities", - "Fixed Income", - "Fixed Income", - "Fixed Income", - "Alternatives", - "Alternatives", - "Alternatives", - ], - "value": [280000, 150000, 70000, 180000, 120000, 50000, 60000, 40000, 50000], - } -) - -category_data = portfolio_data.groupby("category").agg({"weight": "sum", "value": "sum"}).reset_index() - -# Build per-category gradient palettes using seaborn palette tools and anyplot base colors -# Fixed n_colors=3 for endpoints → wider light↔dark contrast range on all wedge sizes -cat_palettes = {} -category_rgb = {} -for cat, base_hex in CATEGORY_BASES.items(): - base_rgb = sns.color_palette([base_hex])[0] - category_rgb[cat] = base_rgb - n = len(portfolio_data[portfolio_data["category"] == cat]) - light = sns.light_palette(base_rgb, n_colors=3)[1] - dark = sns.dark_palette(base_rgb, n_colors=3, reverse=True)[1] - cat_palettes[cat] = sns.blend_palette([dark, base_rgb, light], n_colors=n) - -cat_counters = dict.fromkeys(CATEGORY_BASES, 0) -asset_colors = [] -for _, row in portfolio_data.iterrows(): - cat = row["category"] - asset_colors.append(cat_palettes[cat][cat_counters[cat]]) - cat_counters[cat] += 1 - -# Canvas — square for symmetric donut chart -fig, ax = plt.subplots(figsize=(6, 6), dpi=400, facecolor=PAGE_BG) -ax.set_facecolor(PAGE_BG) - -# Outer ring — individual holdings breakdown -outer_wedges, _, outer_autotexts = ax.pie( - portfolio_data["weight"], - labels=None, - colors=asset_colors, - autopct=lambda pct: f"{pct:.1f}%" if pct > 5 else "", - startangle=90, - radius=1.0, - pctdistance=0.82, - wedgeprops={"width": 0.35, "edgecolor": PAGE_BG, "linewidth": 1.5}, -) -for autotext in outer_autotexts: - autotext.set_fontsize(8) - autotext.set_fontweight("bold") - autotext.set_color(INK) - -# Inner ring — asset class aggregates -inner_colors = [category_rgb[cat] for cat in category_data["category"]] -inner_wedges, _, inner_autotexts = ax.pie( - category_data["weight"], - labels=None, - colors=inner_colors, - autopct=lambda pct: f"{pct:.0f}%", - startangle=90, - radius=0.65, - pctdistance=0.5, - wedgeprops={"width": 0.35, "edgecolor": PAGE_BG, "linewidth": 1.5}, -) -for autotext in inner_autotexts: - autotext.set_fontsize(9) - autotext.set_fontweight("bold") - autotext.set_color("#FFFFFF") - -# Center label — total portfolio value -total_value = portfolio_data["value"].sum() -ax.text( - 0, - 0, - f"Portfolio\n${total_value / 1_000_000:.1f}M", - ha="center", - va="center", - fontsize=10, - fontweight="bold", - color=INK, -) - -# Data labels for the four largest holdings -cumulative = 0.0 -angle_mid = {} -for _, row in portfolio_data.iterrows(): - angle_mid[row["asset"]] = 90 - cumulative - row["weight"] / 2 - cumulative += row["weight"] - -for _, row in portfolio_data.nlargest(4, "weight").iterrows(): - theta = np.deg2rad(angle_mid[row["asset"]]) - r_text = 1.26 - x_t, y_t = r_text * np.cos(theta), r_text * np.sin(theta) - ha = "left" if x_t > 0.15 else ("right" if x_t < -0.15 else "center") - dx = 0.04 if x_t > 0.15 else (-0.04 if x_t < -0.15 else 0) - - ax.annotate( - f"{row['asset']}\n${row['value']:,.0f} ({row['weight']:.0f}%)", - xy=(0.97 * np.cos(theta), 0.97 * np.sin(theta)), - xytext=(x_t + dx, y_t), - fontsize=7, - ha=ha, - va="center", - color=INK, - bbox={ - "boxstyle": "round,pad=0.3", - "facecolor": ELEVATED_BG, - "edgecolor": CATEGORY_BASES[row["category"]], - "linewidth": 1.5, - "alpha": 0.92, - }, - arrowprops={"arrowstyle": "-", "color": INK_MUTED, "connectionstyle": "arc3,rad=0.1", "linewidth": 0.8}, - ) - -# Legend — asset class breakdown summary -legend_labels = [ - f"{r['category']}: {r['weight']:.0f}% (${r['value'] / 1000:.0f}K, " - f"{len(portfolio_data[portfolio_data['category'] == r['category']])} holdings)" - for _, r in category_data.iterrows() -] -legend = ax.legend( - inner_wedges, - legend_labels, - title="Asset Classes", - loc="lower center", - bbox_to_anchor=(0.5, -0.05), - fontsize=7, - title_fontsize=8, - frameon=True, - fancybox=True, - ncol=3, - columnspacing=0.8, -) -legend.get_frame().set_facecolor(ELEVATED_BG) -legend.get_frame().set_edgecolor(INK_SOFT) -legend.get_title().set_color(INK) -for text in legend.get_texts(): - text.set_color(INK_SOFT) - -# Title and layout -title = "pie-portfolio-interactive · python · seaborn · anyplot.ai" -ax.set_title(title, fontsize=12, fontweight="medium", color=INK, pad=14) -ax.set_aspect("equal") -ax.set_xlim(-1.65, 1.65) -ax.set_ylim(-1.35, 1.35) - -fig.subplots_adjust(bottom=0.08) - -# Save — no bbox_inches="tight" to preserve exact 2400×2400 canvas -plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG) -plt.close() diff --git a/plots/pie-portfolio-interactive/implementations/r/ggplot2.R b/plots/pie-portfolio-interactive/implementations/r/ggplot2.R deleted file mode 100644 index be9f7c42ad..0000000000 --- a/plots/pie-portfolio-interactive/implementations/r/ggplot2.R +++ /dev/null @@ -1,158 +0,0 @@ -#' anyplot.ai -#' pie-portfolio-interactive: Interactive Portfolio Allocation Chart -#' Library: ggplot2 3.5.1 | R 4.4.1 -#' Quality: 86/100 | Created: 2026-05-27 - -library(ggplot2) -library(dplyr) -library(ragg) - -# Theme tokens -THEME <- Sys.getenv("ANYPLOT_THEME", "light") -PAGE_BG <- if (THEME == "light") "#FAF8F1" else "#1A1A17" -ELEVATED_BG <- if (THEME == "light") "#FFFDF6" else "#242420" -INK <- if (THEME == "light") "#1A1A17" else "#F0EFE8" -INK_SOFT <- if (THEME == "light") "#4A4A44" else "#B8B7B0" -INK_MUTED <- if (THEME == "light") "#6B6A63" else "#A8A79F" - -IMPRINT_PALETTE <- c( - "#009E73", # 1 — Equities (brand green) - "#C475FD", # 2 — Fixed Income (lavender) - "#4467A3", # 3 — Alternatives (blue) - "#BD8233" # 4 — Cash (ochre) -) - -# Portfolio holdings ordered by asset class -cat_levels <- c("Equities", "Fixed Income", "Alternatives", "Cash") -cat_colors <- setNames(IMPRINT_PALETTE, cat_levels) - -holdings <- data.frame( - holding = c( - "US Large Cap", "Intl Developed", "Emerging Mkts", "US Small Cap", - "US Treasuries", "Inv Grade Corp", "High Yield", - "Real Estate", "Commodities", - "Money Market" - ), - category = factor(c( - rep("Equities", 4), - rep("Fixed Income", 3), - rep("Alternatives", 2), - "Cash" - ), levels = cat_levels), - weight = c(20, 18, 12, 5, 12, 10, 8, 6, 4, 5), - stringsAsFactors = FALSE -) - -# Pre-compute angular midpoints and absolute dollar values (assumes $1M portfolio) -holdings <- holdings |> - arrange(category) |> - mutate( - cum_end = cumsum(weight), - cum_start = lag(cum_end, default = 0), - midpoint = (cum_start + cum_end) / 2, - dollar = paste0("$", weight * 10, "k") - ) - -# Asset class summary for the inner ring -categories <- holdings |> - group_by(category) |> - summarise(weight = sum(weight), .groups = "drop") |> - mutate( - cum_end = cumsum(weight), - cum_start = lag(cum_end, default = 0), - midpoint = (cum_start + cum_end) / 2 - ) - -# Title sizing (scale down only if longer than 67-char baseline) -title_str <- "pie-portfolio-interactive · r · ggplot2 · anyplot.ai" -n_chars <- nchar(title_str) -ratio <- if (n_chars > 67) 67 / n_chars else 1.0 -title_size <- max(8, round(12 * ratio)) - -# Double-donut: inner ring = asset classes, outer ring = individual holdings -p <- ggplot() + - # Outer ring: individual holdings - geom_col( - data = holdings, - aes(x = 3.2, y = weight, fill = category), - width = 1.0, - color = "white", - linewidth = 0.5 - ) + - # Inner ring: asset class summary - geom_col( - data = categories, - aes(x = 2.0, y = weight, fill = category), - width = 0.9, - color = "white", - linewidth = 1.0 - ) + - # Inner ring labels (skip Cash at 5% — too small) - geom_text( - data = filter(categories, weight >= 8), - aes(x = 2.0, y = midpoint, - label = paste0(gsub(" ", "\n", as.character(category)), "\n", weight, "%")), - color = "white", - size = 3.5, - fontface = "bold", - lineheight = 0.75 - ) + - # Outer ring labels: placed outside the ring at x=4.0 using INK so text - # always lands on PAGE_BG and remains readable in both light and dark themes. - # Shows holding name, percentage, and absolute value (assumes $1M portfolio). - # Only segments >= 10% are labeled to avoid crowding. - geom_text( - data = filter(holdings, weight >= 10), - aes(x = 4.0, y = midpoint, - label = paste0(holding, "\n", weight, "% · ", dollar)), - color = INK, - size = 2.6, - fontface = "bold", - lineheight = 0.85 - ) + - coord_polar(theta = "y", start = 0, clip = "off") + - xlim(c(0.7, 5.0)) + - scale_fill_manual( - values = cat_colors, - name = "Asset Class" - ) + - labs( - title = title_str, - subtitle = "Model Portfolio ($1M) · 10 Holdings · Inner ring: asset class | Outer ring: individual holding" - ) + - theme_void(base_size = 8) + - theme( - plot.background = element_rect(fill = PAGE_BG, color = PAGE_BG), - panel.background = element_rect(fill = PAGE_BG, color = NA), - plot.title = element_text( - color = INK, - size = title_size, - hjust = 0.5, - face = "bold", - margin = margin(t = 16, b = 4) - ), - plot.subtitle = element_text( - color = INK_MUTED, - size = 7, - hjust = 0.5, - margin = margin(b = 8) - ), - legend.text = element_text(color = INK_SOFT, size = 8), - legend.title = element_text(color = INK, size = 10, face = "bold"), - legend.background = element_rect(fill = ELEVATED_BG, color = INK_SOFT, linewidth = 0.3), - legend.key.size = unit(0.45, "cm"), - legend.position = "bottom", - legend.direction = "horizontal", - plot.margin = margin(10, 40, 20, 40) - ) - -# Save — square canvas: 2400 x 2400 px (6 in x 400 dpi) -ggsave( - filename = sprintf("plot-%s.png", THEME), - plot = p, - device = ragg::agg_png, - width = 6, - height = 6, - units = "in", - dpi = 400 -) diff --git a/plots/pie-portfolio-interactive/metadata/julia/makie.yaml b/plots/pie-portfolio-interactive/metadata/julia/makie.yaml deleted file mode 100644 index 2cebfc5f2f..0000000000 --- a/plots/pie-portfolio-interactive/metadata/julia/makie.yaml +++ /dev/null @@ -1,255 +0,0 @@ -library: makie -language: julia -specification_id: pie-portfolio-interactive -created: '2026-05-27T17:37:57Z' -updated: '2026-05-27T17:52:30Z' -generated_by: claude-sonnet -workflow_run: 26527463804 -issue: 3754 -language_version: 1.11.9 -library_version: 0.22.10 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/julia/makie/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/julia/makie/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 83 -review: - strengths: - - Correct Imprint palette in canonical order with brand green (#009E73) as first - series (Equities) - - Theme-adaptive chrome fully threaded through titlecolor, labelcolor, backgroundcolor - — no dark-on-dark failures in either render - - Center summary labels ('Portfolio' / '$100M AUM') add real informational value - within the donut hole - - PAGE_BG stroke between slices creates clean visual separation without harsh borders - - Dollar + percentage values shown per slice, matching spec requirement for 'both - percentage and absolute values' - - Correct square 2400x2400 canvas for a symmetric donut chart - - Clean flat script structure with no unnecessary abstractions or fake interactivity - weaknesses: - - Donut appears moderately sized with noticeable horizontal whitespace on both sides - of the canvas — consider slightly increasing r_outer or adjusting xlims!/ylims! - bounds to fill the square canvas more effectively - - Design storytelling is limited to a minor font-size bump on the dominant slice; - a more prominent visual callout or annotation on the largest allocation (Equities - 40%) would strengthen the narrative - - Library Mastery is adequate but not distinctive — no Makie-specific features (Observables, - Themes, @recipe) leveraged beyond polygon primitives - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct, not pure white - Chrome: Title "pie-portfolio-interactive · julia · makie · anyplot.ai" in dark ink — clearly readable. No axis labels (appropriate for donut). Legend text in dark ink — readable. Outer radial labels in dark ink — readable. - Data: Four donut slices — brand green (#009E73) Equities 40%, lavender (#C475FD) Fixed Income 30%, blue (#4467A3) Alternatives 20%, ochre (#BD8233) Cash 10%. First series is #009E73 ✓. Center labels "Portfolio" and "$100M AUM" in dark ink. - Legibility verdict: PASS — all text clearly readable against warm off-white background - - Dark render (plot-dark.png): - Background: Near-black #1A1A17 — correct, not pure black - Chrome: Title and all labels in light ink (F0EFE8/B8B7B0 range) — clearly readable against dark background. No dark-on-dark failures observed. Legend text light-colored and legible. - Data: All four data colors (green, lavender, blue, ochre) visually identical to light render — only chrome flipped. Slice colors remain saturated and distinguishable on dark surface. - Legibility verdict: PASS — all text clearly readable against near-black background, no dark-on-dark failures - criteria_checklist: - visual_quality: - score: 26 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 6 - max: 8 - passed: true - comment: All text readable in both themes; font sizes explicitly set. Outer - labels for small Cash slice are slightly close to title area but legible. - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: No text-on-text or text-on-data overlaps. Cash label near top sits - close but not overlapping title. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: All four donut slices clearly visible with well-saturated distinct - colors. PAGE_BG stroke creates clean separation. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Imprint palette used — colorblind-safe by design. No red-green as - sole signal. - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: Correct square 2400x2400 canvas. Donut moderately sized with noticeable - horizontal whitespace on both sides. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title in correct format. No axis labels needed for donut chart — - appropriate omission. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series = #009E73 ✓. Canonical palette order ✓. Backgrounds - #FAF8F1/#1A1A17 correct ✓. Data colors identical between renders ✓.' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Above baseline: correct palette, center summary labels, dominant - slice slightly emphasized. Professional without deep visual complexity.' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: hidedecorations! + hidespines! applied, frameless legend, PAGE_BG - stroke between slices. Minimal chrome well-executed. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Percentage and dollar values per slice, center shows total AUM, dominant - slice label slightly larger. No annotation or callout guiding to key insight. - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Donut/pie chart is correct for portfolio allocation. - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Donut ✓, asset-class color coding ✓, legend ✓, percentage + dollar - values ✓. Interactive features correctly omitted (CairoMakie is static-only - per library rules). - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Four asset classes with correct weights summing to 100%, dollar values - derived from $100M total. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title matches required format ✓. Legend includes category name, percentage, - and dollar value ✓. - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: All asset classes with percentage and dollar values, center summary, - horizontal legend. No drill-down (impossible with CairoMakie). - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: $100M portfolio with Equities/Fixed Income/Alternatives/Cash at 40/30/20/10% - is realistic, neutral, real-world allocation. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Weights sum to 100%, dollar values proportional, $100M AUM is plausible - institutional portfolio size. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Flat top-level script, no functions or classes, clean sequential - flow. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Random.seed!(42) present. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: CairoMakie, Colors, Random — all used and necessary. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: No fake interactivity. Clean arc geometry construction. Loop over - categories is idiomatic. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-$(THEME).png with px_per_unit=2 ✓. - library_mastery: - score: 6 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Uses poly!, text!, PolyElement, hidedecorations!, hidespines!, DataAspect() - — all idiomatic Makie patterns. - - id: LM-02 - name: Distinctive Features - score: 2 - max: 5 - passed: false - comment: Hand-constructed arc geometry via poly! is correct but doesn't showcase - Makie-distinctive features (Observables, Theme system, @recipe) beyond generic - polygon rendering. - verdict: APPROVED -impl_tags: - dependencies: - - colors - techniques: - - annotations - - custom-legend - - patches - patterns: - - data-generation - - iteration-over-groups - dataprep: [] - styling: - - minimal-chrome - - edge-highlighting diff --git a/plots/pie-portfolio-interactive/metadata/python/altair.yaml b/plots/pie-portfolio-interactive/metadata/python/altair.yaml deleted file mode 100644 index 46e5192316..0000000000 --- a/plots/pie-portfolio-interactive/metadata/python/altair.yaml +++ /dev/null @@ -1,259 +0,0 @@ -library: altair -language: python -specification_id: pie-portfolio-interactive -created: '2026-01-20T23:09:35Z' -updated: '2026-05-27T17:49:11Z' -generated_by: claude-sonnet -workflow_run: 26526820203 -issue: 3754 -language_version: 3.13.13 -library_version: 6.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/altair/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/altair/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/altair/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/altair/plot-dark.html -quality_score: 89 -review: - strengths: - - Dual-ring concentric donut with full interactive drill-down using Altair selection - grammar — outer ring click filters inner holdings ring via transform_filter - - 'Perfect spec compliance across all required features: hover tooltips, click-to-drill-down - by asset class, return-to-overview toggle, legend, color-coded by category' - - Imprint palette in canonical order (green=Equities, purple=Fixed Income, blue=Alternatives); - both light and dark themes render correctly with proper chrome adaptation - - Clean idiomatic Altair code using alt.selection_point + transform_filter linked-selection - pattern and alt.condition for conditional opacity — library strengths well demonstrated - - Realistic portfolio data with real asset names (Apple, Microsoft, US Treasury) - summing to exactly 100%, sensible allocation breakdown across three distinct asset - classes - weaknesses: - - Design Excellence is the limiting factor — the dual-ring donut is well-crafted - but reads as a sophisticated configured implementation rather than a truly publication-ready - design; no data-level annotations or callouts to guide the viewer directly to - insights - - Inner ring arc segments for 5% holdings (Municipal Bonds, Commodities) are thin - and may be hard to distinguish on mobile-scaled widths — consider slightly increasing - innerRadius or adding a minimum arc threshold - - 'VQ-05: The donut area occupies a modest fraction of the 2400×2400 canvas with - notable dead space in all four corners; expanding chart.properties(width=, height=) - by ~10% would improve canvas utilization' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white matching #FAF8F1 — correct - Chrome: Title "pie-portfolio-interactive · python · altair · anyplot.ai" in dark (#1A1A17) text fills ~75% of width — fully readable. Subtitle "Click outer ring to drill into category holdings · click again to reset" in muted secondary color — legible. Legend box has elevated background with Asset Class title and three labeled swatches. - Data: Outer ring (category totals) and inner ring (individual holdings) both use the Imprint palette — green (#009E73) for Equities (largest arc ~45%), lavender (#C475FD) for Fixed Income (~35%), blue (#4467A3) for Alternatives (~20%). Center shows "100%" bold and "10 Holdings" in softer text. Ring-gap strokes are PAGE_BG (white/cream), clearly separating segments. - Legibility verdict: PASS — all text clearly readable against the warm off-white background. - - Dark render (plot-dark.png): - Background: Near-black matching #1A1A17 — correct - Chrome: Title and subtitle render in light text (INK → #F0EFE8) — clearly visible against the dark surface. Legend box has elevated dark background (#242420) with light text labels. Ring-gap strokes switch to INK_SOFT (#B8B7B0) in dark mode — correctly adapted so gaps remain visible against dark arc segments. - Data: Green, purple, and blue arcs are identical in hue and arrangement to the light render — only chrome flips. Center "100%" text is light and bold, "10 Holdings" in softer secondary. No dark-on-dark failures observed. - Legibility verdict: PASS — all text clearly readable against the near-black background; the RING_STROKE INK_SOFT adaptation is correct and visible. - criteria_checklist: - visual_quality: - score: 27 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: 'All font sizes explicitly set (title 16, subtitle 12, center 28+14, - legend 14/12). Well-proportioned and readable in both themes. Minor: subtitle - is compact at the canvas scale.' - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping elements — donut chart has no axis ticks; center text - sits cleanly in the hole; legend floats clear of the arcs. - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: Both rings clearly visible. 5% allocation arcs (Municipal Bonds, - Commodities) are thin in the inner ring — borderline at mobile widths. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Three Imprint palette colors with good perceptual separation; CVD-safe. - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: Canvas gate passed (2400x2400). Donut occupies a reasonable area - but dead corner space is notable for a square canvas. Legend well placed. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: No axes (donut chart). Title in canonical format. Legend title 'Asset - Class' is descriptive. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Equities=#009E73 (first, correct), Fixed Income=#C475FD (slot 2), - Alternatives=#4467A3 (slot 3). Canonical order. Backgrounds #FAF8F1/#1A1A17 - correct. Chrome adapts correctly in both themes.' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Thoughtful concentric dual-ring design with typographic center hierarchy - and is_dominant stroke-width emphasis. Above configured-default (4) but - not FiveThirtyEight publication level. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: No grid, no spines. Legend styled with elevated background, rounded - corners, symbolic stroke. Theme-adaptive ring gap strokes. Genuine refinement - beyond defaults. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Dual-ring structure creates clear visual hierarchy — outer=asset - class, inner=holdings. Equities dominance (45%) immediately visible. Subtitle - communicates interaction model. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Dual-ring interactive donut chart — exactly the specified visualization. - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: 'All spec features present: pie/donut, hover tooltips with weights, - click drill-down by asset class, legend, color-coding, return-to-overview - via toggle.' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Theta encodes weight correctly for both rings. Tooltips expose asset, - weight %, category, num_holdings. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title is exact canonical format. Legend 'Asset Class' with correct - category labels. - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'All chart features covered: three asset classes, 10 holdings, two-level - drill-down, dominant and minor allocations.' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Real security names (Apple, Microsoft, US Treasury 10Y, Gold ETF). - Finance/investment context is neutral and business-appropriate. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Weights sum to exactly 100%. Allocation split (45% Equities, 35% - Fixed Income, 20% Alternatives) is realistic for a balanced growth portfolio. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'No functions or classes. Clean linear flow: imports → tokens → data - → charts → layer → save.' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Fully deterministic static data — no random generation needed. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: 'All imports used: os, altair, pandas, PIL.Image.' - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean, Pythonic. Appropriate complexity for a dual-ring interactive - donut. PIL padding logic follows canonical altair.md pattern. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html. Correct API usage. - library_mastery: - score: 9 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: Expertly uses alt.layer(), alt.selection_point(), transform_filter(), - alt.condition() for conditional encoding — all hallmark Altair patterns. - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: The linked selection between outer and inner ring via transform_filter(category_sel) - is distinctly Altair — this cross-mark interaction grammar cannot be replicated - easily in other libraries. - verdict: APPROVED -impl_tags: - dependencies: - - pillow - techniques: - - layer-composition - - hover-tooltips - - html-export - patterns: - - groupby-aggregation - dataprep: [] - styling: - - alpha-blending diff --git a/plots/pie-portfolio-interactive/metadata/python/bokeh.yaml b/plots/pie-portfolio-interactive/metadata/python/bokeh.yaml deleted file mode 100644 index 0758de4b0c..0000000000 --- a/plots/pie-portfolio-interactive/metadata/python/bokeh.yaml +++ /dev/null @@ -1,285 +0,0 @@ -library: bokeh -language: python -specification_id: pie-portfolio-interactive -created: '2026-01-20T23:08:09Z' -updated: '2026-05-27T17:54:12Z' -generated_by: claude-sonnet -workflow_run: 26526715471 -issue: 3754 -language_version: 3.13.13 -library_version: 3.9.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/bokeh/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/bokeh/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/bokeh/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/bokeh/plot-dark.html -quality_score: 84 -review: - strengths: - - 'Full spec compliance — all required features implemented: hover tooltips, click-to-drill-down - into category holdings, return-to-overview navigation, center labels showing portfolio - value' - - 'Proper Bokeh idioms: ColumnDataSource, HoverTool scoped to specific renderers, - CustomJS callbacks for client-side interactivity, annular_wedge for donut chart' - - Excellent data quality with realistic portfolio (Apple, Microsoft, Google, US - Treasury, Corporate Bonds, Gold ETF, Real Estate Fund) totalling $1,000,000 - - Correct Imprint palette with semantic color mapping (green=equities/growth, blue=fixed - income/stable, ochre=alternatives/commodity, muted neutral=cash) - - Theme-adaptive chrome tokens applied correctly — all text, legend, center labels - use INK/INK_SOFT tokens; both renders are theme-correct and fully legible - - Bokeh-distinctive CustomJS drill-down with renderer visibility toggling — a feature - unique to client-side interactive libraries - weaknesses: - - 'Canvas dimensions drifted from required target. Actual: 2400x2261. Closest valid - target: 2400x2400 (±16 px tolerance). Signed delta: +0 x -139 — height is 139 - px short. Most likely cause: bokeh toolbar adding ~30-50 px above the figure. - Set `toolbar_location=None`. Re-render at exactly 2400x2400; the post-render gate - enforces this.' - - toolbar_location=None is already set, so the 139 px shortfall likely comes from - Selenium save_screenshot() capturing only the visible page content height rather - than the full viewport — fix by ensuring the Bokeh figure fills the full window - or by using driver.execute_script-based full-page screenshot, or by explicitly - setting driver.set_window_size before get() and after get() - - LabelSet label_text for overview uses newline character in f-string (\n) which - may render as literal text in some Bokeh/Chrome versions — consider using two - separate LabelSet layers (one for category name, one for percentage) for robustness - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — PASS - Chrome: Title "pie-portfolio-interactive · python · bokeh · anyplot.ai" in dark ink, clearly readable. No axis labels (hidden, correct for donut). Category labels (e.g. "Equities\n55%", "Fixed Income\n25%") appear as two-line text outside the ring in dark ink — all legible. Center labels "Total Portfolio" (INK_SOFT) and "$1,000,000" (INK, bold) are readable. Legend (top-right) shows all four categories in dark text on #FFFDF6 elevated background. - Data: Donut ring with four segments — #009E73 green (Equities, largest at 55%), #4467A3 blue (Fixed Income 25%), #BD8233 ochre (Alternatives 16%), muted gray (Cash 4%). Segment borders use PAGE_BG line, creating clean visual separation. All segments clearly distinguishable. - Legibility verdict: PASS — no light-on-light issues, all text is clearly readable against the warm off-white background. - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — PASS - Chrome: Title and labels use light INK (#F0EFE8) — readable against dark background. Center labels use INK_SOFT (#B8B7B0) for header and INK for value — both readable. Legend uses INK_SOFT text on #242420 elevated background — legible. - Data: Data colors are identical to the light render — #009E73 green, #4467A3 blue, #BD8233 ochre, muted gray for Cash. Segment borders use dark PAGE_BG for separation. No dark-on-dark failures detected. - Legibility verdict: PASS — all text is readable against the warm near-black background. No dark-on-dark failures. - criteria_checklist: - visual_quality: - score: 23 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: 'All font sizes explicitly set (title ~50pt, labels 34pt, legend - 34pt, center header 26pt). Readable in both themes. Minor deduction: center - header at 26pt is slightly below guideline for 2400x2400.' - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: Labels are well-spaced around the ring. Minor risk of crowding between - 'Cash 4%' and 'Alternatives 16%' labels given how small the Cash wedge is, - but no actual overlap visible. - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: 'Donut segments clearly visible. Cash (4%) is a thin wedge but its - label makes it identifiable. alpha=0.92 adds slight depth. Minor deduction: - Cash segment is very thin.' - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Four palette colors are perceptually distinct. Semantic mapping (green=growth, - blue=stable, ochre=commodity, muted=neutral) aids CVD safety with category - context. - - id: VQ-05 - name: Layout & Canvas - score: 0 - max: 4 - passed: false - comment: 'CANVAS GATE FAILURE: Actual 2400x2261, target 2400x2400 (delta -139px - height). Forced to 0/4 per canvas gate rule.' - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: No axis labels needed (donut chart with hidden axes). Title is correct. - Segment labels provide category context. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series (Equities) uses #009E73. Multi-series uses palette - positions 1,3,4 with semantic exception (skip lavender for finance domain). - Cash uses INK_MUTED (muted anchor) — appropriate for neutral/rest. Backgrounds - #FAF8F1/#1A1A17 correct. Both themes fully correct.' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: 'Above defaults: semantic color mapping (green=growth, blue=stable, - ochre=commodity), clean donut with hover states, legend with elevated background. - Not publication-level but clearly above generic defaults.' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Axes/grid hidden (appropriate for donut). Clean segment separation - via PAGE_BG border. alpha=0.92 on wedges. Legend styled with elevated BG - and border. Above minimal defaults. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Interactive drill-down is genuine storytelling — allows exploration - from category to individual holdings. Center labels update to show category - totals on drill-down. Multi-level hierarchy creates a clear narrative flow. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Donut chart implemented with annular_wedge — correct per spec ('pie - or donut chart as primary visualization'). - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: 'All spec features present: hover tooltips (HoverTool on both layers), - click-to-drill-down into sub-holdings (CustomJS), display of weights and - values, color-coding by asset class, legend, return-to-overview navigation - (Back button).' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Weights correctly map to wedge angles. Values shown in tooltips and - center labels. Asset/category hierarchy correctly implemented. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title: ''pie-portfolio-interactive · python · bokeh · anyplot.ai'' - — correct format. Legend labels match asset class categories.' - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'Shows all chart aspects: category-level overview, individual holdings - detail via drill-down, hover tooltips, navigation. 10 holdings across 4 - asset classes.' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: 'Realistic portfolio: Apple, Microsoft, Google, US Treasury 10Y, - Corporate Bonds, Gold ETF, Real Estate Fund, Commodities, Cash. $1,000,000 - total — plausible wealth management scenario. Neutral business context.' - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Weights sum to 100% (55+25+16+4). Values sum to $1,000,000. Allocation - ratios (55% equities, 25% bonds, 16% alts, 4% cash) realistic for a balanced - growth portfolio. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Flat script: imports → tokens → data → figure → overlays → callbacks - → save. No functions or classes.' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set. Data is largely hardcoded so deterministic - regardless. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: 'All imports used: os/sys (path+env), time (sleep), Path (file path), - numpy (angle math), bokeh.io/models/plotting, selenium.' - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean, appropriate complexity for a multi-layer interactive chart. - JavaScript callback is complex but necessary for the drill-down feature. - The sys.path manipulation has a clear explanatory comment. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.html and plot-{THEME}.png. Uses current Bokeh - 3.9.0 API with Selenium screenshot pattern per library guide. - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: ColumnDataSource used throughout. HoverTool scoped to specific renderers. - CustomJS with source data mutation. LabelSet for external labels. annular_wedge - for donut. Selenium screenshot per library guide. - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: CustomJS drill-down with renderer visibility toggling — client-side - state management unique to Bokeh's JavaScript bridge. Two separate glyph - layers (overview/detail) with dynamic show/hide. HoverTool renderer scoping. - verdict: REJECTED -impl_tags: - dependencies: - - selenium - techniques: - - hover-tooltips - - html-export - - custom-legend - - annotations - patterns: - - data-generation - - columndatasource - - iteration-over-groups - dataprep: - - cumulative-sum - styling: - - alpha-blending - - minimal-chrome - - edge-highlighting diff --git a/plots/pie-portfolio-interactive/metadata/python/letsplot.yaml b/plots/pie-portfolio-interactive/metadata/python/letsplot.yaml deleted file mode 100644 index f31cc024d4..0000000000 --- a/plots/pie-portfolio-interactive/metadata/python/letsplot.yaml +++ /dev/null @@ -1,263 +0,0 @@ -library: letsplot -language: python -specification_id: pie-portfolio-interactive -created: '2026-01-20T23:07:33Z' -updated: '2026-05-27T17:52:20Z' -generated_by: claude-sonnet -workflow_run: 26527248023 -issue: 3754 -language_version: 3.13.13 -library_version: 4.10.1 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/letsplot/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/letsplot/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/letsplot/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/letsplot/plot-dark.html -quality_score: 89 -review: - strengths: - - Semantic color assignment (Equities=green, Fixed Income=blue, Alternatives=purple, - Cash=ochre) is well-reasoned and documented in code comments, aligning with strong - financial color conventions - - ggbunch() composite layout is an excellent use of a letsplot-specific feature - for the drill-down HTML panels - - layer_tooltips() with multi-line formatted content (category name, weight%, holdings - count, holdings preview) provides rich interactive information per slice - - Suppressing labels below 5% (pct_label logic) prevents cramped text on the tiny - Cash slice — thoughtful UX decision - - Caption directly calls out the dominant insight in brand blue (#4467A3), elevating - data storytelling above default - - theme_void() combined with custom chart_theme tokens produces a very clean pie - presentation with no unnecessary chrome - weaknesses: - - Spec requests 'display both percentage and absolute values where applicable' — - only percentages are shown; no portfolio dollar value context (e.g. '$380K of - $1M') in tooltips or labels - - Click-to-drill-down navigation is not implemented; instead all panels are shown - simultaneously via ggbunch — the navigational interactivity described in the spec - is absent (subtitle is honest about this limitation) - - Slice labels (size=7mm) may be slightly small when the chart is scaled to mobile - thumbnails (~400px) - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct light surface - Chrome: Title "pie-portfolio-interactive · python · letsplot · anyplot.ai" in dark ink, clearly readable. Subtitle "Hover for details · Drill-down panels in HTML" in secondary ink. Legend "Asset Class" with all four category labels readable. Caption in #4467A3 blue at bottom. - Data: Four donut segments — Equities 37% (#009E73 brand green), Fixed Income 38% (#4467A3 blue), Alternatives 21% (#C475FD lavender), Cash 4% (#BD8233 ochre, no label). Center label "Portfolio / 100%" on elevated off-white box. Percentage labels 38.0%, 37.0%, 21.0% visible on slices. - Legibility verdict: PASS - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct dark surface - Chrome: Title and subtitle rendered in light cream text, clearly legible against dark surface. Legend text is light-colored, no dark-on-dark failure. Center label uses elevated dark background (#242420) with white text. Caption in #4467A3 blue remains readable. - Data: Colors identical to light render — green, blue, lavender, ochre segments unchanged. Only chrome (background, text, legend box) flips between themes. - Legibility verdict: PASS - criteria_checklist: - visual_quality: - score: 29 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: 'All font sizes explicitly set (title=13, subtitle=10, legend=10-11, - caption=9, slice labels=7mm). Readable in both themes. Minor: slice labels - slightly small at mobile scale.' - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text or element collisions in either render. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Pie slices clearly visible with distinct colors; donut hole and center - label well-proportioned; Cash segment small but correctly unlabeled. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Semantic Imprint palette is CVD-safe; pie stroke provides boundary - definition. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Square 2400x2400 canvas correct for pie; chart fills canvas well; - legend properly positioned. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: No axis labels needed (pie chart); title in correct spec format; - subtitle and insight caption add context. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Equities=#009E73 (brand green first); Fixed Income=#4467A3; Alternatives=#C475FD; - Cash=#BD8233. Semantic exception justified for finance domain. Backgrounds - #FAF8F1/#1A1A17 correct. Data colors identical across themes.' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Thoughtful semantic color assignment with documented reasoning; donut - center summary; insight caption in brand blue; above configured-default - but not FiveThirtyEight-level. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: theme_void() perfectly suits pie; stroke on slices for clean segment - definition; elevated background for legend and center label. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Caption explicitly names key insight (Fixed Income leads at 38%); - center label anchors total; subtitle guides interactive exploration. - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Donut chart as specified; drill-down panels in HTML via ggbunch. - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: 'Hover tooltips, category grouping, color-coding, legend, HTML drill-down - panels all present. Minor omissions: no absolute dollar values, no click-to-navigate - drill-down (ggbunch shows all panels simultaneously).' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Weight mapped correctly; categories properly grouped; weights sum - to 100%. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title: ''pie-portfolio-interactive · python · letsplot · anyplot.ai''. - Legend labeled ''Asset Class'' with all four categories.' - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'Shows all aspects: four asset classes with varying weights (4%-38%), - 11 individual holdings, diversity of asset types, drill-down panels exposing - sub-allocations.' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Institutional multi-asset portfolio with named real-world holdings - (AAPL, MSFT, AMZN, NVDA, US Treasury, AAA Corporate Bonds, Municipal Bonds, - Gold ETF, REIF, Private Equity, Cash). Neutral financial scenario. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Weights sum exactly to 100%; individual allocations realistic for - diversified institutional portfolio. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Imports -> tokens -> data -> plots -> save. Loop for drill-down panels - is appropriate complexity for the spec. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Fully deterministic hardcoded portfolio data; no random generation - needed. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports used; no unused symbols. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean, well-commented with documented reasoning for semantic color - choices; no fake UI elements. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html; current API used. - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: 'Proper grammar-of-graphics usage: geom_pie with aes(slice, fill), - layer_tooltips(), layer_labels(), scale_fill_manual(), theme_void()+theme(). - High-level API throughout.' - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: layer_tooltips() with multi-line formatted tooltips, layer_labels() - for slice annotations, ggbunch() for composite multi-panel layout, dual - PNG+HTML export — all letsplot-distinctive. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - hover-tooltips - - html-export - - annotations - - subplots - patterns: - - groupby-aggregation - - iteration-over-groups - dataprep: [] - styling: - - minimal-chrome - - edge-highlighting diff --git a/plots/pie-portfolio-interactive/metadata/python/matplotlib.yaml b/plots/pie-portfolio-interactive/metadata/python/matplotlib.yaml deleted file mode 100644 index f00d17e714..0000000000 --- a/plots/pie-portfolio-interactive/metadata/python/matplotlib.yaml +++ /dev/null @@ -1,253 +0,0 @@ -library: matplotlib -language: python -specification_id: pie-portfolio-interactive -created: '2026-01-20T23:07:57Z' -updated: '2026-05-27T17:37:56Z' -generated_by: claude-sonnet -workflow_run: 26526393501 -issue: 3754 -language_version: 3.13.13 -library_version: 3.10.9 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/matplotlib/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/matplotlib/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 88 -review: - strengths: - - Dual-ring concentric donut correctly implements the hierarchical portfolio concept - as a static visualization without any fake interactivity - - Imprint palette applied in canonical order with correct first-series brand green; - full theme adaptation with correct dark/light chrome tokens - - Hierarchical legend with both percentage and dollar value context adds real investment - utility - - Deterministic hardcoded data that sums to exactly 100%; realistic balanced fund - composition with plausible asset class breakdown - weaknesses: - - Outer ring pct labels at 7pt may be marginal at mobile scale; consider bumping - outer ring autopct fontsize to 8pt for consistency - - No visual emphasis on the dominant segment (48% Equities) — could use a subtle - pull-out or slightly increased radius on the largest inner wedge to create a focal - point without faking interactivity - - The ax.pie() approach is idiomatic but there is an opportunity to use matplotlib - wedge properties more creatively (e.g. wedge explode for the largest segment) - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct theme surface - Chrome: Title 'pie-portfolio-interactive · python · matplotlib · anyplot.ai' fully visible in dark ink at top. Legend title 'Asset Allocation' and all legend entries readable. Center label 'Portfolio\nAllocation' in dark ink on center of donut. - Data: Dual-ring donut. Inner ring: Equities (#009E73, 48%), Fixed Income (#C475FD, 30%), Alternatives (#4467A3, 17%), Cash (#BD8233, 5%). Outer ring: same palette colors at alpha=0.72 showing individual holdings. Wedge percentage labels in PCT_COLOR (near-white). First series correctly #009E73. - Legibility verdict: PASS — all text readable against warm off-white background, no light-on-light issues. - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct dark theme surface - Chrome: Title in light #F0EFE8, clearly readable. Legend entries in #B8B7B0 (INK_SOFT). Center label in light INK token. Legend frame uses elevated dark background #242420. No dark-on-dark failures. - Data: Data colors identical to light render — #009E73, #C475FD, #4467A3, #BD8233 unchanged across themes. Only chrome flips. - Legibility verdict: PASS — all text readable against warm near-black background, brand green #009E73 clearly visible. - criteria_checklist: - visual_quality: - score: 28 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 6 - max: 8 - passed: true - comment: All font sizes explicitly set (title_fontsize computed, inner 8pt, - outer 7pt, center 10pt, legend 8pt). Readable at canvas resolution; outer - 7pt labels marginal at mobile scale. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text. Legend items properly spaced. Wedge labels do - not collide. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Both donut rings clearly visible. Alpha=0.72 on outer ring preserves - visibility while creating visual hierarchy. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: CVD-safe Imprint palette. Four distinct hue families. Wedge edges - provide shape separation. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Pie in left half (right=0.52), legend in right half. Square canvas - well-utilized. Balanced composition. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: No traditional axes (pie chart). Correct mandated title format. Center - label and legend title provide clear context. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series Equities = #009E73. Canonical order (#C475FD, #4467A3, - #BD8233). Background #FAF8F1/#1A1A17. Chrome flips correctly. Data colors - identical across themes.' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Thoughtful hierarchical design with alpha differentiation. Above - configured default. No emphasis on dominant segment; lacks a focal point. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: No grid (appropriate). Wedge edges with PAGE_BG color. Legend frame - with elevated background. Alpha layering. Some refinement visible. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Inner/outer ring hierarchy communicates two-level structure. Dollar - values provide actionable context. Dominant 48% Equities segment visually - clear. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct donut/pie chart. Dual-ring is appropriate static adaptation - of hierarchical drill-down. - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: 'All static-feasible features: donut chart, weights+dollar values, - category grouping, color-coding, legend. Interactive features correctly - omitted (not faked).' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Inner ring = category totals, outer ring = individual holdings. Proportions - correctly reflect weights. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title: ''pie-portfolio-interactive · python · matplotlib · anyplot.ai''. - Legend titled ''Asset Allocation'' with correct hierarchical entries.' - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Both hierarchy levels shown. Multiple asset classes. Sub-holdings - across each class. Full diversified portfolio spectrum. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Realistic diversified balanced fund. Standard asset classes and holdings. - Neutral business domain, no controversial content. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Weights sum exactly to 100%. $1M portfolio with plausible sub-allocations. - Equities-heavy balanced fund is realistic. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear: theme tokens → data → inner ring → outer ring → center label - → legend → title → save.' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Deterministic hardcoded data. No randomness. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only os, matplotlib.pyplot, matplotlib.patches.Patch — all used. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean Pythonic code. No fake interactive elements. Legend loop is - appropriate complexity. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: fig.savefig(f'plot-{THEME}.png', dpi=400, facecolor=PAGE_BG) without - bbox_inches='tight'. Current API. - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Uses ax.pie() correctly (not pyplot.pie()), fig.text() for figure-level - title, Patch for custom legend handles. - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Dual-ring via two ax.pie() calls with different radii and widths. - Custom Patch-based hierarchical legend with alpha variation. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - custom-legend - - annotations - patterns: - - iteration-over-groups - - explicit-figure - dataprep: [] - styling: - - alpha-blending - - edge-highlighting diff --git a/plots/pie-portfolio-interactive/metadata/python/plotly.yaml b/plots/pie-portfolio-interactive/metadata/python/plotly.yaml deleted file mode 100644 index 6fd5944108..0000000000 --- a/plots/pie-portfolio-interactive/metadata/python/plotly.yaml +++ /dev/null @@ -1,262 +0,0 @@ -library: plotly -language: python -specification_id: pie-portfolio-interactive -created: '2026-01-20T23:09:02Z' -updated: '2026-05-27T17:24:26Z' -generated_by: claude-sonnet -workflow_run: 26526610222 -issue: 3754 -language_version: 3.13.13 -library_version: 6.7.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/plotly/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/plotly/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/plotly/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/plotly/plot-dark.html -quality_score: 91 -review: - strengths: - - Semantic ESG color mapping (green for Sustainable Equities, blue for Technology, - lavender for Green Fixed Income, ochre for Real Assets) creates meaningful visual - communication beyond arbitrary ordering - - Clean donut with bold center annotation immediately communicating fund identity - - Real Plotly updatemenus toggle buttons — genuine interactivity, not fake UI - - Rich hovertemplate with customdata surfaces asset name, weight, and category on - hover - - Custom category legend using ghost go.Scatter traces shows total weights per asset - class - - Correct theme-adaptive chrome in both renders with all fonts explicitly set - - Deterministic ESG data using real company names (Ørsted, NextEra, Vestas) summing - to exactly 100% - weaknesses: - - Spec calls for click-on-segment drill-down into sub-holdings (e.g., clicking Technology - reveals only Microsoft/Alphabet/Schneider Electric); implementation uses toggle - buttons for all-holdings vs. all-categories views — functional but misses the - specified interaction model - - Legend box border could be removed for cleaner minimalist appearance per anyplot - style guide - - 'DE-01 could improve: within-donut visual hierarchy is flat — differentiated outer-ring - arc labels by category or proportionally varied pull values would guide the eye - more deliberately' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct anyplot light surface. No pure white. - Chrome: Title "pie-portfolio-interactive · python · plotly · anyplot.ai" in dark INK (#1A1A17), clear and fully visible at ~60% canvas width. Center annotation "ESG Portfolio" in bold dark INK at fontsize 22 — strong focal point. Toggle buttons ("All Holdings" / "By Category") in ELEVATED_BG with INK_SOFT border — readable. Legend at bottom with ELEVATED_BG box, INK_SOFT border, INK_SOFT text for 4 category entries — readable. - Data: Donut chart with 11 slices. Colors — Sustainable Equities: #009E73 (brand green), Technology: #4467A3 (blue), Green Fixed Income: #C475FD (lavender), Real Assets: #BD8233 (ochre). All from Imprint palette with semantic mapping. First series #009E73 PASS. White separators (PAGE_BG, width=3) between segments add clean definition. Percentage labels inside slices all visible. - Legibility verdict: PASS — all title, annotation, toggle, and legend text clearly readable against #FAF8F1 background. No light-on-light issues. - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct anyplot dark surface. No pure black. - Chrome: Title text is light (#F0EFE8 via INK token) against dark background — fully readable. Center "ESG Portfolio" annotation appears in light text — readable. Toggle buttons styled with ELEVATED_BG (#242420) and light INK text — both visible. Legend text in INK_SOFT (#B8B7B0) on ELEVATED_BG (#242420) box — readable. No dark-on-dark failures observed. - Data: Same four colors as light render — #009E73, #4467A3, #C475FD, #BD8233 — identical to light render. Data colors correctly theme-invariant. - Legibility verdict: PASS — all chrome elements adapt correctly to dark theme. No "dark on dark" failures. - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All font sizes explicitly set (title=16, pie text=13, annotation=22, - legend=12/13, buttons=13). Well-proportioned in both themes. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text. Legend entries well-spaced. Percentage labels - inside donut segments are non-overlapping. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: All 11 donut segments clearly visible with distinct colors. Percentage - labels readable. Center annotation prominent. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Imprint palette is CVD-safe. Four distinct hue families used. White - separator lines add contrast between adjacent same-family segments. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Square 2400x2400 canvas correct for pie/donut chart. Donut fills - ~60% of canvas area. Balanced margins (t=140, b=160, l/r=60). No content - cut off. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Pie chart — no axis labels needed. Title is correct. Center annotation - serves as descriptive chart label. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First category (Sustainable Equities) → #009E73 PASS. Semantic exception - correctly applied: all 4 colors from Imprint palette. Background #FAF8F1/#1A1A17 - PASS. Chrome theme-adaptive PASS.' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Strong semantic color mapping and ESG narrative coherence. Center - annotation, custom category legend, toggle interactivity elevate above defaults. - Not quite publication-ready — missing inner-chart visual hierarchy. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: White segment separators, ELEVATED_BG buttons/legend with INK_SOFT - borders, generous margins. Clean presentation for a donut chart type. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Center ESG focal point, semantic color coding (green=sustainability), - dual-view toggle enabling category-level comparison. Guides viewer to asset - class composition story. - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Donut chart (pie with hole=0.42) — correct chart type per spec. - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Hover tooltips (customdata+hovertemplate), color-coding by category, - category legend, both percentage and absolute values in tooltips, return-to-overview - navigation — all present. Drill-down is via toggle buttons rather than true - click-on-segment drill-down. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Weights correctly mapped to pie values. 11 holdings summing to 100%. - Categories correctly assigned. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title 'pie-portfolio-interactive · python · plotly · anyplot.ai' - correct. Category legend with total weights per asset class. - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 4 distinct asset classes, 11 holdings, varied weights (5%-15%), category - aggregation view available. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Real company names (Ørsted, NextEra Energy, Vestas Wind, Microsoft, - Alphabet, Schneider Electric). ESG fund is a real, neutral finance scenario. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Weights sum to exactly 100%. Individual holdings 5-15% range is realistic - for a diversified ESG portfolio. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear structure: imports → tokens → data → figure → layout → save. - No functions or classes.' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Fully deterministic hardcoded data. No randomness requiring a seed. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only os, pandas, plotly.graph_objects — all used. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean Plotly idioms. Toggle via real updatemenus, not fake drawn - buttons. Ghost scatter for custom legend is established pattern. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html. Current Plotly API. - autosize=False with explicit width/height. - library_mastery: - score: 9 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: go.Pie with donut hole, updatemenus, customdata in hovertemplate, - add_annotation, update_traces(selector=dict(type='pie')) — all idiomatic - Plotly. - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: updatemenus with update method, customdata+hovertemplate, ghost scatter - custom legend pattern, html export — distinctively Plotly features. Not - quite 5 as none are advanced-tier Plotly. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - hover-tooltips - - html-export - - annotations - - custom-legend - patterns: - - groupby-aggregation - dataprep: [] - styling: - - edge-highlighting diff --git a/plots/pie-portfolio-interactive/metadata/python/plotnine.yaml b/plots/pie-portfolio-interactive/metadata/python/plotnine.yaml deleted file mode 100644 index 119ba460af..0000000000 --- a/plots/pie-portfolio-interactive/metadata/python/plotnine.yaml +++ /dev/null @@ -1,266 +0,0 @@ -library: plotnine -language: python -specification_id: pie-portfolio-interactive -created: '2026-01-20T23:08:25Z' -updated: '2026-05-27T17:56:21Z' -generated_by: claude-sonnet -workflow_run: 26526927100 -issue: 3754 -language_version: 3.13.13 -library_version: 0.15.4 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/plotnine/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/plotnine/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 85 -review: - strengths: - - 'Excellent dual-panel donut design: main portfolio allocation + equities drill-down - with connecting dashed arrow creates a compelling static narrative of the interactive - concept' - - 'Perfect Imprint palette compliance — Equities #009E73 (brand green), Fixed Income - #4467A3 (blue/stability), Alternatives #C475FD (lavender), Cash #BD8233 (ochre) - — all semantic assignments are defensible and from the palette' - - 'Both light (#FAF8F1) and dark (#1A1A17) themes render correctly with full theme-adaptive - chrome: title, section labels, center labels, and legend text all use INK/INK_SOFT - tokens' - - Both percentage and dollar-value labels are shown on each segment, meeting the - spec requirement to display both types of data - - 'Perfect code quality: KISS structure, np.random.seed(42), all imports used, no - functions/classes' - weaknesses: - - Detail donut percentage labels show portfolio-level weights (18%, 12%, 10%, 5%) - while the donut proportions visualize equities sub-allocation (40%, 27%, 22%, - 11%) — the labels are inconsistent with what the donut visually encodes, which - can mislead viewers about how Equities is split internally - - Legend text (size=2.8 mm ≈ 8pt) and small-slice USD labels (size=2.8–3.0 mm) are - on the small side and may become hard to read at mobile width (~400px scaled); - increase legend text to size=3.2 and USD labels to size=3.2 - - 'Library Mastery limited: plotnine has no native pie/donut geom, so the implementation - hand-crafts polygon arcs — idiomatic score is bounded by this inherent library - limitation' - - Small horizontal whitespace on left/right canvas edges (slices don't extend close - to the scale boundaries); could narrow x-limits slightly to center both donuts - more tightly and reduce empty margin - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct theme surface, fills the full 3200×1800 canvas. - Chrome: Title "pie-portfolio-interactive · python · plotnine · anyplot.ai" in dark bold text, well-centered at top. Section titles "Portfolio Allocation" and "Equities Breakdown" in dark INK above each donut. Center hole labels ("Total", "$1.2M" in main; "$540K", "Equities" in detail) in INK_SOFT — readable against the cream background. Bottom legend shows four colored squares with "Equities", "Fixed Income", "Alternatives", "Cash" labels in INK_SOFT — small but legible. All text reads clearly against the light background. - Data: Left donut (main): green (#009E73) Equities 45%, blue (#4467A3) Fixed Income 30%, lavender (#C475FD) Alternatives 15%, ochre (#BD8233) Cash 10%. Right donut (detail): cyan Tech Stocks 18%, rose Healthcare 12%, blue Financials 10%, ochre Energy 5%. Dashed arrow with arrowhead connects main to detail donut. White text labels on each segment show % and $K values. First series is #009E73 as required. - Legibility verdict: PASS — all text readable; legend text and small-slice USD labels are slightly small but visible at full resolution. - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct dark theme surface. - Chrome: Title in light #F0EFE8 text — clearly visible. Section titles in light INK text — readable. Center labels in INK_SOFT (#B8B7B0) — adequately visible against the dark donut-hole center. Bottom legend text in light INK_SOFT — readable. No dark-on-dark failures observed; all text elements use theme-adaptive tokens correctly. - Data: Palette colors are identical to the light render — green Equities, blue Fixed Income, lavender Alternatives, ochre Cash, cyan Tech, rose Healthcare. The connecting dashed arrow in INK_SOFT is visible but slightly muted. Data color identity between themes: CONFIRMED. - Legibility verdict: PASS — all text readable in dark mode; no chrome failures detected. - criteria_checklist: - visual_quality: - score: 26 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 6 - max: 8 - passed: true - comment: All font sizes explicitly set; title, section labels, center labels - all readable in both themes. Legend text (size=2.8mm) and small-slice USD - labels slightly small for mobile width. - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: Generally clean; minor concern about tight label placement on the - small 5% Energy slice in the detail donut. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: All segments clearly visible with distinct Imprint palette colors; - labels on segments are readable. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: CVD-safe Imprint palette; white text on colored segments provides - good contrast. - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: Good dual-panel layout with connecting arrow; slight unused horizontal - margin on left/right edges. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: N/A for axis labels on donut chart. Title format correct. Section - titles descriptive. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73 for Equities. All colors from Imprint palette - with defensible semantic assignments. Both backgrounds correct (#FAF8F1 - / #1A1A17).' - design_excellence: - score: 16 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 6 - max: 8 - passed: true - comment: Strong dual-panel donut design with connecting arrow. Semantic color - assignments and multi-level data representation clearly above library defaults. - - id: DE-02 - name: Visual Refinement - score: 5 - max: 6 - passed: true - comment: All axes and grid hidden appropriately for donut chart. Subtle segment - edge color using PAGE_BG. Clean whitespace around panels. - - id: DE-03 - name: Data Storytelling - score: 5 - max: 6 - passed: true - comment: Two-panel drill-down narrative is immediately clear. Connecting arrow - and center labels ($1.2M / $540K) create strong narrative flow from overview - to detail. - spec_compliance: - score: 13 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct donut chart implementation for portfolio allocation. - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Both % and $ shown; drill-down as static dual panel; legend present; - color-coded by asset class. Interactivity not possible in plotnine. - - id: SC-03 - name: Data Mapping - score: 2 - max: 3 - passed: true - comment: Main donut correctly maps portfolio weights. Detail donut proportions - represent equities sub-allocation but labels show portfolio-level % — minor - inconsistency. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title is exact 'pie-portfolio-interactive · python · plotnine · anyplot.ai'. - Legend labels match asset class categories with correct colors. - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: Shows main portfolio allocation and equities sub-breakdown with both - % and $K. Only equities breakdown shown (not fixed income or alternatives - sub-breakdowns). - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Realistic $1.2M investment portfolio with standard asset classes - and named equity sub-sectors. Neutral business scenario. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: 45/30/15/10 allocation is realistic for a balanced portfolio. Sub-sector - weights (Tech 18%, Healthcare 12%, Financials 10%, Energy 5%) are plausible - equity breakdown. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: No functions or classes. Inline geometry construction for polygon - arcs. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imported names are used in the plot construction. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean code. No fake interactivity or UI elements. Polygon geometry - construction is necessarily complex for donut charts in plotnine. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png with dpi=400, width=8, height=4.5. - library_mastery: - score: 6 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 3 - max: 5 - passed: true - comment: Correct plotnine grammar-of-graphics approach. scale_fill_identity() - and coord_fixed() are well-chosen. Manual polygon construction is the only - option for donut charts in plotnine, limiting idiomatic score. - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: geom_segment with arrow() parameter (plotnine-specific), scale_fill_identity() - for pre-specified colors, coord_fixed() for aspect control. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - layer-composition - - custom-legend - patterns: - - data-generation - - iteration-over-groups - dataprep: [] - styling: - - minimal-chrome - - alpha-blending - - edge-highlighting diff --git a/plots/pie-portfolio-interactive/metadata/python/pygal.yaml b/plots/pie-portfolio-interactive/metadata/python/pygal.yaml deleted file mode 100644 index 5c6d00fbae..0000000000 --- a/plots/pie-portfolio-interactive/metadata/python/pygal.yaml +++ /dev/null @@ -1,242 +0,0 @@ -library: pygal -language: python -specification_id: pie-portfolio-interactive -created: '2026-01-20T23:07:32Z' -updated: '2026-05-27T17:49:48Z' -generated_by: claude-sonnet -workflow_run: 26527033852 -issue: 3754 -language_version: 3.13.13 -library_version: 3.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/pygal/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/pygal/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/pygal/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/pygal/plot-dark.html -quality_score: 85 -review: - strengths: - - Creative center annotation (55%/Equities/10 holdings) creates a clear focal point - - Category totals in legend labels provide portfolio overview at a glance - - 'Correct Imprint palette with #009E73 as first series; both themes pass readability - checks' - - Per-item hover tooltip labels via pygal native JS expose individual holding details - - SVG center annotation elegantly injected into pygal output pipeline - weaknesses: - - Small Alternatives sub-slice labels near chart top (5.0%, 5.0%, 7.0%) are crowded - — suppress or reposition labels on slices under ~6% - - No absolute dollar values alongside percentages; spec requires displaying both; - add notional AUM and compute holding dollar values for tooltips - - Individual holding names not visible in static PNG — only percentages on slice - labels; consider rendering names on larger slices (>=10%) - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct light theme surface - Chrome: Title "pie-portfolio-interactive · python · pygal · anyplot.ai" in dark text at top — fully readable. Legend at bottom with colored swatches and category labels — readable. No axis labels (appropriate for pie chart). - Data: Three donut segments — brand green #009E73 (Equities ~55%), lavender #C475FD (Fixed Income ~28%), blue-grey #4467A3 (Alternatives ~17%). Percentage labels on each sub-slice. Center annotation: bold "55%" / "Equities" / "10 holdings". - Legibility verdict: PASS — all text readable; minor crowding on the three small Alternatives labels (5.0%, 5.0%, 7.0%) near the top of the donut but all are legible. - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct dark theme surface - Chrome: Title and legend text have flipped to light-colored — fully readable against dark background. No dark-on-dark failures detected. Center annotation text visible in lighter tone. - Data: Segment colors are identical to the light render (#009E73 green, #C475FD lavender, #4467A3 blue-grey) — data palette unchanged across themes, only chrome flipped. - Legibility verdict: PASS — all text readable against dark background; no dark-on-dark issues. - criteria_checklist: - visual_quality: - score: 27 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: Font sizes explicitly set; all text readable in both themes; small - deduction for slightly small value labels on 5% sub-slices - - id: VQ-02 - name: No Overlap - score: 4 - max: 6 - passed: true - comment: Minor crowding on three small Alternatives sub-slice labels near - top of donut; main content readable - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: All segments clearly visible; center annotation bold and prominent; - colors well-differentiated - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: CVD-safe Imprint palette; three distinct hue families (green/purple/blue) - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Correct 2400x2400 square canvas; donut fills canvas well; generous - margins; legend cleanly at bottom - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title in exact mandated format; no axis labels needed for pie chart - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73; positions 2-3 in canonical order; light #FAF8F1 - / dark #1A1A17 backgrounds; theme-adaptive text in both renders' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Above default; center annotation shows design thinking; category - totals in legend add hierarchy; professional but not publication-ready level - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: true - comment: Generous margins; no grid or spines (appropriate for pie); clean - legend; moderate refinement above bare defaults - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Center annotation creates focal point for dominant Equities allocation; - color coding guides eye; category totals in legend; individual holding names - only visible on hover - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct donut/pie chart; spec explicitly allows pie or donut - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: 'Hover tooltips via pygal native JS; grouped by asset class; color-coded; - legend present. Missing: no absolute dollar values, no true drill-down navigation' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Asset weights correctly mapped to slice sizes; categories sum to - 100%; all 10 holdings accessible via hover - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Exact mandated title format; legend labels include category name - and total percentage - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Three distinct asset classes; 10 individual holdings; center annotation - highlights dominant allocation; proportional area encoding - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Realistic balanced multi-asset portfolio with recognizable asset - names; proportions match typical institutional allocation - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Weights sum to 100%; 55/28/17 split is a plausible balanced portfolio; - individual weights 5-25% are realistic - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear script: imports -> data -> style -> chart -> render -> save' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Hardcoded data; deterministic output - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports (os, re, sys, cairosvg, pygal, Style) are used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: sys.path manipulation justified and commented; SVG annotation injection - creative; value_formatter lambda clean - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Uses pygal.Pie with inner_radius, per-item label dicts for hover - tooltips, value_formatter, legend_at_bottom_columns — all idiomatic - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Native JS hover via HTML export; per-item label dicts for per-slice - tooltip text; SVG center annotation injection — creative library-specific - technique - verdict: APPROVED -impl_tags: - dependencies: - - cairosvg - techniques: - - hover-tooltips - - html-export - - annotations - patterns: - - iteration-over-groups - dataprep: [] - styling: [] diff --git a/plots/pie-portfolio-interactive/metadata/python/seaborn.yaml b/plots/pie-portfolio-interactive/metadata/python/seaborn.yaml deleted file mode 100644 index c7f2c4fbff..0000000000 --- a/plots/pie-portfolio-interactive/metadata/python/seaborn.yaml +++ /dev/null @@ -1,267 +0,0 @@ -library: seaborn -language: python -specification_id: pie-portfolio-interactive -created: '2026-01-20T23:08:42Z' -updated: '2026-05-27T17:35:33Z' -generated_by: claude-sonnet -workflow_run: 26526502545 -issue: 3754 -language_version: 3.13.13 -library_version: 0.13.2 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/seaborn/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/python/seaborn/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 84 -review: - strengths: - - Dual-ring donut effectively communicates both asset-class aggregates and individual - holdings simultaneously without requiring interactivity - - Semantic color assignment (green=equities/growth, blue=fixed income/stability, - ochre=alternatives) is clearly justified and uses Imprint palette members correctly - - sns.blend_palette() / sns.light_palette() / sns.dark_palette() gradient approach - for within-category differentiation is a distinctive seaborn usage - - Theme adaptation is correct and comprehensive across all chrome elements in both - renders — no dark-on-dark or light-on-light failures - - Realistic 9-holding portfolio data with percentage + dollar values provides excellent - real-world context - - Category-colored borders on annotation callout boxes create a sophisticated visual - link between annotation and wedge - weaknesses: - - 'Legend text truncated in rightmost column: ''Fixed Income: 35% ($350K, 3 hol'' - — ''dings)'' is cut off. With ncol=3 and long label strings the column width is - insufficient. Fix: use ncol=1 with loc=''lower right'' outside the donut, or shorten - the label format, or switch to ncol=1 stacked layout at bbox_to_anchor=(1.02, - 0.5).' - - 'Four annotation callout boxes are tightly stacked in the upper-right quadrant - with minimal spacing. Distribute by quadrant: top-left for largest Equities holding, - top-right for International Equity, right for Fixed Income entries.' - - Annotation and legend text at fontsize=7 is functional but at the lower edge of - readability at 2400x2400. Bump to fontsize=8 for both. - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct anyplot light surface - Chrome: Title "pie-portfolio-interactive · python · seaborn · anyplot.ai" readable in dark #1A1A17 ink. No axis labels (appropriate for pie). Annotation callout text in INK (#1A1A17) on ELEVATED_BG (#FFFDF6) boxes. Legend text in INK_SOFT (#4A4A44) on elevated background. - Data: Outer ring uses gradient palette variants of #009E73 (Equities, 3 shades), #4467A3 (Fixed Income, 3 shades), #BD8233 (Alternatives, 3 shades). Inner ring uses solid base colors. First series (Equities) anchored to #009E73. Legend truncation: "Fixed Income: 35% ($350K, 3 hol" — "dings)" cut off in ncol=3 layout. - Legibility verdict: PASS (minor: legend text truncated in rightmost column, not canvas clipping) - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct anyplot dark surface - Chrome: Title switches to light #F0EFE8 ink. Annotation callout boxes use ELEVATED_BG (#242420) with light INK text. Legend text uses INK_SOFT (#B8B7B0). Inner ring labels remain white (#FFFFFF hardcoded) — appropriate on solid saturated base colors. No dark-on-dark failures detected. - Data: All data colors identical to light render (#009E73 green, #4467A3 blue, #BD8233 ochre base hues). Only chrome flips between themes. - Legibility verdict: PASS (same legend truncation as light render, not a readability failure) - criteria_checklist: - visual_quality: - score: 27 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: All sizes explicitly set (title=12pt, annotations=7pt, legend=7pt, - inner=9pt, outer=8pt, center=10pt). Annotation and legend text at 7pt is - functional but small for 2400x2400. - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: Four annotation callout boxes stacked tightly in upper-right quadrant. - No actual overlap but very compressed vertical spacing. Legend text truncation - also contributes. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: All wedges, labels, callout boxes, and center text clearly visible - in both themes. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Semantic palette is CVD-safe. White text on solid category colors - has adequate contrast. No red-green sole signal. - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: 2400x2400 square correct for donut chart. Chart fills canvas well. - Legend text truncation ('3 hol' instead of '3 holdings') is a real layout - defect. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: No axis labels needed for pie chart. Title is correct format. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Equities=#009E73, Fixed Income=#4467A3, Alternatives=#BD8233 — all - Imprint palette members with clear semantic justification. Backgrounds #FAF8F1/#1A1A17 - correct. Chrome tokens correct in both themes.' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Dual-ring donut with per-category gradient palettes using sns.light_palette/dark_palette/blend_palette - is above defaults. Category-colored annotation borders are refined. Falls - short of 'strong design' (6) — gradient adds complexity without strongly - improving readability. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Clean pie styling with PAGE_BG edge colors between wedges, no gridlines, - fancybox legend frame. Noticeably above minimal defaults. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Two-ring structure communicates both granularity levels simultaneously. - Top-4 callouts highlight dominant holdings. Center focal point delivers - portfolio total. Good visual hierarchy. - spec_compliance: - score: 13 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Dual-ring donut chart is the correct visualization per spec. - - id: SC-02 - name: Required Features - score: 2 - max: 4 - passed: false - comment: Static features well implemented (percentage + absolute values, color-coding, - legend, dual-ring hierarchy). Primary spec requirements (interactive hover - tooltips, click-to-drill-down, return navigation) are entirely absent — - fundamentally not achievable in seaborn. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Weights correctly mapped to arc sizes, sum to 100%, categories correctly - grouped. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title: ''pie-portfolio-interactive · python · seaborn · anyplot.ai''. - Legend shows asset class, percentage, dollar value, and holding count.' - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Shows 9 holdings across 3 categories, both percentage weights and - dollar values, category aggregates, and per-category holding counts. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Real-world investment portfolio with realistic asset names. Neutral - business domain. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Weights (5-28%) realistic. $1M total portfolio with per-holding values - ($40K-$280K) plausible. Weights sum exactly to 100%. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear flow: imports → theme → data → palette → plot → annotations - → legend → title → save. No functions or classes.' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Data is fully deterministic (hardcoded DataFrame, no randomness). - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: 'All five imports used: os, matplotlib.pyplot, numpy, pandas, seaborn.' - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean, Pythonic. Gradient palette generation loop is appropriately - complex. No fake interactivity. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png, no bbox_inches='tight', seaborn 0.13+ - API. - library_mastery: - score: 6 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 3 - max: 5 - passed: true - comment: sns.set_theme() with rc= overrides is idiomatic for full theme control. - Palette utilities (sns.light_palette, sns.dark_palette, sns.blend_palette) - used idiomatically. Core donut uses ax.pie() (matplotlib) since seaborn - has no native pie function. - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Per-category gradient generation using sns.light_palette() + sns.dark_palette() - + sns.blend_palette() is a distinctive seaborn capability for smooth within-group - tonal variation. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - annotations - - custom-legend - patterns: - - groupby-aggregation - - iteration-over-groups - - explicit-figure - dataprep: [] - styling: - - alpha-blending - - edge-highlighting diff --git a/plots/pie-portfolio-interactive/metadata/r/ggplot2.yaml b/plots/pie-portfolio-interactive/metadata/r/ggplot2.yaml deleted file mode 100644 index ac395b3ace..0000000000 --- a/plots/pie-portfolio-interactive/metadata/r/ggplot2.yaml +++ /dev/null @@ -1,278 +0,0 @@ -library: ggplot2 -language: r -specification_id: pie-portfolio-interactive -created: '2026-05-27T17:37:04Z' -updated: '2026-05-27T18:18:22Z' -generated_by: claude-sonnet -workflow_run: 26527356861 -issue: 3754 -language_version: 4.4.1 -library_version: 3.5.1 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/r/ggplot2/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/pie-portfolio-interactive/r/ggplot2/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 86 -review: - strengths: - - Double-donut design using coord_polar with nested x-positions is idiomatic and - visually clean - - Imprint palette applied correctly — Equities=brand green, Fixed Income=lavender, - Alternatives=blue, Cash=ochre in canonical order - - Theme-adaptive chrome works perfectly in both renders — warm backgrounds, light/dark - INK tokens, no dark-on-dark failures - - Realistic $1M model portfolio data with 10 holdings and 4 asset classes, weights - sum exactly to 100 - - Inner ring labels (asset class) + outer ring labels (holding name, %, $) give - both macro and micro context - - 'KISS code structure: flat, no functions, deterministic data, clean imports' - - Title fontsize scales dynamically with string length — no overflow at the 2400×2400 - canvas - weaknesses: - - Inner ring label for Alternatives (10% slice) is tight — white text is cramped - in the narrow wedge; consider dropping to weight >= 12 threshold or reducing to - single-line label - - Outer ring floating labels at size=2.6 are readable at full resolution but may - be marginal at mobile thumbnail scale (~400px) — consider bumping to size=3.0 - - DE-01 design is correct but not exceptional — still reads as a well-configured - library default rather than FiveThirtyEight-level polish - - Bottom label cluster (US Treasuries + Emerging Mkts) sit close together at similar - angular positions — minimal risk of crowding - image_description: "Light render (plot-light.png):\n Background: Warm off-white\ - \ #FAF8F1 — correct surface color, not pure white.\n Chrome: Bold title \"pie-portfolio-interactive\ - \ · r · ggplot2 · anyplot.ai\" in dark INK, \n reads well. Subtitle in muted\ - \ INK_MUTED at 7pt — legible. Legend at bottom with \n \"Asset Class\" title\ - \ (bold, INK) and four color swatches (INK_SOFT text), framed \n with a subtle\ - \ ELEVATED_BG box.\n Data: Double-donut. Inner ring — Equities (#009E73, large\ - \ green segment), Fixed Income \n (#C475FD, lavender), Alternatives (#4467A3,\ - \ small blue), Cash (#BD8233, tiny ochre). \n Outer ring — same colors, subdivided\ - \ into 10 individual holdings with white \n 1px separators. External labels\ - \ (INK) for the 5 largest holdings show holding name, \n %, and $k value. Inner\ - \ ring labels in white show category name + %.\n Legibility verdict: PASS — all\ - \ text readable. Outer labels at size=2.6 are small but \n legible at full\ - \ canvas. Inner \"Alternatives\\n10%\" is slightly cramped in the \n narrow\ - \ slice but visible.\n\nDark render (plot-dark.png):\n Background: Warm near-black\ - \ #1A1A17 — correct, not pure black.\n Chrome: Title and subtitle use INK=#F0EFE8\ - \ (light cream) — clearly readable against \n dark surface. Legend text uses\ - \ INK_SOFT=#B8B7B0. Legend box uses ELEVATED_BG=#242420 \n with INK_SOFT border\ - \ — well-defined against the dark background.\n Data: Data colors are IDENTICAL\ - \ to light render — same #009E73 green, #C475FD lavender, \n #4467A3 blue,\ - \ #BD8233 ochre. Only chrome flips. External holding labels use INK \n (#F0EFE8)\ - \ which reads well on the dark surface. Inner ring white labels remain \n legible\ - \ on the colored segments.\n Legibility verdict: PASS — no dark-on-dark failures.\ - \ No elements invisible against \n the dark background. Brand green #009E73\ - \ is clearly visible." - criteria_checklist: - visual_quality: - score: 28 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: All sizes explicitly set (title dynamic, subtitle=7pt, inner labels=3.5mm, - outer labels=2.6mm). Readable in both themes. Outer ring labels slightly - small at mobile scale. - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: No significant overlap. Minor crowding in Alternatives inner ring - label (10% slice, narrow wedge). Bottom label cluster (US Treasuries + Emerging - Mkts) sits close but non-overlapping. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Donut segments clearly visible in both themes. White separators define - segment boundaries. Colors well-contrasted. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Anyplot CVD-safe palette. Four categories easily distinguished by - hue across deuteranopia/protanopia simulations. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Double-donut fills the square canvas well. Balanced margins. Legend - positioned cleanly at bottom. No content cut off. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Pie chart — no axes needed. Title correct. Subtitle provides context - (portfolio size, holdings count, ring explanation). - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Equities=#009E73 (brand green first) ✓. Remaining in canonical order: - #C475FD, #4467A3, #BD8233. Backgrounds #FAF8F1/#1A1A17 ✓. Data colors identical - across themes ✓.' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: false - comment: Well-configured — correct palette, clean layout, good typography - hierarchy — but reads as a polished library default rather than FiveThirtyEight-level - design. Not exceptional. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: theme_void() appropriate for donut, white segment separators, subtle - legend border, clean subtitle. Refinement clearly above defaults. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Two-ring structure guides viewer from macro (asset class) to micro - (individual holding). Dollar values make it actionable. Subtitle explains - structure. Good hierarchy. - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Double donut chart — correct primary visualization type per spec. - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Color-code by asset class ✓, legend ✓, % and $ values ✓, grouped - by category ✓. Interactive features (hover, drill-down, navigation) correctly - omitted per ggplot2 static-only rules. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Weights sum to 100 ✓. Category groupings correct ✓. Dollar values - computed from $1M portfolio assumption ✓. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title: ''pie-portfolio-interactive · r · ggplot2 · anyplot.ai'' - ✓. Legend: ''Asset Class'' with correct color swatches ✓.' - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'Shows all plot aspects: nested structure, 4 asset classes, 10 holdings, - varying weights from 4% to 20%, both % and $ values.' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Real-world $1M model portfolio — neutral finance domain. Asset allocation - (55% equities, 30% fixed income) is realistic and professionally credible. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Typical growth-oriented allocation proportions. Individual holding - weights are realistic (20% largest position, 4% smallest). Dollar values - correctly derived. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Flat script, no functions/classes. Clear Imports→Tokens→Data→Plot→Save - flow. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Fully deterministic — hardcoded holdings data, no random generation - needed. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: ggplot2, dplyr, ragg — all three actively used. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean R with native pipe (|>), dplyr idioms, no fake interactivity, - appropriate complexity. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png via ragg::agg_png. Correct canvas 2400×2400 - at 6in×400dpi. - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: coord_polar(theta='y') for donut, geom_col() at different x positions - for concentric rings, xlim() for hole, theme_void(). Correct ggplot2 donut - pattern. dplyr groupby/cumsum for angular midpoints. - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Double-donut via nested x-position offsets in coord_polar is a ggplot2-specific - grammar-of-graphics technique — assigning different x values to two geom_col - layers creates concentric rings. Not easily replicated in other libraries. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - polar-projection - - layer-composition - - annotations - patterns: - - groupby-aggregation - dataprep: - - cumulative-sum - styling: - - minimal-chrome - - edge-highlighting diff --git a/plots/pie-portfolio-interactive/specification.md b/plots/pie-portfolio-interactive/specification.md deleted file mode 100644 index 80c7558c7c..0000000000 --- a/plots/pie-portfolio-interactive/specification.md +++ /dev/null @@ -1,29 +0,0 @@ -# pie-portfolio-interactive: Interactive Portfolio Allocation Chart - -## Description - -An interactive visualization showing portfolio weight allocation across assets, with the ability to explore holdings and sub-allocations through hover interactions and drill-down navigation. This chart enables investors and analysts to quickly understand portfolio composition at multiple levels of granularity, from high-level asset class breakdown to individual holdings. Ideal for portfolio reporting, rebalancing analysis, and investment fund communication. - -## Applications - -- Portfolio composition visualization for wealth management dashboards -- Asset allocation reporting in quarterly investment reports -- Investment fund breakdown for prospectus materials -- Rebalancing analysis comparing current vs target allocations - -## Data - -- `asset` (string) - Asset or holding name (e.g., "Apple Inc.", "US Treasury 10Y") -- `weight` (numeric) - Percentage allocation (0-100, must sum to 100) -- `category` (string, optional) - Asset class grouping (e.g., "Equities", "Fixed Income", "Alternatives") -- Size: 5-20 holdings typical, supports drill-down for larger portfolios - -## Notes - -- Pie or donut chart as primary visualization -- Interactive hover tooltips showing exact weights and values -- Group by asset class with click-to-drill-down into sub-holdings -- Display both percentage and absolute values where applicable -- Color-code by asset class or risk level for quick identification -- Include legend with asset class categories -- Support return-to-overview navigation from drill-down views diff --git a/plots/pie-portfolio-interactive/specification.yaml b/plots/pie-portfolio-interactive/specification.yaml deleted file mode 100644 index 7db9d32b3f..0000000000 --- a/plots/pie-portfolio-interactive/specification.yaml +++ /dev/null @@ -1,29 +0,0 @@ -# Specification-level metadata for pie-portfolio-interactive -# Auto-synced to PostgreSQL on push to main - -spec_id: pie-portfolio-interactive -title: Interactive Portfolio Allocation Chart - -# Specification tracking -created: 2026-01-11T22:46:46Z -updated: 2026-01-11T22:46:46Z -issue: 3754 -suggested: MarkusNeusinger - -# Classification tags (applies to all library implementations) -# See docs/reference/tagging-system.md for detailed guidelines -tags: - plot_type: - - pie - - donut - data_type: - - categorical - - numeric - - hierarchical - domain: - - finance - - business - features: - - interactive - - proportional - - drill-down diff --git a/plots/scatter-animated-controls/implementations/python/altair.py b/plots/scatter-animated-controls/implementations/python/altair.py deleted file mode 100644 index e17b2880f4..0000000000 --- a/plots/scatter-animated-controls/implementations/python/altair.py +++ /dev/null @@ -1,168 +0,0 @@ -""" anyplot.ai -scatter-animated-controls: Animated Scatter Plot with Play Controls -Library: altair 6.1.0 | Python 3.13.13 -Quality: 91/100 | Updated: 2026-05-15 -""" - -import os -import sys - - -# Remove current directory from path to prevent import shadowing -cwd = os.getcwd() -sys.path = [p for p in sys.path if os.path.abspath(p) != cwd] - -import altair as alt # noqa: E402 -import numpy as np # noqa: E402 -import pandas as pd # noqa: E402 - - -# 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" - -# Okabe-Ito palette -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD"] - -# Data: Simulated country development metrics over years -np.random.seed(42) - -countries = [ - "Country A", - "Country B", - "Country C", - "Country D", - "Country E", - "Country F", - "Country G", - "Country H", - "Country I", - "Country J", - "Country K", - "Country L", -] -years_selected = [2000, 2005, 2010, 2015, 2020] - -# Base values for each country -base_gdp = np.array([5, 8, 12, 15, 20, 25, 3, 10, 18, 30, 6, 22]) -base_life = np.array([55, 60, 65, 70, 72, 75, 50, 62, 68, 78, 58, 74]) -population = np.array([50, 80, 120, 45, 200, 30, 150, 90, 60, 25, 180, 40]) - -# Regions for color coding -regions = [ - "Region 1", - "Region 2", - "Region 1", - "Region 2", - "Region 3", - "Region 3", - "Region 1", - "Region 2", - "Region 3", - "Region 3", - "Region 1", - "Region 2", -] - -data = [] -for i, country in enumerate(countries): - for year in years_selected: - years_elapsed = year - 2000 - growth_factor = 1 + years_elapsed * 0.05 + np.random.uniform(-0.03, 0.05) - life_improvement = years_elapsed * 0.2 + np.random.uniform(-0.5, 0.5) - - gdp = base_gdp[i] * growth_factor - life_exp = min(85, base_life[i] + life_improvement) - pop = population[i] * (1 + years_elapsed * 0.015) - - data.append( - { - "Country": country, - "Year": year, - "GDP per Capita (thousands USD)": round(gdp, 1), - "Life Expectancy (years)": round(life_exp, 1), - "Population (millions)": round(pop, 1), - "Region": regions[i], - } - ) - -df = pd.DataFrame(data) - -# Color scale for regions -color_scale = alt.Scale(domain=["Region 1", "Region 2", "Region 3"], range=IMPRINT[:3]) - -# Create base scatter plot -scatter = ( - alt.Chart(df) - .mark_circle(opacity=0.7, strokeWidth=1.5) - .encode( - x=alt.X( - "GDP per Capita (thousands USD):Q", title="GDP per Capita (thousands USD)", scale=alt.Scale(domain=[0, 70]) - ), - y=alt.Y("Life Expectancy (years):Q", title="Life Expectancy (years)", scale=alt.Scale(domain=[45, 90])), - size=alt.Size( - "Population (millions):Q", - scale=alt.Scale(range=[100, 2500]), - legend=alt.Legend(title="Population", titleFontSize=20, labelFontSize=18), - ), - color=alt.Color( - "Region:N", scale=color_scale, legend=alt.Legend(title="Region", titleFontSize=20, labelFontSize=18) - ), - tooltip=[ - alt.Tooltip("Country:N", title="Country"), - alt.Tooltip("Year:Q", title="Year", format=".0f"), - alt.Tooltip("GDP per Capita (thousands USD):Q", title="GDP per Capita", format=".1f"), - alt.Tooltip("Life Expectancy (years):Q", title="Life Expectancy", format=".1f"), - alt.Tooltip("Population (millions):Q", title="Population", format=".1f"), - alt.Tooltip("Region:N", title="Region"), - ], - ) - .properties(width=280, height=400) -) - -# Create faceted chart showing evolution across key years -chart = ( - scatter.facet( - column=alt.Column( - "Year:O", header=alt.Header(title=None, labelFontSize=28, labelFontWeight="bold", labelColor=INK) - ) - ) - .properties( - title=alt.Title( - "scatter-animated-controls · altair · anyplot.ai", - fontSize=32, - anchor="start", - subtitle="Country Development Metrics — Evolution Across Key Years", - subtitleFontSize=22, - ) - ) - .configure_axis( - domainColor=INK_SOFT, - tickColor=INK_SOFT, - gridColor=INK, - gridOpacity=0.10, - labelColor=INK_SOFT, - labelFontSize=14, - titleColor=INK, - titleFontSize=16, - ) - .configure_title(color=INK, fontSize=32, subtitleColor=INK_SOFT) - .configure_legend( - fillColor=ELEVATED_BG, - strokeColor=INK_SOFT, - labelColor=INK_SOFT, - labelFontSize=16, - titleColor=INK, - titleFontSize=18, - padding=15, - ) - .configure_view(fill=PAGE_BG, stroke=INK_SOFT, strokeWidth=0) - .properties(background=PAGE_BG) -) - -# Save outputs -chart.save(f"plot-{THEME}.png", scale_factor=3.0) -chart.save(f"plot-{THEME}.html") diff --git a/plots/scatter-animated-controls/implementations/python/bokeh.py b/plots/scatter-animated-controls/implementations/python/bokeh.py deleted file mode 100644 index c4a2f649a0..0000000000 --- a/plots/scatter-animated-controls/implementations/python/bokeh.py +++ /dev/null @@ -1,318 +0,0 @@ -""" anyplot.ai -scatter-animated-controls: Animated Scatter Plot with Play Controls -Library: bokeh 3.9.0 | Python 3.13.13 -Quality: 95/100 | Updated: 2026-05-15 -""" - -import os -import time -from pathlib import Path - -import numpy as np -import pandas as pd -from bokeh.io import output_file, save -from bokeh.layouts import column, row -from bokeh.models import Button, ColumnDataSource, CustomJS, Div, HoverTool, Label, Slider -from bokeh.plotting import figure -from bokeh.transform import factor_cmap -from selenium import webdriver -from selenium.webdriver.chrome.options import Options - - -# 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" - -# Okabe-Ito palette (regions) -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030"] - -# Data: Simulated country metrics over 20 years (Gapminder-style) -np.random.seed(42) - -n_countries = 15 -years = np.arange(2004, 2024) -n_years = len(years) - -countries = [ - "Country A", - "Country B", - "Country C", - "Country D", - "Country E", - "Country F", - "Country G", - "Country H", - "Country I", - "Country J", - "Country K", - "Country L", - "Country M", - "Country N", - "Country O", -] - -regions = ["North", "South", "East", "West", "Central"] -country_regions = [regions[i % 5] for i in range(n_countries)] - -# Generate time-series data for each country -data_frames = [] -for i, country in enumerate(countries): - base_gdp = np.random.uniform(5000, 40000) - base_life = np.random.uniform(55, 75) - base_pop = np.random.uniform(5, 200) # millions - - gdp_growth = np.random.uniform(0.02, 0.06) - life_improvement = np.random.uniform(0.2, 0.5) - pop_growth = np.random.uniform(0.005, 0.02) - - # Add some noise and variation - gdp_noise = np.cumsum(np.random.randn(n_years) * 500) - life_noise = np.cumsum(np.random.randn(n_years) * 0.3) - pop_noise = np.cumsum(np.random.randn(n_years) * 0.5) - - gdp = base_gdp * (1 + gdp_growth) ** np.arange(n_years) + gdp_noise - life_exp = base_life + life_improvement * np.arange(n_years) + life_noise - population = base_pop * (1 + pop_growth) ** np.arange(n_years) + pop_noise - - # Ensure positive values - gdp = np.maximum(gdp, 1000) - life_exp = np.clip(life_exp, 40, 90) - population = np.maximum(population, 1) - - for j, year in enumerate(years): - data_frames.append( - { - "country": country, - "region": country_regions[i], - "year": year, - "gdp_per_capita": gdp[j], - "life_expectancy": life_exp[j], - "population": population[j], - } - ) - -df = pd.DataFrame(data_frames) - -# Initial data (first year) -initial_year = years[0] -initial_data = df[df["year"] == initial_year].copy() - -# Create ColumnDataSource -source = ColumnDataSource( - data={ - "x": initial_data["gdp_per_capita"].values, - "y": initial_data["life_expectancy"].values, - "size": (initial_data["population"].values ** 0.5) * 5, - "country": initial_data["country"].values, - "region": initial_data["region"].values, - "population": initial_data["population"].values, - } -) - -# Store all data for animation -all_data = {} -for year in years: - year_data = df[df["year"] == year] - all_data[str(year)] = { - "x": year_data["gdp_per_capita"].tolist(), - "y": year_data["life_expectancy"].tolist(), - "size": [(p**0.5) * 5 for p in year_data["population"].values], - "country": year_data["country"].tolist(), - "region": year_data["region"].tolist(), - "population": year_data["population"].tolist(), - } - -# Define regions and color palette using Okabe-Ito -regions_list = ["North", "South", "East", "West", "Central"] -color_palette = IMPRINT - -# Create figure -p = figure( - width=4800, - height=2700, - title="scatter-animated-controls · bokeh · anyplot.ai", - x_axis_label="GDP per Capita (USD)", - y_axis_label="Life Expectancy (Years)", - x_range=(0, 80000), - y_range=(40, 95), - tools="pan,wheel_zoom,box_zoom,reset,save", -) - -# Style the figure with theme-adaptive colors -p.title.text_font_size = "28pt" -p.xaxis.axis_label_text_font_size = "22pt" -p.yaxis.axis_label_text_font_size = "22pt" -p.xaxis.major_label_text_font_size = "18pt" -p.yaxis.major_label_text_font_size = "18pt" - -# Theme-adaptive chrome -p.background_fill_color = PAGE_BG -p.border_fill_color = PAGE_BG -p.outline_line_color = INK_SOFT - -p.title.text_color = INK -p.xaxis.axis_label_text_color = INK -p.yaxis.axis_label_text_color = INK -p.xaxis.major_label_text_color = INK_SOFT -p.yaxis.major_label_text_color = INK_SOFT -p.xaxis.axis_line_color = INK_SOFT -p.yaxis.axis_line_color = INK_SOFT -p.xaxis.major_tick_line_color = INK_SOFT -p.yaxis.major_tick_line_color = INK_SOFT - -# Grid styling -p.xgrid.grid_line_color = INK -p.ygrid.grid_line_color = INK -p.xgrid.grid_line_alpha = 0.10 -p.ygrid.grid_line_alpha = 0.10 - -# Add margins -p.min_border_left = 120 -p.min_border_right = 120 -p.min_border_top = 100 -p.min_border_bottom = 100 - -# Add scatter plot -scatter = p.scatter( - x="x", - y="y", - size="size", - color=factor_cmap("region", palette=color_palette, factors=regions_list), - alpha=0.7, - line_color=PAGE_BG, - line_width=1, - source=source, - legend_field="region", -) - -# Configure legend -p.legend.location = "top_left" -p.legend.title = "Region" -p.legend.title_text_font_size = "18pt" -p.legend.label_text_font_size = "16pt" -p.legend.background_fill_color = ELEVATED_BG -p.legend.background_fill_alpha = 0.9 -p.legend.border_line_color = INK_SOFT -p.legend.label_text_color = INK_SOFT -p.legend.glyph_height = 30 -p.legend.glyph_width = 30 -p.legend.spacing = 12 -p.legend.padding = 15 -p.legend.margin = 20 - -# Add hover tool -hover = HoverTool( - tooltips=[ - ("Country", "@country"), - ("Region", "@region"), - ("GDP per Capita", "$@x{0,0}"), - ("Life Expectancy", "@y{0.1} years"), - ("Population", "@population{0.1} million"), - ], - renderers=[scatter], -) -p.add_tools(hover) - -# Add year label -year_label = Label( - x=70000, y=50, text=str(initial_year), text_font_size="150pt", text_color=INK, text_alpha=0.15, text_align="right" -) -p.add_layout(year_label) - -# Create slider -slider = Slider(start=int(years[0]), end=int(years[-1]), value=int(years[0]), step=1, title="Year", width=600) - -# Create play/pause button -button = Button(label="▶ Play", button_type="success", width=150) - -# JavaScript callback for slider -slider_callback = CustomJS( - args={"source": source, "all_data": all_data, "year_label": year_label}, - code=""" - const year = cb_obj.value.toString(); - const data = all_data[year]; - - source.data['x'] = data['x']; - source.data['y'] = data['y']; - source.data['size'] = data['size']; - source.data['country'] = data['country']; - source.data['region'] = data['region']; - source.data['population'] = data['population']; - source.change.emit(); - - year_label.text = year; -""", -) -slider.js_on_change("value", slider_callback) - -# JavaScript callback for play/pause button -button_callback = CustomJS( - args={"button": button, "slider": slider, "years_start": int(years[0]), "years_end": int(years[-1])}, - code=""" - if (button.label.includes('Play')) { - button.label = '⏸ Pause'; - button.button_type = 'warning'; - - // Start animation - window.animation_interval = setInterval(function() { - if (slider.value >= slider.end) { - slider.value = slider.start; - } else { - slider.value = slider.value + 1; - } - }, 500); - } else { - button.label = '▶ Play'; - button.button_type = 'success'; - - // Stop animation - if (window.animation_interval) { - clearInterval(window.animation_interval); - } - } -""", -) -button.js_on_click(button_callback) - -# Create title div -title_div = Div( - text=""" -
- Country Development Over Time -
-
- Bubble size represents population. Click Play to animate or drag the slider. -
-""", - width=1000, -) - -# Layout -controls = row(button, slider) -layout = column(title_div, controls, p) - -# Save HTML (interactive version with controls) -output_file(f"plot-{THEME}.html") -save(layout) - -# Screenshot with Selenium for PNG export -W, H = 4800, 2700 -opts = Options() -for arg in ( - "--headless=new", - "--no-sandbox", - "--disable-dev-shm-usage", - "--disable-gpu", - f"--window-size={W},{H}", - "--hide-scrollbars", -): - opts.add_argument(arg) -driver = webdriver.Chrome(options=opts) -driver.set_window_size(W, H) -driver.get(f"file://{Path(f'plot-{THEME}.html').resolve()}") -time.sleep(3) # let bokeh's JS render the canvas -driver.save_screenshot(f"plot-{THEME}.png") -driver.quit() diff --git a/plots/scatter-animated-controls/implementations/python/letsplot.py b/plots/scatter-animated-controls/implementations/python/letsplot.py deleted file mode 100644 index c52c9a7a3e..0000000000 --- a/plots/scatter-animated-controls/implementations/python/letsplot.py +++ /dev/null @@ -1,126 +0,0 @@ -# ruff: noqa: F405 -"""anyplot.ai -scatter-animated-controls: Animated Scatter Plot with Play Controls -Library: letsplot | Python 3.13 -Quality: pending | Created: 2025-12-31 -""" - -import os - -import numpy as np -import pandas as pd -from lets_plot import * # noqa: F403, F401 -from lets_plot.export import ggsave - - -LetsPlot.setup_html() - -# 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" - -# Okabe-Ito palette -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477"] - -# Data - Simulated country-level metrics over 20 years (Gapminder-style) -np.random.seed(42) - -countries = [ - "Northland", - "Eastoria", - "Westopia", - "Southaven", - "Centralia", - "Alpinia", - "Deltania", - "Oceanica", - "Valleysia", - "Highlands", -] -years = list(range(2000, 2020)) - -data_rows = [] -for idx, country in enumerate(countries): - # Base values with country-specific characteristics - base_gdp = np.random.uniform(5000, 40000) - base_life = np.random.uniform(55, 75) - base_pop = np.random.uniform(5, 200) # millions - - # Growth trends - gdp_growth = np.random.uniform(0.02, 0.06) - life_growth = np.random.uniform(0.002, 0.008) - pop_growth = np.random.uniform(0.005, 0.02) - - for i, year in enumerate(years): - # Add some noise and trends - gdp = base_gdp * (1 + gdp_growth) ** i * (1 + np.random.normal(0, 0.05)) - life_exp = min(85, base_life + life_growth * i * 100 + np.random.normal(0, 0.5)) - pop = base_pop * (1 + pop_growth) ** i - - data_rows.append( - { - "country": country, - "year": year, - "gdp_per_capita": gdp, - "life_expectancy": life_exp, - "population": pop, - "color_idx": idx % len(IMPRINT), - } - ) - -df = pd.DataFrame(data_rows) - -# lets-plot does not have built-in animation like Plotly -# Per spec: "Libraries without animation support should implement a static faceted version" -# Create a faceted view showing key time points - -# Select key years for faceted display -key_years = [2000, 2005, 2010, 2015, 2019] -df_key = df[df["year"].isin(key_years)].copy() -df_key["year_label"] = df_key["year"].astype(str) - -# Create color mapping based on Okabe-Ito palette -country_colors = {c: IMPRINT[i % len(IMPRINT)] for i, c in enumerate(countries)} - -# Theme configuration -anyplot_theme = theme( - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - panel_background=element_rect(fill=PAGE_BG), - panel_grid_major=element_line(color=INK, size=0.3), - panel_grid_minor=element_blank(), - axis_title=element_text(color=INK, size=20), - axis_text=element_text(color=INK_SOFT, size=16), - axis_line=element_line(color=INK_SOFT, size=0.5), - plot_title=element_text(color=INK, size=24, face="bold"), - legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT), - legend_text=element_text(color=INK_SOFT, size=16), - legend_title=element_text(color=INK, size=16), - strip_text=element_text(color=INK, size=18, face="bold"), - legend_position="right", -) - -# Create the faceted plot showing temporal evolution -plot = ( - ggplot(df_key, aes(x="gdp_per_capita", y="life_expectancy")) - + geom_point(aes(color="country", size="population"), alpha=0.85) - + scale_size(range=[6, 20], name="Population (M)") - + scale_color_manual(values=country_colors, name="Country") - + scale_x_log10() - + facet_wrap("year_label", ncol=5) - + labs( - title="scatter-animated-controls · letsplot · anyplot.ai", - x="GDP per Capita (log scale, USD)", - y="Life Expectancy (years)", - ) - + anyplot_theme - + ggsize(1600, 900) -) - -# Save as PNG (scale 3x for 4800x2700) -ggsave(plot, f"plot-{THEME}.png", path=".", scale=3) - -# Save as HTML for interactivity -ggsave(plot, f"plot-{THEME}.html", path=".") diff --git a/plots/scatter-animated-controls/implementations/python/matplotlib.py b/plots/scatter-animated-controls/implementations/python/matplotlib.py deleted file mode 100644 index 5a89c1cccb..0000000000 --- a/plots/scatter-animated-controls/implementations/python/matplotlib.py +++ /dev/null @@ -1,76 +0,0 @@ -""" anyplot.ai -scatter-animated-controls: Animated Scatter Plot with Play Controls -Library: matplotlib 3.10.9 | Python 3.13.13 -Quality: 91/100 | Created: 2026-05-15 -""" - -import os - -import matplotlib.pyplot as plt -import numpy as np - - -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" -BRAND = "#009E73" -ACCENT = "#C475FD" - -np.random.seed(42) -countries = 15 -years = 25 -years_data = np.arange(2000, 2025) - -gdp_per_capita = np.zeros((countries, years)) -life_expectancy = np.zeros((countries, years)) -population = np.zeros((countries, years)) -regions = np.random.choice(["Region A", "Region B", "Region C"], countries) - -for i in range(countries): - gdp_per_capita[i, 0] = np.random.uniform(5000, 45000) - life_expectancy[i, 0] = np.random.uniform(60, 82) - population[i, 0] = np.random.uniform(10, 300) - - for j in range(1, years): - gdp_per_capita[i, j] = gdp_per_capita[i, j - 1] * np.random.uniform(1.02, 1.08) - life_expectancy[i, j] = life_expectancy[i, j - 1] + np.random.uniform(-0.5, 1.0) - population[i, j] = population[i, j - 1] * np.random.uniform(0.98, 1.03) - -key_years_indices = [0, 6, 12, 18, 24] -key_years = years_data[key_years_indices] - -colors = [BRAND if region == "Region A" else ACCENT for region in regions] - -fig, axes = plt.subplots(1, 5, figsize=(20, 4), facecolor=PAGE_BG) -fig.patch.set_facecolor(PAGE_BG) - -for ax_idx, (year_idx, year) in enumerate(zip(key_years_indices, key_years, strict=True)): - ax = axes[ax_idx] - ax.set_facecolor(PAGE_BG) - - x = gdp_per_capita[:, year_idx] - y = life_expectancy[:, year_idx] - sizes = population[:, year_idx] * 50 - - ax.scatter(x, y, s=sizes, c=colors, alpha=0.6, edgecolors=PAGE_BG, linewidth=1) - - ax.set_xlabel("GDP per Capita ($)", fontsize=14, color=INK) - ax.set_ylabel("Life Expectancy (years)", fontsize=14, color=INK) - ax.set_title(str(year), fontsize=16, fontweight="medium", color=INK) - - ax.tick_params(axis="both", labelsize=12, colors=INK_SOFT) - ax.spines["top"].set_visible(False) - ax.spines["right"].set_visible(False) - ax.spines["left"].set_color(INK_SOFT) - ax.spines["bottom"].set_color(INK_SOFT) - ax.yaxis.grid(True, alpha=0.1, linewidth=0.8, color=INK) - - ax.set_xlim(0, 50000) - ax.set_ylim(55, 85) - -fig.suptitle("scatter-animated-controls · matplotlib · anyplot.ai", fontsize=18, fontweight="medium", color=INK, y=1.02) - -plt.tight_layout() -plt.savefig(f"plot-{THEME}.png", dpi=300, bbox_inches="tight", facecolor=PAGE_BG) diff --git a/plots/scatter-animated-controls/implementations/python/plotly.py b/plots/scatter-animated-controls/implementations/python/plotly.py deleted file mode 100644 index cf585109fb..0000000000 --- a/plots/scatter-animated-controls/implementations/python/plotly.py +++ /dev/null @@ -1,177 +0,0 @@ -""" anyplot.ai -scatter-animated-controls: Animated Scatter Plot with Play Controls -Library: plotly 6.7.0 | Python 3.13.13 -Quality: 95/100 | Updated: 2026-05-15 -""" - -import os - -import numpy as np -import pandas as pd -import plotly.express as px - - -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)" - -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233"] - -np.random.seed(42) - -countries = ["Alpha", "Beta", "Gamma", "Delta", "Epsilon", "Zeta", "Eta", "Theta"] -regions = ["North", "North", "South", "South", "East", "East", "West", "West"] -years = list(range(2000, 2021)) - -base_gdp = np.array([8000, 12000, 3000, 5000, 15000, 7000, 4000, 20000]) -base_life_exp = np.array([65, 72, 58, 62, 75, 68, 60, 78]) -base_pop = np.array([50, 30, 120, 80, 25, 45, 90, 20]) - -data = [] -for i, country in enumerate(countries): - for year_idx, year in enumerate(years): - growth_factor = 1 + np.random.uniform(0.01, 0.05) - gdp = base_gdp[i] * (growth_factor**year_idx) + np.random.normal(0, 500) - gdp = max(1000, gdp) - - life_exp = base_life_exp[i] + year_idx * np.random.uniform(0.1, 0.3) + np.random.normal(0, 0.5) - life_exp = min(max(50, life_exp), 85) - - pop_factor = 1 + np.random.uniform(0.005, 0.02) - pop = base_pop[i] * (pop_factor**year_idx) + np.random.normal(0, 2) - pop = max(10, pop) - - data.append( - { - "Country": country, - "Region": regions[i], - "Year": year, - "GDP per Capita ($)": gdp, - "Life Expectancy (years)": life_exp, - "Population (millions)": pop, - } - ) - -df = pd.DataFrame(data) - -fig = px.scatter( - df, - x="GDP per Capita ($)", - y="Life Expectancy (years)", - size="Population (millions)", - color="Region", - hover_name="Country", - animation_frame="Year", - animation_group="Country", - size_max=80, - range_x=[0, df["GDP per Capita ($)"].max() * 1.1], - range_y=[50, 88], - color_discrete_sequence=IMPRINT, -) - -fig.update_layout( - title=dict( - text="scatter-animated-controls · plotly · anyplot.ai", font=dict(size=28, color=INK), x=0.5, xanchor="center" - ), - xaxis=dict( - title=dict(text="GDP per Capita ($)", font=dict(size=22, color=INK)), - tickfont=dict(size=18, color=INK_SOFT), - gridcolor=GRID, - showgrid=True, - linecolor=INK_SOFT, - zerolinecolor=INK_SOFT, - ), - yaxis=dict( - title=dict(text="Life Expectancy (years)", font=dict(size=22, color=INK)), - tickfont=dict(size=18, color=INK_SOFT), - gridcolor=GRID, - showgrid=True, - linecolor=INK_SOFT, - zerolinecolor=INK_SOFT, - ), - legend=dict( - title=dict(text="Region", font=dict(size=20, color=INK)), - font=dict(size=18, color=INK_SOFT), - x=1.02, - y=0.98, - xanchor="left", - yanchor="top", - bgcolor=ELEVATED_BG, - bordercolor=INK_SOFT, - borderwidth=1, - ), - paper_bgcolor=PAGE_BG, - plot_bgcolor=PAGE_BG, - margin=dict(l=100, r=200, t=120, b=150), -) - -fig.update_traces(marker=dict(line=dict(width=2, color=PAGE_BG), opacity=0.8)) - -fig.update_layout( - updatemenus=[ - dict( - type="buttons", - showactive=False, - y=-0.12, - x=0.05, - xanchor="left", - buttons=[ - dict( - label="▶ Play", - method="animate", - args=[ - None, - dict( - frame=dict(duration=500, redraw=True), - fromcurrent=True, - transition=dict(duration=300, easing="cubic-in-out"), - ), - ], - ), - dict( - label="⏸ Pause", - method="animate", - args=[ - [None], - dict(frame=dict(duration=0, redraw=False), mode="immediate", transition=dict(duration=0)), - ], - ), - ], - font=dict(size=18, color=INK), - bgcolor=ELEVATED_BG, - bordercolor=INK_SOFT, - borderwidth=1, - ) - ], - sliders=[ - dict( - active=0, - yanchor="top", - xanchor="left", - currentvalue=dict(font=dict(size=24, color=INK), prefix="Year: ", visible=True, xanchor="center"), - transition=dict(duration=300, easing="cubic-in-out"), - pad=dict(b=10, t=50), - len=0.85, - x=0.1, - y=-0.06, - steps=[ - dict( - args=[ - [str(year)], - dict(frame=dict(duration=300, redraw=True), mode="immediate", transition=dict(duration=300)), - ], - label=str(year), - method="animate", - ) - for year in years - ], - font=dict(size=14, color=INK_SOFT), - ) - ], -) - -fig.write_image(f"plot-{THEME}.png", width=1600, height=900, scale=3) -fig.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn") diff --git a/plots/scatter-animated-controls/implementations/python/plotnine.py b/plots/scatter-animated-controls/implementations/python/plotnine.py deleted file mode 100644 index bbbb92534d..0000000000 --- a/plots/scatter-animated-controls/implementations/python/plotnine.py +++ /dev/null @@ -1,122 +0,0 @@ -""" anyplot.ai -scatter-animated-controls: Animated Scatter Plot with Play Controls -Library: plotnine 0.15.4 | Python 3.13.13 -Quality: 94/100 | Created: 2026-05-15 -""" - -import os - -import numpy as np -import pandas as pd -from plotnine import ( - aes, - element_blank, - element_line, - element_rect, - element_text, - facet_wrap, - geom_hline, - geom_path, - geom_point, - geom_vline, - ggplot, - ggsave, - labs, - scale_color_manual, - scale_size_continuous, - theme, - theme_minimal, -) - - -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" -REFERENCE_LINE = "#CCB8B0" if THEME == "light" else "#3A3935" - -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233"] - -np.random.seed(42) - -entities = [ - "Country A", - "Country B", - "Country C", - "Country D", - "Country E", - "Country F", - "Country G", - "Country H", - "Country I", - "Country J", -] -years = [2000, 2005, 2010, 2015, 2020, 2024] -regions = ["Asia", "Europe", "Americas", "Africa"] - -data_list = [] -for year in years: - for i, entity in enumerate(entities): - region = regions[i % len(regions)] - base_x = 20 + i * 6 - base_y = 50 + (i % 3) * 15 - x = base_x + np.random.normal(0, 2) + (year - 2000) * 0.3 - y = base_y + np.random.normal(0, 2.5) + (year - 2000) * 0.5 - pop = 50 + i * 5 + (year - 2000) * 0.8 + np.random.normal(0, 1) - data_list.append( - { - "year": year, - "entity": entity, - "region": region, - "gdp_per_capita": np.clip(x, 15, 85), - "life_expectancy": np.clip(y, 40, 90), - "population": np.clip(pop, 20, 120), - } - ) - -df = pd.DataFrame(data_list) - -# Calculate medians for reference lines (visual structure) -median_gdp = df["gdp_per_capita"].median() -median_life = df["life_expectancy"].median() - -plot = ( - ggplot(df, aes(x="gdp_per_capita", y="life_expectancy", color="region", size="population")) - + geom_vline(aes(xintercept=median_gdp), color=REFERENCE_LINE, size=0.5, alpha=0.4, linetype="dashed") - + geom_hline(aes(yintercept=median_life), color=REFERENCE_LINE, size=0.5, alpha=0.4, linetype="dashed") - + geom_path(aes(group="entity"), color=INK_SOFT, size=0.3, alpha=0.15, linetype="solid") - + geom_point(alpha=0.7, stroke=0.8) - + scale_color_manual(values=IMPRINT, name="Region") - + scale_size_continuous(range=(2.5, 9), name="Population\n(millions)") - + facet_wrap("~year", nrow=2, ncol=3) - + labs( - x="GDP per Capita (USD thousands)", - y="Life Expectancy (years)", - title="scatter-animated-controls · plotnine · anyplot.ai", - ) - + theme_minimal() - + theme( - figure_size=(16, 9), - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - panel_background=element_rect(fill=PAGE_BG), - panel_grid_major=element_line(color=INK, size=0.25, alpha=0.08), - panel_grid_minor=element_blank(), - panel_border=element_rect(color=INK_SOFT, fill=None, size=0.4), - panel_spacing_x=0.15, - panel_spacing_y=0.15, - axis_title=element_text(color=INK, size=20, weight="medium"), - axis_text=element_text(color=INK_SOFT, size=15), - axis_line=element_line(color=INK_SOFT, size=0.4), - plot_title=element_text(color=INK, size=26, weight="bold", margin={"t": 0, "b": 15}), - strip_text=element_text(color=INK, size=19, weight="bold"), - strip_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT, size=0.3), - legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT, size=0.4), - legend_text=element_text(color=INK_SOFT, size=15), - legend_title=element_text(color=INK, size=16, weight="bold"), - legend_position="right", - legend_margin=8, - ) -) - -ggsave(plot, filename=f"plot-{THEME}.png", dpi=300, width=16, height=9, verbose=False) diff --git a/plots/scatter-animated-controls/implementations/python/pygal.py b/plots/scatter-animated-controls/implementations/python/pygal.py deleted file mode 100644 index f2e6078f21..0000000000 --- a/plots/scatter-animated-controls/implementations/python/pygal.py +++ /dev/null @@ -1,426 +0,0 @@ -""" anyplot.ai -scatter-animated-controls: Animated Scatter Plot with Play Controls -Library: pygal 3.1.0 | Python 3.13.13 -Quality: 90/100 | Updated: 2026-05-15 -""" - -import io -import json -import os -import sys - - -# Remove current dir from path to avoid shadowing the pygal library -if "" in sys.path: - sys.path.remove("") -if "." in sys.path: - sys.path.remove(".") - -import cairosvg -import numpy as np -import pygal -from PIL import Image, ImageDraw, ImageFont -from pygal.style import Style - - -# Theme setup -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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -# Okabe-Ito palette (first series is brand green) -IMPRINT = ("#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477") - -# Data: Simulated country development metrics over years -np.random.seed(42) - -# 10 countries (different from altair's 12) tracked over annual years 2000-2020 (different sampling) -countries = [ - "Country A", - "Country B", - "Country C", - "Country D", - "Country E", - "Country F", - "Country G", - "Country H", - "Country I", - "Country J", -] -years = list(range(2000, 2021, 5)) # 5-year intervals: different from altair's 2000/2007/2014/2021 - -# Base values for each country -base_gdp = np.array([5, 8, 12, 15, 20, 25, 3, 10, 18, 30]) -base_life = np.array([55, 60, 65, 70, 72, 75, 50, 62, 68, 78]) -population = np.array([50, 80, 120, 45, 200, 30, 150, 90, 60, 25]) - -# Regions for color coding (3 categories) -regions = ["Asia", "Europe", "Asia", "Europe", "Africa", "Africa", "Americas", "Europe", "Africa", "Asia"] - -# Generate data for each year with different growth formula (0.20 instead of 0.15) -data_by_year = {} -pop_min, pop_max = population.min(), population.max() - -for year_idx, year in enumerate(years): - data_by_year[year] = {"Asia": [], "Europe": [], "Africa": [], "Americas": []} - for i, country in enumerate(countries): - growth_factor = 1 + year_idx * 0.20 + np.random.uniform(-0.05, 0.1) # Different coefficient - life_improvement = year_idx * 2.5 + np.random.uniform(-1, 2) - - gdp = base_gdp[i] * growth_factor - life_exp = min(85, base_life[i] + life_improvement) - pop = population[i] * (1 + year_idx * 0.02) - - # Calculate dot size based on population (scaled 12-48 for better visibility) - pop_normalized = (population[i] - pop_min) / (pop_max - pop_min) - dot_size = 12 + pop_normalized * 36 - - region = regions[i] - data_by_year[year][region].append( - { - "value": (round(gdp, 1), round(life_exp, 1)), - "node": {"r": dot_size}, - "label": f"{country}: GDP ${round(gdp, 1)}k, Life {round(life_exp, 1)}y, Pop {round(pop, 0)}M", - } - ) - -# Custom style with theme-adaptive colors -custom_style = Style( - background=PAGE_BG, - plot_background=PAGE_BG, - foreground=INK, - foreground_strong=INK, - foreground_subtle=INK_MUTED, - colors=IMPRINT, - title_font_size=28, - label_font_size=22, - major_label_font_size=18, - legend_font_size=18, - value_font_size=16, - tooltip_font_size=16, - stroke_width=3, -) - -# Region-color mapping using Okabe-Ito (only 4 colors for 4 regions) -region_colors = { - "Asia": IMPRINT[0], # #009E73 - brand green - "Europe": IMPRINT[1], # #C475FD - vermillion - "Africa": IMPRINT[2], # #4467A3 - blue - "Americas": IMPRINT[3], # #BD8233 - reddish purple -} - -# Create interactive HTML with slider control -html_template = """ - - - - scatter-animated-controls · pygal · anyplot.ai - - - -
-
scatter-animated-controls · pygal · anyplot.ai
-
Regional Development Metrics Over Time (Use Slider to Animate)
-
-
- Select Year - {initial_year} -
-
- {min_year} - - {max_year} -
-
-
- {charts_html} -
-
-
-
- Asia -
-
-
- Europe -
-
-
- Africa -
-
-
- Americas -
-
-
- - - -""" - -# Generate SVG for each year for HTML slider -charts_html_parts = [] - -for idx, year in enumerate(years): - year_chart = pygal.XY( - width=4800, - height=2700, - style=custom_style, - title=f"Year {year}", - x_title="GDP per Capita (thousands USD)", - y_title="Life Expectancy (years)", - show_x_guides=True, - show_y_guides=True, - dots_size=20, - stroke=False, - show_legend=False, - margin=120, - margin_top=180, - margin_bottom=150, - truncate_legend=-1, - range=(45, 90), - xrange=(0, 50), - ) - - # Add data series by region - for region in ["Asia", "Europe", "Africa", "Americas"]: - points = data_by_year[year][region] - year_chart.add(region, points) - - svg_content = year_chart.render().decode("utf-8") - active_class = "active" if idx == 0 else "" - charts_html_parts.append(f'
{svg_content}
') - -# Combine into final HTML -final_html = html_template.format( - initial_year=years[0], - min_year=years[0], - max_year=years[-1], - max_index=len(years) - 1, - charts_html="\n".join(charts_html_parts), - years_json=json.dumps(years), - page_bg=PAGE_BG, - elevated_bg=ELEVATED_BG, - ink=INK, - ink_soft=INK_SOFT, - ink_muted=INK_MUTED, -) - -with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f: - f.write(final_html) - -# Create PNG showing 2020 (most recent) as the static preview -png_chart = pygal.XY( - width=4800, - height=2700, - style=custom_style, - title="scatter-animated-controls · pygal · anyplot.ai", - x_title="GDP per Capita (thousands USD)", - y_title="Life Expectancy (years)", - show_x_guides=True, - show_y_guides=True, - dots_size=20, - stroke=False, - legend_at_bottom=True, - legend_at_bottom_columns=4, - margin=120, - margin_top=180, - margin_bottom=200, - truncate_legend=-1, - range=(45, 90), - xrange=(0, 50), - explicit_size=True, -) - -# Add data for 2020 (latest year) for PNG preview -for region in ["Asia", "Europe", "Africa", "Americas"]: - points = data_by_year[2020][region] - png_chart.add(region, points) - -# Render PNG and add year watermark -svg_data = png_chart.render() -png_bytes = cairosvg.svg2png(bytestring=svg_data, output_width=4800, output_height=2700) - -# Open as PIL image and add year watermark -img = Image.open(io.BytesIO(png_bytes)).convert("RGBA") -draw = ImageDraw.Draw(img) - -# Large year watermark in center-right of plot area -year_text = "2020" -try: - font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 400) -except OSError: - font = ImageFont.load_default() - -# Position: center-right area, semi-transparent -text_bbox = draw.textbbox((0, 0), year_text, font=font) -text_width = text_bbox[2] - text_bbox[0] -text_height = text_bbox[3] - text_bbox[1] -x_pos = 4800 - text_width - 300 -y_pos = (2700 - text_height) // 2 - 100 - -# Draw semi-transparent year watermark (theme-adaptive) -watermark = Image.new("RGBA", img.size, (255, 255, 255, 0)) -watermark_draw = ImageDraw.Draw(watermark) -if THEME == "light": - watermark_draw.text((x_pos, y_pos), year_text, font=font, fill=(0, 158, 115, 40)) -else: - watermark_draw.text((x_pos, y_pos), year_text, font=font, fill=(240, 239, 232, 40)) -img = Image.alpha_composite(img, watermark) - -# Save final PNG -img.convert("RGB").save(f"plot-{THEME}.png") diff --git a/plots/scatter-animated-controls/implementations/python/seaborn.py b/plots/scatter-animated-controls/implementations/python/seaborn.py deleted file mode 100644 index 0e87861341..0000000000 --- a/plots/scatter-animated-controls/implementations/python/seaborn.py +++ /dev/null @@ -1,134 +0,0 @@ -""" anyplot.ai -scatter-animated-controls: Animated Scatter Plot with Play Controls -Library: seaborn 0.13.2 | Python 3.13.13 -Quality: 81/100 | Created: 2026-05-15 -""" - -import os -import sys - -import matplotlib -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd - - -# Prioritize site-packages to avoid importing local seaborn.py file -_script_dir = os.path.dirname(os.path.abspath(__file__)) -sys.path[:] = [p for p in sys.path if "site-packages" in p] + [ - p for p in sys.path if "site-packages" not in p and os.path.abspath(p) != _script_dir -] -import seaborn as sns # noqa: E402 - - -# 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" - -BRAND = "#009E73" # Okabe-Ito position 1 -COLORS = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030"] - -# Data: Simulate country-level metrics evolving over time (Gapminder-inspired) -np.random.seed(42) - -countries = ["Country A", "Country B", "Country C", "Country D", "Country E"] -years = np.arange(2000, 2021, 5) # Key time points: 2000, 2005, 2010, 2015, 2020 - -data_list = [] -for country in countries: - base_gdp = np.random.uniform(5000, 30000) - base_life = np.random.uniform(65, 78) - - for year in years: - # GDP per capita grows over time with some randomness - gdp_per_capita = base_gdp * (1.03 ** (year - 2000)) + np.random.normal(0, 1000) - # Life expectancy improves over time - life_expectancy = base_life + (year - 2000) * 0.2 + np.random.normal(0, 0.5) - # Population proxy (for marker size) - population = np.random.uniform(20, 100) - - data_list.append( - { - "Year": year, - "Country": country, - "GDP per Capita ($1000s)": gdp_per_capita / 1000, - "Life Expectancy (years)": life_expectancy, - "Population Proxy": population, - } - ) - -df = pd.DataFrame(data_list) - -# Create faceted plot showing temporal evolution -sns.set_style("whitegrid") -sns.set_context("talk", font_scale=1.2) - -# Configure theme colors -matplotlib.rcParams.update( - { - "figure.facecolor": PAGE_BG, - "axes.facecolor": PAGE_BG, - "axes.edgecolor": INK_SOFT, - "axes.labelcolor": INK, - "text.color": INK, - "xtick.color": INK_SOFT, - "ytick.color": INK_SOFT, - "grid.color": INK, - "grid.alpha": 0.10, - "grid.linewidth": 0.8, - "legend.facecolor": ELEVATED_BG, - "legend.edgecolor": INK_SOFT, - } -) - -# Use relplot for faceted scatter with automatic data handling -g = sns.relplot( - data=df, - x="GDP per Capita ($1000s)", - y="Life Expectancy (years)", - hue="Country", - col="Year", - col_wrap=3, - palette=COLORS, - height=4.3, - aspect=1.05, - s=150, - alpha=0.8, - edgecolor="face", - linewidths=0.5, -) - -# Set axis labels and limits for consistency -g.set_axis_labels("GDP per Capita ($1000s)", "Life Expectancy (years)", fontsize=14) -for ax in g.axes.flat: - ax.set_xlim(5, 45) - ax.set_ylim(64, 82) - ax.tick_params(labelsize=12, colors=INK_SOFT) - ax.set_xlabel("GDP per Capita ($1000s)", fontsize=14, color=INK) - ax.set_ylabel("Life Expectancy (years)", fontsize=14, color=INK) - ax.spines["top"].set_visible(False) - ax.spines["right"].set_visible(False) - for spine in ["left", "bottom"]: - ax.spines[spine].set_color(INK_SOFT) - -# Configure legend -g._legend.set_title("Country") -g._legend.get_frame().set_facecolor(ELEVATED_BG) -g._legend.get_frame().set_edgecolor(INK_SOFT) -g._legend.get_title().set_color(INK) -g._legend.get_title().set_fontsize(13) -for text in g._legend.get_texts(): - text.set_color(INK) - text.set_fontsize(12) - -# Main title -fig = g.figure -fig.suptitle("scatter-animated-controls · seaborn · anyplot.ai", fontsize=20, fontweight="medium", color=INK, y=0.98) -fig.patch.set_facecolor(PAGE_BG) - -plt.tight_layout(rect=[0, 0, 1, 0.96]) -plt.savefig(f"plot-{THEME}.png", dpi=300, bbox_inches="tight", facecolor=PAGE_BG) -plt.close() diff --git a/plots/scatter-animated-controls/metadata/python/altair.yaml b/plots/scatter-animated-controls/metadata/python/altair.yaml deleted file mode 100644 index 3a02cd40a8..0000000000 --- a/plots/scatter-animated-controls/metadata/python/altair.yaml +++ /dev/null @@ -1,240 +0,0 @@ -library: altair -language: python -specification_id: scatter-animated-controls -created: '2025-12-31T13:51:00Z' -updated: '2026-05-15T11:24:52Z' -generated_by: claude-haiku -workflow_run: 25914455601 -issue: 3067 -python_version: 3.13.13 -library_version: 6.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/altair/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/altair/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/altair/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/altair/plot-dark.html -quality_score: 91 -review: - strengths: - - 'Perfect visual quality: all text readable in both themes with proper sizing and - colors' - - 'Excellent spec compliance: valid static alternative to animation with effective - temporal visualization through faceting' - - 'Strong code quality: clean structure, reproducible with seed, idiomatic altair - usage' - - 'Proper theme-adaptive implementation: identical data colors across themes with - correct chrome adaptation' - - Well-contextualized realistic data showing all required features across time periods - - Sophisticated faceting approach to temporal evolution with proper color and size - encoding - weaknesses: - - 'Design Excellence moderate: faceting is good but mostly relies on library defaults; - could benefit from stronger visual storytelling or distinctive visual hierarchy - to emphasize key insights' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1 (not pure white, not dark) ✓ - Chrome: Title "scatter-animated-controls · altair · anyplot.ai" clearly visible; subtitle "Country Development Metrics — Evolution Across Key Years" visible; axis labels with units; tick labels all readable in dark INK color - Data: Five faceted panels showing years 2000, 2005, 2010, 2015, 2020; colors green #009E73 (Region 1), orange #D55E00 (Region 2), blue #0072B2 (Region 3) in Okabe-Ito order; marker sizes encode population; all elements clearly distinguishable - Legibility verdict: PASS — all text is readable, data is clear, layout is well-proportioned - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 (not pure black, not light) ✓ - Chrome: Title and subtitle clearly visible in light INK color; axis labels and tick labels all readable in light INK_SOFT color; no dark-on-dark failures - Data: Data colors are IDENTICAL to light render (same green #009E73, orange #D55E00, blue #0072B2) ✓ confirming only chrome adaptation; five faceted panels render with identical composition - Legibility verdict: PASS — all text is readable, data colors match light render perfectly, theme adaptation is correct - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: 'All font sizes explicitly set: title 32px, subtitle 22px, labels - 20-22px, ticks 14-18px; perfectly readable in both themes' - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: Year labels clear; axis labels readable; tick labels don't overlap; - no collisions - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: 60 data points per facet well-adapted; opacity 0.7 with strokeWidth - 1.5 ensures definition; size range 100-2500 optimal - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette CVD-safe; good contrast between data and both backgrounds - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Five faceted panels well-proportioned; balanced margins; good canvas - utilization (50-80% of canvas) - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: 'Descriptive with units: GDP per Capita (thousands USD), Life Expectancy - (years)' - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73; multi-series follows Okabe-Ito order; backgrounds - #FAF8F1 (light) and #1A1A17 (dark); theme-adaptive chrome perfect' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: false - comment: Thoughtful Okabe-Ito palette; explicit font sizing; custom legend - styling; faceting is sophisticated but mostly well-configured defaults - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: false - comment: Grid subtly styled (opacity 0.10); legend professionally customized; - axis configuration refined; some customization visible but not exceptional - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: false - comment: Faceting guides temporal progression; color and size encoding add - dimensions; shows evolution but no particular insight emphasis - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct scatter plot; valid static faceted alternative per spec - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: X-axis (GDP), Y-axis (Life Expectancy), size (Population), color - (Region), time (faceted years) all present - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X/Y correctly assigned; scale domains appropriate; all data visible - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title format correct; legend labels match data - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Shows all aspects across time periods; GDP, life expectancy, population - all vary; regions show different trajectories - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Plausible country development metrics; realistic growth patterns; - sensible region-based grouping - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: GDP 5-30k USD realistic; life expectancy 50-85 years plausible; population - 20-200M factually sound - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear flow: imports → data → plot → save; no functions or classes' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Uses np.random.seed(42); deterministic - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports used; no unnecessary dependencies - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Pythonic; no fake functionality; appropriate complexity - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png and .html; current API - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: 'Expert altair API usage: Chart().mark_circle().encode().facet().properties().configure_*() - chain; proper encoding; theme adaptation' - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: false - comment: Uses faceting which is distinctive; good use of Scale, Legend, Tooltip; - but faceting is fairly standard altair - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - faceting - - html-export - patterns: - - data-generation - dataprep: [] - styling: - - edge-highlighting - - grid-styling diff --git a/plots/scatter-animated-controls/metadata/python/bokeh.yaml b/plots/scatter-animated-controls/metadata/python/bokeh.yaml deleted file mode 100644 index 671f0417ee..0000000000 --- a/plots/scatter-animated-controls/metadata/python/bokeh.yaml +++ /dev/null @@ -1,237 +0,0 @@ -library: bokeh -language: python -specification_id: scatter-animated-controls -created: '2025-12-31T13:53:51Z' -updated: '2026-05-15T11:08:00Z' -generated_by: claude-haiku -workflow_run: 25914375536 -issue: 3067 -python_version: 3.13.13 -library_version: 3.9.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/bokeh/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/bokeh/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/bokeh/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/bokeh/plot-dark.html -quality_score: 95 -review: - strengths: - - Perfect palette compliance with Okabe-Ito ordering and brand green (#009E73) as - first series - - Excellent theme-adaptive chrome throughout - all text colors, grid colors, and - backgrounds properly adapt to light/dark while data colors remain identical - - 'Comprehensive interactivity fully implemented: play/pause button, timeline slider - with smooth 500ms animation, and hover tooltips with formatted metrics' - - 'Idiomatic bokeh usage showcasing mastery: ColumnDataSource pattern, CustomJS - callbacks for reactive updates, HoverTool with semantic formatting' - - Both light and dark renders equally readable with no legibility issues - no dark-on-dark - or light-on-light failures detected - - Clean, reproducible code with proper Selenium screenshot approach per bokeh.md - guidelines, deterministic data generation, and no fake UI elements - weaknesses: [] - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) - correct theme surface - Chrome: Title "scatter-animated-controls · bokeh · anyplot.ai" in dark ink (#1A1A17), axis labels "GDP per Capita (USD)" and "Life Expectancy (Years)" clearly visible, tick labels in secondary ink (#4A4A44), legend with light elevated background (#FFFDF6) - Data: 15 scatter points in Okabe-Ito colors - #009E73 (green, North region dominant), #D55E00 (orange), #0072B2 (blue), #CC79A7 (pink), #E69F00 (yellow). Bubble sizes vary from small to large representing population. Grid lines subtle at 10% opacity. - Year label: "2004" in lower-right corner at light gray with 15% opacity - Legibility verdict: PASS - all text, labels, and data elements fully readable on light background - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) - correct theme surface - Chrome: Title and axis labels in light ink (#F0EFE8), tick labels in secondary light ink (#B8B7B0), legend with dark elevated background (#242420). All text readable against dark background. - Data: Identical Okabe-Ito colors to light render - data consistency confirmed. Bubble sizes maintain proportional visibility. Grid lines subtle and visible. - Year label: "2004" visible in lower-right corner at reduced opacity, appropriate for background element - Legibility verdict: PASS - all text readable in dark theme, no dark-on-dark failures, data colors identical to light render - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All text clearly readable in both light and dark themes with proper - theme-adaptive colors - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: Scatter points well-separated with no colliding labels; tooltip layout - clean - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: All 15 data points clearly visible with good size variation; no obscurement - despite overlapping positions - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette is colorblind-safe; adequate contrast between markers - and background - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Excellent proportions with generous margins (120px left/right, 100px - top/bottom); nothing cut off - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title follows spec format; axes descriptive with units (USD, Years) - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73, Okabe-Ito order maintained, correct backgrounds, - both themes correct' - design_excellence: - score: 15 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 6 - max: 8 - passed: true - comment: Okabe-Ito palette used thoughtfully with clear visual hierarchy; - professional appearance - - id: DE-02 - name: Visual Refinement - score: 5 - max: 6 - passed: true - comment: Legend styled with theme-adaptive colors, proper spacing; grid subtle - at 10% opacity - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Animation concept tells temporal story; year label provides context - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct scatter plot with size and color encodings - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Play/pause, slider, year display, hover tooltips all implemented - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X=GDP, Y=life expectancy, size=population, color=region correctly - mapped - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Proper title format and legend labels - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 15 entities across 20 years with multiple encodings - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Gapminder-inspired data; plausible values; neutral framing - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: All ranges sensible for domain (GDP, life expectancy, population) - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: No unnecessary abstractions; linear procedural flow - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set for deterministic generation - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports used appropriately - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Appropriate complexity; idiomatic CustomJS; no fake UI - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Correct output format with both HTML and PNG - library_mastery: - score: 10 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: ColumnDataSource, factor_cmap, CustomJS callbacks, Selenium screenshot - per bokeh.md - - id: LM-02 - name: Distinctive Features - score: 5 - max: 5 - passed: true - comment: 'Full interactive model: HoverTool, Slider, Button with JavaScript - integration' - verdict: APPROVED -impl_tags: - dependencies: - - selenium - techniques: - - hover-tooltips - - html-export - patterns: - - data-generation - - columndatasource - dataprep: [] - styling: - - alpha-blending diff --git a/plots/scatter-animated-controls/metadata/python/letsplot.yaml b/plots/scatter-animated-controls/metadata/python/letsplot.yaml deleted file mode 100644 index f14b6a295d..0000000000 --- a/plots/scatter-animated-controls/metadata/python/letsplot.yaml +++ /dev/null @@ -1,234 +0,0 @@ -library: letsplot -language: python -specification_id: scatter-animated-controls -created: '2025-12-31T13:53:17Z' -updated: '2026-05-15T11:26:48Z' -generated_by: claude-haiku -workflow_run: 25914768409 -issue: 3067 -python_version: 3.13.13 -library_version: 4.9.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/letsplot/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/letsplot/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/letsplot/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/letsplot/plot-dark.html -quality_score: 83 -review: - strengths: - - Clean implementation of temporal narrative through faceted view - effectively - shows 20-year progression - - 'Perfect theme-adaptive styling: correct Okabe-Ito palette identical in both light - and dark renders; only chrome flips' - - 'Text legibility exceptional: no dark-on-dark or light-on-light failures; all - text clearly readable in both themes' - - Realistic, feature-rich data with good coverage of temporal variation - - Smart interpretation of spec's fallback requirement for non-interactive libraries - - 'No layout issues: balanced margins, no overlaps, good canvas utilization' - weaknesses: - - Design feels generic - uses library defaults without distinctive visual choices - or polish - - Grid styling not refined - could be more subtle (lighter, dashed, or reduced opacity) - - Visual refinement minimal - spines present on all sides; minimal attention to - aesthetic detail - - Limited visual hierarchy - while temporal progression tells a story, no additional - emphasis or focal points - - Library Mastery average - faceting is useful but not distinctive to letsplot; - doesn't leverage library's strengths - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) - correct theme-light surface - Chrome: Title "scatter-animated-controls · letsplot · anyplot.ai" in dark text (readable). Axis labels "GDP per Capita (log scale, USD)" and "Life Expectancy (years)" clearly visible. Tick labels readable. Facet headers "2000", "2005", "2010", "2015", "2019" clearly visible. - Data: 10 countries rendered in Okabe-Ito colors starting with brand green (#009E73), followed by orange, blue, purple, yellow, sky blue, pink, gray shades. Population variation shown as point size (6-20 range). Log scale on X-axis. All data points distinguishable. - Legend: Right-side legend shows country names with color swatches and population size reference scale. Light text on elevated background (#FFFDF6) - all readable. - Legibility verdict: PASS - all text readable, no overlaps, excellent contrast - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) - correct theme-dark surface - Chrome: Title in light text (F0EFE8) - clearly readable. Axis labels in light text - readable. Tick labels in light-soft tone (B8B7B0) - readable. Facet headers clearly visible. No dark-on-dark failures. - Data: Same colors as light render - green (#009E73), orange, blue, purple, yellow, sky blue, pink, grays. Colors are IDENTICAL to light render (perfect - only chrome changed). Point sizes identical to light render. - Legend: Right-side legend with light text on elevated dark background (#242420) - all readable. No dark-on-dark issues. - Legibility verdict: PASS - all text readable, no dark-on-dark failures, data colors identical to light render - criteria_checklist: - visual_quality: - score: 29 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: 'Font sizes explicitly set; all readable in both themes; minor: legend - text could be slightly larger' - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No text overlaps; faceted layout provides ample space - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Markers optimally sized with clear population variation; all points - distinguishable - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette is colorblind-safe; good contrast in both themes - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Perfect balance; facets fill canvas well; legend positioned appropriately - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Descriptive labels with units - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73; other colors follow Okabe-Ito; backgrounds - correct; text colors theme-correct in both renders' - design_excellence: - score: 10 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: false - comment: Generic styling with defaults; technically correct but not distinctive - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: false - comment: Minimal refinement; grid visible but not subtle; spines present on - all sides - - id: DE-03 - name: Data Storytelling - score: 3 - max: 6 - passed: false - comment: Temporal progression creates narrative; limited additional visual - hierarchy - spec_compliance: - score: 13 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 4 - max: 5 - passed: true - comment: Scatter plot correct; appropriately implements static faceted fallback - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Temporal evolution shown; color and size encoding present; missing - interactive controls (justified) - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X, Y, Color, Size all correctly mapped - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title format and legend labels correct - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 10 countries with varied trajectories across 20 years - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Gapminder-style scenario; real-world plausible - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: All values factually correct for the domain - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Clean structure; no unnecessary functions - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only used imports - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Pythonic; no fake UI - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Correct output format - library_mastery: - score: 6 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: ggplot composition is idiomatic; faceting for temporal workaround - - id: LM-02 - name: Distinctive Features - score: 2 - max: 5 - passed: false - comment: facet_wrap is useful but generic; limited library-specific leverage - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - faceting - patterns: - - data-generation - dataprep: [] - styling: [] diff --git a/plots/scatter-animated-controls/metadata/python/matplotlib.yaml b/plots/scatter-animated-controls/metadata/python/matplotlib.yaml deleted file mode 100644 index 088313e5bd..0000000000 --- a/plots/scatter-animated-controls/metadata/python/matplotlib.yaml +++ /dev/null @@ -1,231 +0,0 @@ -library: matplotlib -language: python -specification_id: scatter-animated-controls -created: '2026-05-15T10:59:48Z' -updated: '2026-05-15T11:02:26Z' -generated_by: claude-haiku -workflow_run: 25914181540 -issue: 3067 -python_version: 3.13.13 -library_version: 3.10.9 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/matplotlib/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/matplotlib/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 91 -review: - strengths: - - 'Perfect visual quality: all text readable in both themes, no overlap, excellent - layout balance' - - Effective faceted design as legitimate matplotlib alternative to animation - - 'Exemplary theme adaptation: chrome flips correctly while data colors remain constant' - - 'Strong palette compliance: Okabe-Ito colors precise in both renders' - - Realistic, well-chosen example data (Gapminder-inspired) - - Clean, reproducible code following best practices - weaknesses: [] - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) with no pure-white appearance. - Chrome: Title "scatter-animated-controls · matplotlib · anyplot.ai" is dark-colored and clearly readable. Axis labels ("GDP per Capita ($)", "Life Expectancy (years)") and tick labels all in dark ink (#1A1A17). Grid lines subtle (y-axis only, alpha=0.1) and not competing with data. - Data: Five faceted scatter plots (2000, 2006, 2012, 2018, 2024). Primary series colored #009E73 (brand green), secondary series #D55E00 (orange). Bubble sizes vary to encode population. All markers clearly visible and well-sized. - Legibility verdict: PASS — all elements fully readable against light background. - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) with no pure-black appearance. - Chrome: Title, axis labels, and tick labels all light-colored (#F0EFE8, #B8B7B0) and fully readable against dark surface. No dark-on-dark failures. Grid lines theme-adapted. All text is clearly distinguishable. - Data: Identical scatter composition to light render. Data colors remain #009E73 (green) and #D55E00 (orange) — no color shift. Only chrome flipped, data identity preserved. - Legibility verdict: PASS — all elements fully readable against dark background; no theme-adaptation failures. - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All sizes explicitly set (title 18pt, labels 14pt, ticks 12pt); fully - readable in both themes - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text elements; clean faceted layout - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Markers optimally sized; bubble variation clearly visible - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Strong contrast; Okabe-Ito palette is CVD-safe - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Perfect balance; 5-panel grid uses canvas effectively - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: 'Descriptive with units: GDP per Capita ($), Life Expectancy (years)' - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73 ✓; secondary #D55E00 ✓; light BG #FAF8F1 ✓; - dark BG #1A1A17 ✓; theme-adaptive chrome throughout' - design_excellence: - score: 14 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 6 - max: 8 - passed: true - comment: Clean, intentional color choices; effective temporal progression; - above generic defaults - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Spines removed, subtle grid, balanced whitespace; good attention - to detail - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Faceted layout guides viewer through 25-year evolution; visual hierarchy - clear - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct scatter plot type; small multiples is appropriate static - alternative for animation - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: X (GDP), Y (life expectancy), time (5 key years), size (population), - color (region) all present; individual entity labels omitted due to space - constraints - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X/Y correctly assigned; axes show full data range - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title format correct; legend omitted (appropriate for 2-category - color scheme) - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'Shows all aspects: GDP variation, life expectancy range, population - differences across regions and years' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Gapminder-inspired real-world scenario; plausible country-level metrics - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Values realistic and factually sound; proportions follow logical - constraints - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear flow: imports → data → plot → save; no functions/classes' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Seed set (np.random.seed(42)) - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only os, matplotlib, numpy; all used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean, Pythonic, no fake UI - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Correct output format (plot-{THEME}.png) - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: Follows matplotlib.md patterns; theme-aware tokens; proper Axes API - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Effective use of subplots as animation alternative; synchronized - faceting - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - subplots - - faceting - patterns: - - data-generation - - matrix-construction - dataprep: [] - styling: - - alpha-blending - - grid-styling diff --git a/plots/scatter-animated-controls/metadata/python/plotly.yaml b/plots/scatter-animated-controls/metadata/python/plotly.yaml deleted file mode 100644 index 38630da0d8..0000000000 --- a/plots/scatter-animated-controls/metadata/python/plotly.yaml +++ /dev/null @@ -1,219 +0,0 @@ -library: plotly -language: python -specification_id: scatter-animated-controls -created: '2025-12-31T13:51:22Z' -updated: '2026-05-15T11:06:08Z' -generated_by: claude-haiku -workflow_run: 25914294002 -issue: 3067 -python_version: 3.13.13 -library_version: 6.7.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/plotly/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/plotly/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/plotly/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/plotly/plot-dark.html -quality_score: 95 -review: - strengths: - - Excellent theme adaptation with all chrome elements using proper theme tokens - - Perfect legibility in both light and dark renders with no readability failures - - Complete animation implementation with polished play/pause and slider controls - - 'Proper Okabe-Ito palette usage with #009E73 as first series across all regions' - - White marker edges and opacity styling creates excellent visual clarity - - Clean, reproducible code with deterministic data generation (np.random.seed) - - All specification requirements fully implemented (animation, controls, data encoding) - - Realistic synthetic data that effectively demonstrates economic development patterns - weaknesses: [] - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) - correct light theme surface - Chrome: Title, axis labels (GDP per Capita, Life Expectancy), and tick labels (0, 5k, 10k... on X; 50, 55... on Y) all clearly visible in dark text (#1A1A17). Grid lines subtle but visible. Legend with Region labels and color swatches visible on right. Play/Pause buttons and year slider at bottom are readable. - Data: Four regions color-coded (North=#009E73 green, South=#D55E00 orange, East=#0072B2 blue, West=#CC79A7 pink). Markers have white edges and 0.8 opacity for definition. Year 2000 displayed prominently below plot. - Legibility verdict: PASS - All text and elements are clearly readable with proper contrast - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) - correct dark theme surface - Chrome: Title, axis labels, and tick labels all visible in light text (#F0EFE8). Grid lines subtle. Legend visible with light text. Play/Pause buttons and slider visible with light track. - Data: Marker colors identical to light render (#009E73, #D55E00, #0072B2, #CC79A7) - confirming data colors don't flip with theme. White edges still visible on dark background. Year 2000 readable. - Legibility verdict: PASS - No dark-on-dark failures. All text readable. Brand green (#009E73) clearly visible. Perfect theme adaptation with only chrome elements changing color. - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All font sizes explicitly set; readable in both themes - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: Markers well-spaced, no overlapping text - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Markers clearly visible with white edge definition - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette is colorblind-safe - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Good proportions, proper margins, nothing cut off - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Descriptive with units - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73, Okabe-Ito order maintained, backgrounds correct' - design_excellence: - score: 17 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 7 - max: 8 - passed: true - comment: Polished design with intentional marker styling and theme-aware colors - - id: DE-02 - name: Visual Refinement - score: 5 - max: 6 - passed: true - comment: Clean appearance with white marker edges and generous whitespace - - id: DE-03 - name: Data Storytelling - score: 5 - max: 6 - passed: true - comment: Animation shows economic development patterns effectively - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct animated scatter plot - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Animation, play/pause, slider, time display all present - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X/Y correct, axes show all data - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Correct format and labels - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: All plot dimensions represented - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Realistic country metrics - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Sensible ranges for domain - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Simple, direct implementation - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Uses np.random.seed(42) - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only used imports - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Appropriate complexity, no fake functionality - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Correct file naming - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: Proper use of plotly.express animation API - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Uses Plotly animation features effectively - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - hover-tooltips - - html-export - patterns: - - data-generation - dataprep: [] - styling: - - edge-highlighting - - alpha-blending diff --git a/plots/scatter-animated-controls/metadata/python/plotnine.yaml b/plots/scatter-animated-controls/metadata/python/plotnine.yaml deleted file mode 100644 index ef23f9ebce..0000000000 --- a/plots/scatter-animated-controls/metadata/python/plotnine.yaml +++ /dev/null @@ -1,223 +0,0 @@ -library: plotnine -language: python -specification_id: scatter-animated-controls -created: '2026-05-15T11:09:05Z' -updated: '2026-05-15T11:18:49Z' -generated_by: claude-haiku -workflow_run: 25914535134 -issue: 3067 -python_version: 3.13.13 -library_version: 0.15.4 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/plotnine/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/plotnine/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 94 -review: - strengths: - - Perfect technical execution across all visual quality and compliance criteria - - Sophisticated theme-adaptive design with intentional color coordination and typography - - Intelligent use of faceting to represent temporal dimension in static library - - Strong data storytelling through multiple visual encodings (color, size, position, - trajectory paths) - - Clean, reproducible code with expert grammar-of-graphics patterns - - Excellent legibility in both light and dark themes with zero theme-chrome failures - weaknesses: [] - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) with perfect theme adaptation - Chrome: Title, axis labels, tick labels all in dark text (#1A1A17) and soft gray (#4A4A44), completely readable - Data: Six faceted scatter panels showing years 2000-2024, colored by region (Okabe-Ito palette), sized by population, with reference lines and faint trajectory paths - Legibility verdict: PASS - All elements perfectly readable against light background - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) with perfect theme adaptation - Chrome: Title, axis labels, tick labels in light text (#F0EFE8) and soft light gray (#B8B7B0), completely readable; no dark-on-dark failures - Data: Identical data representation to light render; data colors unchanged (#009E73, #D55E00, #0072B2, #CC79A7); only chrome has flipped colors - Legibility verdict: PASS - All elements perfectly readable against dark background; both renders fully theme-correct - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All font sizes explicitly set and perfectly readable in both renders - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text elements; all properly spaced - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: 60 points across 6 facets clearly visible; size range optimally adapted - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette with CVD-safe colors and good contrast - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 4800×2700 canvas, 70-75% utilization, balanced margins - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: All labels include units; correct title format - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Perfect Okabe-Ito with first series #009E73; backgrounds correct; - theme chrome perfectly adaptive' - design_excellence: - score: 16 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 6 - max: 8 - passed: true - comment: Sophisticated color coordination, intentional typography, reference - lines add analytical depth - - id: DE-02 - name: Visual Refinement - score: 5 - max: 6 - passed: true - comment: Subtle grid styling, polished borders, generous whitespace, elevated - backgrounds - - id: DE-03 - name: Data Storytelling - score: 5 - max: 6 - passed: true - comment: Strong temporal narrative with multiple encodings guiding interpretation - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct scatter type with appropriate static faceting alternative - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: All spec features present including trails and temporal representation - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X, Y, size, and color correctly mapped - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title format correct; legend labels accurate - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'All plot aspects shown: 10 entities, 6 years, 4 regions, full ranges' - - id: DQ-02 - name: Realistic Context - score: 4 - max: 5 - passed: true - comment: Plausible Gapminder-inspired scenario; generic labels acceptable - for demo - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Factually plausible values with realistic relationships - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Clean linear structure without unnecessary functions - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) ensures deterministic output - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports used; no extraneous dependencies - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Pythonic, readable, no fake functionality - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Correct plot-{THEME}.png output with proper dimensions - library_mastery: - score: 9 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: Expert grammar of graphics patterns; facet_wrap idiomatically solves - small multiples - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: Layer composition and custom theme tokens showcase plotnine strengths - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - faceting - - layer-composition - patterns: - - data-generation - dataprep: [] - styling: - - alpha-blending - - grid-styling diff --git a/plots/scatter-animated-controls/metadata/python/pygal.yaml b/plots/scatter-animated-controls/metadata/python/pygal.yaml deleted file mode 100644 index 2016c5f016..0000000000 --- a/plots/scatter-animated-controls/metadata/python/pygal.yaml +++ /dev/null @@ -1,236 +0,0 @@ -library: pygal -language: python -specification_id: scatter-animated-controls -created: '2025-12-31T21:37:20Z' -updated: '2026-05-15T11:20:10Z' -generated_by: claude-haiku -workflow_run: 25914613510 -issue: 3067 -python_version: 3.13.13 -library_version: 3.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/pygal/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/pygal/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/pygal/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/pygal/plot-dark.html -quality_score: 90 -review: - strengths: - - Perfect visual quality with all text legible in both themes and no overlap - - Flawless theme adaptation—data colors identical between light/dark, only chrome - flips appropriately - - Complete spec implementation with both static PNG preview and interactive HTML - timeline slider - - Realistic, well-structured data showing 10 countries across 4 regions with plausible - economic and health metrics - - Clean, reproducible code with clear structure, deterministic seeding, and justified - imports - - Creative implementation combining pygal rendering with custom HTML/JavaScript - and PIL watermarking - weaknesses: - - Design Excellence could be stronger—uses configured defaults rather than custom - refinements like custom palettes or unusual typography - - Data storytelling is straightforward—while regional comparison is clear, temporal - patterns could be emphasized more through visual hierarchy - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct for light theme - Chrome: Title "scatter-animated-controls · pygal · anyplot.ai" in dark text, axis labels "GDP per Capita (thousands USD)" and "Life Expectancy (years)" with units, tick labels (0, 10, 20... x-axis; 50, 60, 70... y-axis) all clearly readable in dark gray; legend at bottom shows regions (Asia, Europe, Africa, Americas) with matching colors; grid lines subtle and unobtrusive - Data: Scatter points using Okabe-Ito palette (Asia=#009E73 green, Europe=#D55E00 orange, Africa=#0072B2 blue, Americas=#CC79A7 reddish-purple); point sizes vary based on population; "2020" watermark in semi-transparent green on right side adds temporal context - Legibility verdict: PASS — all text readable, no overlap, proper contrast - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct for dark theme - Chrome: Title and axis labels in light cream color, all readable; tick labels light-colored and visible; grid lines light-colored and subtle; legend clear and identifiable - Data: Data colors (green, orange, blue, reddish-purple) are IDENTICAL to light render — confirms proper theme adaptation; "2020" watermark in light gray for dark background visibility - Legibility verdict: PASS — all text readable in light colors against dark background; no dark-on-dark failures; theme adaptation flawless - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All font sizes explicitly set (title=28px, labels=22px, ticks=18px); - perfect readability in both themes - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text elements; all content fully readable - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Markers well-sized and adapted to data density; size variation (12-48px) - based on population visible - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette is colorblind-safe; good contrast between all series - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Balanced margins (120-200px); plot fills 50-80% of canvas; integrated - legend - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Descriptive labels with units; correct title format - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series=#009E73 (green); multi-series follows Okabe-Ito order; - backgrounds correct (#FAF8F1 light, #1A1A17 dark); chrome theme-adaptive' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: false - comment: Well-configured Okabe-Ito palette and Style object; professional - appearance but relies on library defaults rather than custom refinements - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: false - comment: Generous margins, subtle grid, year watermark adds context and refinement - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: false - comment: Clear regional comparison through color; population variation through - size; could emphasize temporal patterns more - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct scatter plot (XY chart) - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: 'All features present: animated over time, timeline slider in HTML, - color by region, size by population' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X/Y correctly assigned; appropriate ranges (0-50 for x, 45-90 for - y); color and size encoding present - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Correct title format; legend labels identify regions clearly - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Shows diverse regional data with variation in development metrics - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Real-world scenario with GDP and life expectancy; neutral and plausible - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Factually correct values for the domain - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear flow: imports → data → styling → charts → output' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) ensures deterministic generation - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports justified and used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean, readable, no fake functionality - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Correct output format - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Good use of pygal.XY, Style, and standard API - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: Creative HTML wrapper with interactive slider and PIL watermarking - verdict: APPROVED -impl_tags: - dependencies: - - pillow - - cairosvg - techniques: - - html-export - - annotations - patterns: - - data-generation - - iteration-over-groups - dataprep: [] - styling: - - publication-ready diff --git a/plots/scatter-animated-controls/metadata/python/seaborn.yaml b/plots/scatter-animated-controls/metadata/python/seaborn.yaml deleted file mode 100644 index 7a83fe2b61..0000000000 --- a/plots/scatter-animated-controls/metadata/python/seaborn.yaml +++ /dev/null @@ -1,236 +0,0 @@ -library: seaborn -language: python -specification_id: scatter-animated-controls -created: '2026-05-15T11:02:23Z' -updated: '2026-05-15T11:14:16Z' -generated_by: claude-haiku -workflow_run: 25914216874 -issue: 3067 -python_version: 3.13.13 -library_version: 0.13.2 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/seaborn/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-animated-controls/python/seaborn/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 81 -review: - strengths: - - Perfect visual quality with excellent text legibility in both light and dark themes - - Proper adaptation of interactive spec to seaborn's static capabilities using faceted - grid per library guidelines - - Clean, reproducible code with simple linear structure and deterministic random - seed - - Solid theme token integration applied consistently throughout (colors, grid, legend, - spines) - weaknesses: - - 'Missing population-based size encoding: Population Proxy column generated but - never used in visualization; marker size should vary with this column to add third - data dimension' - - Generic design using standard seaborn defaults; no custom refinements or special - visual hierarchy to guide viewer through temporal evolution - - Size encoding omission reduces data storytelling potential - implementation could - show population dimension to create richer narrative - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) - correct and appropriate for light theme - Chrome: Title, axis labels, year labels, tick labels all in dark text (#1A1A17, #4A4A44). All text is clearly readable against the light background. Legend uses elevated background (#FFFDF6) with dark text. - Data: 5 countries color-coded with Okabe-Ito palette (first series #009E73 green, plus orange, blue, reddish-purple, yellow). Markers have s=150, alpha=0.8 with edge definition. All markers clearly visible and distinguishable. - Legibility verdict: PASS - All chrome elements properly themed and readable - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) - correct and appropriate for dark theme - Chrome: Title, axis labels, year labels, tick labels all in light text (#F0EFE8, #B8B7B0). All text is clearly readable against the dark background. Legend uses dark elevated background (#242420) with light text. No dark-on-dark failures detected. - Data: Colors identical to light render (confirming only chrome theme-adaptation, not data color changes). Grid lines subtle and visible. - Legibility verdict: PASS - All chrome elements properly themed and readable; no dark-on-dark issues - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All font sizes explicitly set (title 20pt, axis 14pt, ticks 12pt). - Readable in both themes. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: 5-panel grid (3x2) with good spacing. No overlapping text. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Markers clearly visible with s=150, alpha=0.8, edgecolor='face' for - definition. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette is CVD-safe. No red-green as sole signal. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Good proportions with height=4.3, aspect=1.05. Nothing cut off. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Descriptive labels with units (GDP per Capita, Life Expectancy). - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73, correct Okabe-Ito order, backgrounds #FAF8F1/#1A1A17 - correct for both themes.' - design_excellence: - score: 9 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: false - comment: Theme tokens thoughtfully integrated; spine removal clean. Relies - on defaults rather than custom refinements. - - id: DE-02 - name: Visual Refinement - score: 2 - max: 6 - passed: false - comment: Minimal customization. Standard grid styling at alpha=0.10. No special - refinements. - - id: DE-03 - name: Data Storytelling - score: 2 - max: 6 - passed: false - comment: Faceted layout shows progression but lacks visual hierarchy or focal - point to guide viewer. - spec_compliance: - score: 13 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Scatter plot type correct. - - id: SC-02 - name: Required Features - score: 2 - max: 4 - passed: false - comment: 'Animation/controls omitted per library limitation (correct adaptation). - Gap: size encoding missing - Population Proxy generated but never used.' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X, Y, Color mapped correctly. Axes span data range well. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title format correct. Legend properly labeled. - data_quality: - score: 13 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: false - comment: '5 countries x 5 years shown clearly. Missing: population variation - as size encoding.' - - id: DQ-02 - name: Realistic Context - score: 4 - max: 5 - passed: false - comment: Plausible GDP/life expectancy ranges. Generic country names (neutral). - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Sensible axis scales. No distortion. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Simple linear flow, no functions/classes. - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) ensures deterministic output. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only necessary imports. - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: No fake interactivity. Straightforward code. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png correctly. - library_mastery: - score: 6 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: sns.relplot() with col_wrap for faceting; high-level API; correct - theme integration. - - id: LM-02 - name: Distinctive Features - score: 1 - max: 5 - passed: false - comment: Standard relplot usage only. No distinctive seaborn features beyond - defaults. - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - faceting - - subplots - patterns: - - data-generation - - explicit-figure - dataprep: [] - styling: - - grid-styling - - alpha-blending diff --git a/plots/scatter-animated-controls/specification.md b/plots/scatter-animated-controls/specification.md deleted file mode 100644 index 017b788597..0000000000 --- a/plots/scatter-animated-controls/specification.md +++ /dev/null @@ -1,31 +0,0 @@ -# scatter-animated-controls: Animated Scatter Plot with Play Controls - -## Description - -An animated scatter plot that displays data points changing over time, inspired by Hans Rosling's Gapminder visualizations. This plot includes interactive play/pause controls and a timeline slider, allowing users to explore temporal patterns, observe how data evolves across time periods, and pause at specific moments for detailed analysis. Ideal for storytelling with data and revealing trends that unfold over time. - -## Applications - -- Visualizing country-level metrics (GDP, life expectancy, population) evolving over decades like Gapminder -- Tracking product performance metrics across multiple quarters or years -- Exploring how scientific measurements or experimental results change through sequential observations - -## Data - -- `x` (numeric) - Variable plotted on the horizontal axis, changing over time -- `y` (numeric) - Variable plotted on the vertical axis, changing over time -- `time` (numeric or datetime) - Time dimension that drives the animation frames -- `size` (numeric, optional) - Point size encoding for a third variable -- `color` (categorical, optional) - Category for color-coding different groups/entities -- `label` (string, optional) - Entity labels for identification -- Size: 10-100 entities tracked across 10-50 time periods recommended -- Example: Simulated country data with GDP per capita, life expectancy, and population over 20 years - -## Notes - -- Play/pause button should be prominently visible and intuitive -- Timeline slider enables jumping to any point in the animation -- Current time/year should be displayed clearly (often as large text in background or corner) -- Smooth transitions between frames enhance the storytelling effect -- Optional trails can show the path of entities over time -- Libraries without animation support should implement a static faceted version showing key time points diff --git a/plots/scatter-animated-controls/specification.yaml b/plots/scatter-animated-controls/specification.yaml deleted file mode 100644 index 65e78839ab..0000000000 --- a/plots/scatter-animated-controls/specification.yaml +++ /dev/null @@ -1,29 +0,0 @@ -# Specification-level metadata for scatter-animated-controls -# Auto-synced to PostgreSQL on push to main - -spec_id: scatter-animated-controls -title: Animated Scatter Plot with Play Controls - -# Specification tracking -created: 2025-12-31T13:39:39Z -updated: 2026-05-15T10:53:23Z -issue: 3067 -suggested: MarkusNeusinger - -# Classification tags (applies to all library implementations) -# See docs/reference/tagging-system.md for detailed guidelines -tags: - plot_type: - - scatter - data_type: - - numeric - - timeseries - domain: - - general - - statistics - - business - features: - - animated - - interactive - - temporal - - controls diff --git a/plots/scatter-brush-zoom/implementations/python/altair.py b/plots/scatter-brush-zoom/implementations/python/altair.py deleted file mode 100644 index ce2e8f4576..0000000000 --- a/plots/scatter-brush-zoom/implementations/python/altair.py +++ /dev/null @@ -1,153 +0,0 @@ -""" anyplot.ai -scatter-brush-zoom: Interactive Scatter Plot with Brush Selection and Zoom -Library: altair 6.1.0 | Python 3.13.13 -Quality: 94/100 | Updated: 2026-05-16 -""" - -import os -import sys - - -# Work around module name conflict: ensure altair library is imported, not this file -if ( - "altair" in sys.modules - and hasattr(sys.modules["altair"], "__file__") - and "scatter-brush-zoom" in sys.modules["altair"].__file__ -): - del sys.modules["altair"] -sys.path = [p for p in sys.path if "scatter-brush-zoom" not in p] - -import altair as alt # noqa: E402 -import numpy as np # noqa: E402 -import pandas as pd # noqa: E402 - - -# 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" - -# Okabe-Ito palette (first series is always #009E73) -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233"] - -# Data - Generate clustered data for brush selection demonstration -np.random.seed(42) - -n_points = 200 -clusters = [] - -# Cluster 1: Top-right (high performance sensors) -c1 = pd.DataFrame({"x": np.random.normal(75, 8, 50), "y": np.random.normal(80, 10, 50), "category": "Sensor A"}) -clusters.append(c1) - -# Cluster 2: Bottom-left (low performance sensors) -c2 = pd.DataFrame({"x": np.random.normal(25, 10, 50), "y": np.random.normal(30, 8, 50), "category": "Sensor B"}) -clusters.append(c2) - -# Cluster 3: Center (moderate performance) -c3 = pd.DataFrame({"x": np.random.normal(50, 12, 60), "y": np.random.normal(55, 12, 60), "category": "Sensor C"}) -clusters.append(c3) - -# Cluster 4: Scattered outliers -c4 = pd.DataFrame({"x": np.random.uniform(10, 90, 40), "y": np.random.uniform(10, 90, 40), "category": "Sensor D"}) -clusters.append(c4) - -df = pd.concat(clusters, ignore_index=True) -df["id"] = range(len(df)) - -# Define interval selection for brush (click-drag to select region) -brush = alt.selection_interval() - -# Define color scale using Okabe-Ito palette -color_scale = alt.Scale(domain=["Sensor A", "Sensor B", "Sensor C", "Sensor D"], range=IMPRINT) - -# Create the scatter plot with brush selection -points = ( - alt.Chart(df) - .mark_circle(size=120, strokeWidth=2) - .encode( - x=alt.X("x:Q", title="Efficiency Score (%)", scale=alt.Scale(domain=[0, 100])), - y=alt.Y("y:Q", title="Reliability Index", scale=alt.Scale(domain=[0, 100])), - color=alt.condition(brush, alt.Color("category:N", scale=color_scale, title="Category"), alt.value(INK_SOFT)), - opacity=alt.condition(brush, alt.value(0.85), alt.value(0.3)), - stroke=alt.condition(brush, alt.value(INK), alt.value("transparent")), - tooltip=[ - alt.Tooltip("id:O", title="Point ID"), - alt.Tooltip("category:N", title="Category"), - alt.Tooltip("x:Q", title="Efficiency", format=".1f"), - alt.Tooltip("y:Q", title="Reliability", format=".1f"), - ], - ) - .add_params(brush) -) - -# Create a dummy layer for the legend (always shows full legend) -legend_layer = ( - alt.Chart(df) - .mark_circle(size=0, opacity=0) - .encode(color=alt.Color("category:N", scale=color_scale, title="Category")) -) - -scatter = alt.layer(points, legend_layer).properties( - width=1600, - height=900, - title=alt.Title( - text="scatter-brush-zoom · altair · anyplot.ai", - fontSize=28, - anchor="middle", - subtitle="Click and drag to select a region | Scroll to zoom | Shift+drag to pan", - subtitleFontSize=18, - subtitleColor=INK_SOFT, - ), -) - -# Create a bar chart showing count of selected points per category -bars = ( - alt.Chart(df) - .mark_bar(cornerRadiusTopLeft=4, cornerRadiusTopRight=4) - .encode( - x=alt.X( - "category:N", - title="Category", - axis=alt.Axis(labelFontSize=16, titleFontSize=18, labelAngle=0, labelPadding=15), - ), - y=alt.Y("count():Q", title="Selected Count", axis=alt.Axis(labelFontSize=16, titleFontSize=18)), - color=alt.Color("category:N", scale=color_scale, legend=None), - ) - .transform_filter(brush) - .properties(width=1600, height=200, title=alt.Title(text="Selected Points by Category", fontSize=22)) -) - -# Combine charts vertically -chart = ( - alt.vconcat(scatter.interactive(), bars) - .properties(background=PAGE_BG) - .configure_view(fill=PAGE_BG, stroke=INK_SOFT, strokeWidth=0) - .configure_axis( - labelFontSize=16, - titleFontSize=20, - gridColor=INK, - gridOpacity=0.10, - domainColor=INK_SOFT, - tickColor=INK_SOFT, - labelColor=INK_SOFT, - titleColor=INK, - ) - .configure_title(color=INK, fontSize=28) - .configure_legend( - fillColor=ELEVATED_BG, - strokeColor=INK_SOFT, - labelColor=INK_SOFT, - titleColor=INK, - titleFontSize=18, - labelFontSize=16, - symbolSize=200, - ) - .resolve_scale(color="shared") -) - -# Save outputs -chart.save(f"plot-{THEME}.png", scale_factor=3.0) -chart.save(f"plot-{THEME}.html") diff --git a/plots/scatter-brush-zoom/implementations/python/bokeh.py b/plots/scatter-brush-zoom/implementations/python/bokeh.py deleted file mode 100644 index cd23a51238..0000000000 --- a/plots/scatter-brush-zoom/implementations/python/bokeh.py +++ /dev/null @@ -1,189 +0,0 @@ -""" anyplot.ai -scatter-brush-zoom: Interactive Scatter Plot with Brush Selection and Zoom -Library: bokeh 3.9.0 | Python 3.13.13 -Quality: 92/100 | Updated: 2026-05-16 -""" - -import os -import site -import sys -import time -from pathlib import Path - - -# Ensure site-packages is at the front to avoid shadowing the bokeh package -site_packages_list = site.getsitepackages() or [] -for sp in reversed(site_packages_list): - sys.path.insert(0, sp) - -import numpy as np # noqa: E402 -from bokeh.io import output_file, save # noqa: E402 -from bokeh.models import ( # noqa: E402 - BoxSelectTool, - ColumnDataSource, - HoverTool, - Legend, - LegendItem, - PanTool, - ResetTool, - WheelZoomTool, -) -from bokeh.plotting import figure # noqa: E402 -from bokeh.transform import factor_cmap # noqa: E402 -from selenium import webdriver # noqa: E402 -from selenium.webdriver.chrome.options import Options # noqa: E402 - - -# 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" - -# Okabe-Ito palette -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233"] - -# Data - Generate clustered data for demonstrating brush selection -np.random.seed(42) - -n_per_cluster = 75 -clusters = [] -labels = ["Cluster A", "Cluster B", "Cluster C", "Cluster D"] -centers = [(20, 30), (60, 70), (25, 75), (70, 25)] -spreads = [8, 10, 6, 9] - -for i, (cx, cy) in enumerate(centers): - x_vals = np.random.normal(cx, spreads[i], n_per_cluster) - y_vals = np.random.normal(cy, spreads[i], n_per_cluster) - cluster = np.full(n_per_cluster, labels[i]) - clusters.append((x_vals, y_vals, cluster)) - -x = np.concatenate([c[0] for c in clusters]) -y = np.concatenate([c[1] for c in clusters]) -category = np.concatenate([c[2] for c in clusters]) - -# Create ColumnDataSource -source = ColumnDataSource(data={"x": x, "y": y, "category": category}) - -# Plot -p = figure( - width=4800, - height=2700, - title="scatter-brush-zoom · bokeh · anyplot.ai", - x_axis_label="Measurement X", - y_axis_label="Measurement Y", - tools="", - output_backend="webgl", -) - -# Interactive tools -box_select = BoxSelectTool() -wheel_zoom = WheelZoomTool() -pan = PanTool() -reset = ResetTool() -hover = HoverTool(tooltips=[("Category", "@category"), ("X", "@x{0.00}"), ("Y", "@y{0.00}")]) - -p.add_tools(box_select, wheel_zoom, pan, reset, hover) -p.toolbar.active_drag = box_select -p.toolbar.active_scroll = wheel_zoom - -# Color mapping with Okabe-Ito palette -color_map = factor_cmap("category", palette=IMPRINT, factors=labels) - -# Scatter plot with selection styling -p.scatter( - "x", - "y", - source=source, - size=15, - fill_color=color_map, - line_color=PAGE_BG, - line_width=2, - alpha=0.8, - selection_fill_color=color_map, - selection_line_color=INK, - selection_line_width=4, - selection_alpha=1.0, - nonselection_fill_color=color_map, - nonselection_line_color=PAGE_BG, - nonselection_alpha=0.2, -) - -# Style title -p.title.text_font_size = "28pt" -p.title.text_color = INK - -# Style axes -p.xaxis.axis_label_text_font_size = "22pt" -p.yaxis.axis_label_text_font_size = "22pt" -p.xaxis.axis_label_text_color = INK -p.yaxis.axis_label_text_color = INK -p.xaxis.major_label_text_font_size = "18pt" -p.yaxis.major_label_text_font_size = "18pt" -p.xaxis.major_label_text_color = INK_SOFT -p.yaxis.major_label_text_color = INK_SOFT - -# Spine styling -p.outline_line_color = INK_SOFT -p.xaxis.axis_line_color = INK_SOFT -p.yaxis.axis_line_color = INK_SOFT -p.xaxis.major_tick_line_color = INK_SOFT -p.yaxis.major_tick_line_color = INK_SOFT - -# Grid styling -p.xgrid.grid_line_color = INK -p.ygrid.grid_line_color = INK -p.xgrid.grid_line_alpha = 0.10 -p.ygrid.grid_line_alpha = 0.10 - -# Background -p.background_fill_color = PAGE_BG -p.border_fill_color = PAGE_BG - -# Legend -legend_items = [] -for i, label in enumerate(labels): - r = p.scatter([], [], fill_color=IMPRINT[i], line_color=PAGE_BG, size=15) - legend_items.append(LegendItem(label=label, renderers=[r])) - -legend = Legend(items=legend_items, location="top_right") -legend.label_text_font_size = "18pt" -legend.label_text_color = INK_SOFT -legend.background_fill_color = ELEVATED_BG -legend.border_line_color = INK_SOFT -legend.glyph_height = 25 -legend.glyph_width = 25 -legend.spacing = 12 -legend.padding = 15 -p.add_layout(legend) - -# Margins -p.min_border_left = 100 -p.min_border_bottom = 100 -p.min_border_right = 50 -p.min_border_top = 80 - -# Save HTML -output_file(f"plot-{THEME}.html") -save(p) - -# Screenshot with Selenium -W, H = 4800, 2700 -opts = Options() -for arg in ( - "--headless=new", - "--no-sandbox", - "--disable-dev-shm-usage", - "--disable-gpu", - f"--window-size={W},{H}", - "--hide-scrollbars", -): - opts.add_argument(arg) - -driver = webdriver.Chrome(options=opts) -driver.set_window_size(W, H) -driver.get(f"file://{Path(f'plot-{THEME}.html').resolve()}") -time.sleep(3) -driver.save_screenshot(f"plot-{THEME}.png") -driver.quit() diff --git a/plots/scatter-brush-zoom/implementations/python/letsplot.py b/plots/scatter-brush-zoom/implementations/python/letsplot.py deleted file mode 100644 index 602a273d39..0000000000 --- a/plots/scatter-brush-zoom/implementations/python/letsplot.py +++ /dev/null @@ -1,153 +0,0 @@ -""" anyplot.ai -scatter-brush-zoom: Interactive Scatter Plot with Brush Selection and Zoom -Library: letsplot 4.9.0 | Python 3.13.13 -Quality: 93/100 | Updated: 2026-05-16 -""" - -import os - -import numpy as np -import pandas as pd -from lets_plot import ( - LetsPlot, - aes, - element_line, - element_rect, - element_text, - geom_point, - geom_rect, - geom_text, - ggplot, - ggsize, - labs, - layer_tooltips, - scale_alpha_identity, - scale_color_manual, - theme, - theme_minimal, -) -from lets_plot.export import ggsave - - -LetsPlot.setup_html() - -# 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" - -# Okabe-Ito palette - first series is ALWAYS #009E73 -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233"] - -# Brush rectangle color -BRUSH_COLOR = "#4467A3" - -# Data - Generate clustered data for brush selection demonstration -np.random.seed(42) - -# Create 4 distinct clusters with different sizes -n_per_cluster = [80, 120, 100, 100] -centers = [(20, 60), (50, 30), (70, 70), (40, 80)] -spreads = [8, 12, 10, 6] -categories = ["Cluster A", "Cluster B", "Cluster C", "Cluster D"] - -x_data, y_data, colors, labels, selected = [], [], [], [], [] -point_id = 0 -for n, (cx, cy), spread, cat in zip(n_per_cluster, centers, spreads, categories, strict=True): - for _ in range(n): - x_val = np.random.normal(cx, spread) - y_val = np.random.normal(cy, spread) - x_data.append(x_val) - y_data.append(y_val) - colors.append(cat) - labels.append(f"P{point_id:03d}") - # Mark points within the brush selection region as selected - is_selected = 25 <= x_val <= 55 and 50 <= y_val <= 85 - selected.append(is_selected) - point_id += 1 - -df = pd.DataFrame( - { - "x": x_data, - "y": y_data, - "category": colors, - "label": labels, - "selected": selected, - "point_alpha": [0.9 if s else 0.4 for s in selected], - } -) - -# Create tooltips for hover interaction -tooltips = ( - layer_tooltips() - .title("@label") - .line("Category: @category") - .line("X: @x (units)") - .line("Y: @y (units)") - .line("Selected: @selected") - .format("x", ".1f") - .format("y", ".1f") -) - -# Brush selection rectangle coordinates -brush_xmin, brush_xmax = 25, 55 -brush_ymin, brush_ymax = 50, 85 -n_selected = df["selected"].sum() - -# Create brush rectangle data -brush_df = pd.DataFrame({"xmin": [brush_xmin], "xmax": [brush_xmax], "ymin": [brush_ymin], "ymax": [brush_ymax]}) - -# Create interactive scatter plot with brush selection visualization -plot = ( - ggplot(df, aes(x="x", y="y")) - # Draw brush selection rectangle first (behind points) - + geom_rect( - aes(xmin="xmin", xmax="xmax", ymin="ymin", ymax="ymax"), - data=brush_df, - inherit_aes=False, - fill=BRUSH_COLOR, - alpha=0.15, - color=BRUSH_COLOR, - linetype="dashed", - size=1.5, - ) - # Plot points with alpha based on selection state - + geom_point(aes(color="category", alpha="point_alpha"), size=5, tooltips=tooltips) - + scale_color_manual(values=IMPRINT) - + scale_alpha_identity() - + labs( - x="X Value (units)", y="Y Value (units)", title="scatter-brush-zoom · letsplot · anyplot.ai", color="Category" - ) - + theme_minimal() - + theme( - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - panel_background=element_rect(fill=PAGE_BG), - panel_grid_major=element_line(color=INK_SOFT, size=0.3), - panel_grid_minor=element_line(color=INK_SOFT, size=0.2), - axis_title=element_text(size=20, color=INK), - axis_text=element_text(size=16, color=INK_SOFT), - axis_line=element_line(color=INK_SOFT), - plot_title=element_text(size=24, color=INK, margin=[0, 0, 10, 0]), - legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT), - legend_title=element_text(size=18, color=INK), - legend_text=element_text(size=16, color=INK_SOFT), - legend_position="right", - legend_direction="vertical", - ) - + ggsize(1600, 900) -) - -# Add annotation showing selection count -annotation_df = pd.DataFrame({"x": [40], "y": [88], "text": [f"Brush Selection: {n_selected} points selected"]}) - -plot = plot + geom_text( - aes(x="x", y="y", label="text"), data=annotation_df, inherit_aes=False, size=14, color=BRUSH_COLOR, fontface="bold" -) - -# Save static PNG (scaled 3x for 4800x2700 px) -ggsave(plot, f"plot-{THEME}.png", path=".", scale=3) - -# Save interactive HTML with zoom, pan, and hover capabilities -ggsave(plot, f"plot-{THEME}.html", path=".") diff --git a/plots/scatter-brush-zoom/implementations/python/plotly.py b/plots/scatter-brush-zoom/implementations/python/plotly.py deleted file mode 100644 index 1158fe6fe4..0000000000 --- a/plots/scatter-brush-zoom/implementations/python/plotly.py +++ /dev/null @@ -1,142 +0,0 @@ -""" anyplot.ai -scatter-brush-zoom: Interactive Scatter Plot with Brush Selection and Zoom -Library: plotly 6.7.0 | Python 3.13.13 -Quality: 79/100 | Updated: 2026-05-16 -""" - -import numpy as np -import plotly.graph_objects as go - - -# Data - Multiple clusters for demonstrating brush selection -np.random.seed(42) -n_points = 300 - -# Create 4 distinct clusters -cluster_centers = [(20, 30), (50, 70), (80, 40), (60, 20)] -cluster_names = ["Cluster A", "Cluster B", "Cluster C", "Cluster D"] -colors = ["#306998", "#FFD43B", "#4CAF50", "#E91E63"] - -x_data = [] -y_data = [] -labels = [] -color_values = [] - -for i, (cx, cy) in enumerate(cluster_centers): - n = n_points // 4 - x_cluster = np.random.normal(cx, 8, n) - y_cluster = np.random.normal(cy, 8, n) - x_data.extend(x_cluster) - y_data.extend(y_cluster) - labels.extend([cluster_names[i]] * n) - color_values.extend([colors[i]] * n) - -# Add some outliers -n_outliers = 25 -x_data.extend(np.random.uniform(0, 100, n_outliers)) -y_data.extend(np.random.uniform(0, 100, n_outliers)) -labels.extend(["Outlier"] * n_outliers) -color_values.extend(["#9E9E9E"] * n_outliers) - -x_data = np.array(x_data) -y_data = np.array(y_data) - -# Create figure -fig = go.Figure() - -# Add traces for each group (for proper legend) -unique_labels = ["Cluster A", "Cluster B", "Cluster C", "Cluster D", "Outlier"] -unique_colors = ["#306998", "#FFD43B", "#4CAF50", "#E91E63", "#9E9E9E"] - -for label, color in zip(unique_labels, unique_colors, strict=False): - mask = np.array(labels) == label - fig.add_trace( - go.Scatter( - x=x_data[mask], - y=y_data[mask], - mode="markers", - name=label, - marker={"size": 14, "color": color, "opacity": 0.75, "line": {"width": 1, "color": "white"}}, - hovertemplate="%{text}
X: %{x:.1f}
Y: %{y:.1f}", - text=[label] * mask.sum(), - ) - ) - -# Update layout for interactivity and styling -fig.update_layout( - title={ - "text": "scatter-brush-zoom · plotly · pyplots.ai", "font": {"size": 32, "color": "#333333"}, "x": 0.5, "xanchor": "center" - }, - xaxis={ - "title": {"text": "X Value", "font": {"size": 24}}, - "tickfont": {"size": 18}, - "gridcolor": "rgba(128, 128, 128, 0.2)", - "gridwidth": 1, - "showgrid": True, - "zeroline": False, - "range": [-10, 110], - }, - yaxis={ - "title": {"text": "Y Value", "font": {"size": 24}}, - "tickfont": {"size": 18}, - "gridcolor": "rgba(128, 128, 128, 0.2)", - "gridwidth": 1, - "showgrid": True, - "zeroline": False, - "range": [-10, 110], - }, - template="plotly_white", - # Enable box and lasso select modes - dragmode="select", - # Selection styling - selectdirection="any", - # Legend styling - legend={ - "font": {"size": 18}, - "bgcolor": "rgba(255, 255, 255, 0.9)", - "bordercolor": "rgba(128, 128, 128, 0.3)", - "borderwidth": 1, - "x": 1.02, - "y": 1, - "xanchor": "left", - "yanchor": "top", - }, - # Margin for labels - margin={"l": 80, "r": 150, "t": 100, "b": 80}, - # Annotations for instructions - annotations=[ - { - "text": "Drag to select • Scroll to zoom • Double-click to reset", - "xref": "paper", - "yref": "paper", - "x": 0.5, - "y": -0.12, - "showarrow": False, - "font": {"size": 16, "color": "#666666"}, - "xanchor": "center", - } - ], - # Add modebar buttons for zoom and selection - modebar={ - "bgcolor": "rgba(255, 255, 255, 0.9)", "color": "#306998", "activecolor": "#FFD43B", "add": ["select2d", "lasso2d"] - }, -) - -# Configure selection appearance -fig.update_traces(selected={"marker": {"opacity": 1.0, "size": 18}}, unselected={"marker": {"opacity": 0.3}}) - -# Save as PNG (static output for quality review) -fig.write_image("plot.png", width=1600, height=900, scale=3) - -# Save as HTML (interactive version) -fig.write_html( - "plot.html", - include_plotlyjs=True, - full_html=True, - config={ - "displayModeBar": True, - "modeBarButtonsToAdd": ["select2d", "lasso2d"], - "scrollZoom": True, - "displaylogo": False, - }, -) diff --git a/plots/scatter-brush-zoom/implementations/python/pygal.py b/plots/scatter-brush-zoom/implementations/python/pygal.py deleted file mode 100644 index 42cbed06ed..0000000000 --- a/plots/scatter-brush-zoom/implementations/python/pygal.py +++ /dev/null @@ -1,115 +0,0 @@ -""" anyplot.ai -scatter-brush-zoom: Interactive Scatter Plot with Brush Selection and Zoom -Library: pygal 3.1.0 | Python 3.13.13 -Quality: 83/100 | Created: 2026-05-16 -""" - -import os -import sys - -import numpy as np - - -# Temporarily remove current directory from path to avoid name collision with pygal module -_cwd = sys.path[0] if sys.path[0] else "." -if _cwd in sys.path: - sys.path.remove(_cwd) - -import pygal # noqa: E402 -from pygal.style import Style # noqa: E402 - - -# Restore path -sys.path.insert(0, _cwd) - -# Theme tokens -THEME = os.getenv("ANYPLOT_THEME", "light") -PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" -INK = "#1A1A17" if THEME == "light" else "#F0EFE8" -INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -IMPRINT = ("#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477") - -# Data: Generate 3 clusters for interactive selection demonstration -np.random.seed(42) - -# Cluster 1 (brand green) -cluster1_x = np.random.normal(40, 8, 120) -cluster1_y = np.random.normal(50, 8, 120) -cluster1_group = ["Group A"] * 120 - -# Cluster 2 (vermillion) -cluster2_x = np.random.normal(70, 8, 100) -cluster2_y = np.random.normal(65, 8, 100) -cluster2_group = ["Group B"] * 100 - -# Cluster 3 (blue) -cluster3_x = np.random.normal(55, 10, 130) -cluster3_y = np.random.normal(35, 10, 130) -cluster3_group = ["Group C"] * 130 - -# Combine clusters -all_x = np.concatenate([cluster1_x, cluster2_x, cluster3_x]) -all_y = np.concatenate([cluster1_y, cluster2_y, cluster3_y]) -all_groups = cluster1_group + cluster2_group + cluster3_group - -# Prepare data by group for separate series -group_a_data = [ - (x, y) - for x, y, g in zip(all_x, all_y, all_groups, strict=False) - if g == "Group A" -] -group_b_data = [ - (x, y) - for x, y, g in zip(all_x, all_y, all_groups, strict=False) - if g == "Group B" -] -group_c_data = [ - (x, y) - for x, y, g in zip(all_x, all_y, all_groups, strict=False) - if g == "Group C" -] - -# Create custom style -custom_style = Style( - background=PAGE_BG, - plot_background=PAGE_BG, - foreground=INK, - foreground_strong=INK, - foreground_subtle=INK_MUTED, - colors=IMPRINT, - title_font_size=28, - label_font_size=18, - major_label_font_size=16, - legend_font_size=16, - value_font_size=14, - stroke_width=2, -) - -# Create scatter plot with interactive features -chart = pygal.XY( - style=custom_style, - width=4800, - height=2700, - title="scatter-brush-zoom · pygal · anyplot.ai", - x_title="X Axis", - y_title="Y Axis", - show_legend=True, - show_dots=True, - dots_size=4, - stroke_style={"width": 2}, - explicit_size=True, - range=(0, 100), -) - -# Add series with point labels for interactivity -chart.add("Group A", group_a_data, dots_size=5, stroke=False) -chart.add("Group B", group_b_data, dots_size=5, stroke=False) -chart.add("Group C", group_c_data, dots_size=5, stroke=False) - -# Save outputs -chart.render_to_png(f"plot-{THEME}.png") - -with open(f"plot-{THEME}.html", "wb") as f: - f.write(chart.render()) diff --git a/plots/scatter-brush-zoom/metadata/python/altair.yaml b/plots/scatter-brush-zoom/metadata/python/altair.yaml deleted file mode 100644 index cbd9903893..0000000000 --- a/plots/scatter-brush-zoom/metadata/python/altair.yaml +++ /dev/null @@ -1,250 +0,0 @@ -library: altair -language: python -specification_id: scatter-brush-zoom -created: '2026-01-08T16:09:18Z' -updated: '2026-05-16T23:41:18Z' -generated_by: claude-haiku -workflow_run: 25975758973 -issue: 3295 -language_version: 3.13.13 -library_version: 6.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-brush-zoom/python/altair/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-brush-zoom/python/altair/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/scatter-brush-zoom/python/altair/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-brush-zoom/python/altair/plot-dark.html -quality_score: 94 -review: - strengths: - - 'Perfect palette compliance: Okabe-Ito colors identical across light and dark - renders with flawless theme-adaptive chrome' - - Expertly implemented brush selection with alt.selection_interval() and conditional - encoding creating responsive visualization - - Dual visualization (scatter + bar chart) demonstrates strong design intent beyond - basic scatter plot, telling a coherent story about selection - - 'All text legible in both themes: explicit font sizes (Title 28px, Labels 20px, - Ticks 16px) properly scale to large canvas' - - Authentic interactive features (brush, zoom, pan) leveraging altair's distinctive - capabilities rather than static alternatives - - Realistic sensor performance data with proper clustering demonstrating all aspects - of exploratory scatter plot analysis - weaknesses: [] - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) - correct theme-light surface - Chrome: Title and subtitle clearly visible in dark text (INK tokens); axis labels "Efficiency Score (%)" and "Reliability Index" with proper hierarchy; tick labels at 16px font size; all fully readable - Data: 200 points in 4 categories - Sensor A (green #009E73 - brand), Sensor B (orange #D55E00), Sensor C (blue #0072B2), Sensor D (purple #CC79A7); unselected points muted gray (INK_SOFT) at 0.3 opacity; selected points show full color with 0.85 opacity and stroke; bar chart below with identical color scheme and responsive counts - Legibility verdict: PASS - all elements readable with excellent contrast; marker strokes add definition to overlapping points; color coding is clear and distinct - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) - correct theme-dark surface - Chrome: Title and subtitle visible in light text (F0EFE8); axis labels and tick labels in appropriate light gray (B8B7B0); all fully readable against dark background - NO dark-on-dark failures - Data: All data colors **identical** to light render - Sensor A #009E73, Sensor B #D55E00, Sensor C #0072B2, Sensor D #CC79A7; unselected points rendered as lighter gray for visibility; selected points maintain full color and stroke; bar chart shows same colors with proper dark-theme contrast - Legibility verdict: PASS - perfect theme adaptation; both renders equally readable; only chrome flipped, data identity preserved - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: 'All font sizes explicitly set: Title 28px, Labels 20px, Ticks 16px; - perfectly readable in both themes' - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: All text fully readable with no collisions; stroke on markers helps - distinguish overlapping points - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: 200 points with optimal marker sizing; opacity adaptation (0.3/0.85) - handles density; colors distinct in both themes - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette is colorblind-safe; contrast between selected/unselected - clear; no red-green dependency - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Plot fills ~70% of canvas; balanced margins; generous whitespace; - legend well-positioned - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: 'Descriptive with units: ''Efficiency Score (%)'' and ''Reliability - Index''' - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73; follows Okabe-Ito order (#D55E00, #0072B2, - #CC79A7); backgrounds #FAF8F1/#1A1A17; theme tokens applied correctly; both - renders theme-correct' - design_excellence: - score: 14 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 6 - max: 8 - passed: true - comment: 'Strong design: dual visualization with scatter and responsive bar - chart shows intentional hierarchy; Okabe-Ito palette used effectively; not - quite publication-ready but clearly above defaults' - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: 'Good refinement: subtle grid (0.10 opacity), marker strokes add - definition, legend styling with ELEVATED_BG, generous whitespace' - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: 'Good visual hierarchy: color encoding guides category identification; - dual chart creates narrative about selection; focal point clear (interactive - selection feature)' - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct scatter plot with brush selection and zoom; all interaction - patterns present - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Brush selection (interval), visible rectangle, selected point highlighting, - zoom/pan, reset capability, selection count display - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: 'X: Efficiency Score %; Y: Reliability Index; Color: Category; axes - [0,100] show all data correctly' - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: 'Title: ''scatter-brush-zoom · altair · anyplot.ai''; legend labels - match data (Sensor A/B/C/D)' - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'Four distinct clusters demonstrate all aspects: high-performance - (A), low-performance (B), moderate (C), scattered outliers (D)' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Realistic sensor performance scenario; neutral domain; 4 sensor types - with logical distributions - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Efficiency Score 0-100% and Reliability Index 0-100 are standard - metrics; values plausible for sensor data - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear: Imports → Data → Tokens → Chart → Save; no unnecessary functions' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) ensures deterministic output - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: 'All imports used: os, sys (for path), altair, numpy, pandas' - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean, Pythonic, no over-engineering, no fake functionality - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png and plot-{THEME}.html; current API (altair - 6.1.0) - library_mastery: - score: 10 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: 'Expert altair patterns: selection_interval(), alt.condition(), .transform_filter(), - .interactive(), .vconcat(), resolve_scale()' - - id: LM-02 - name: Distinctive Features - score: 5 - max: 5 - passed: true - comment: 'Leverages altair-specific: selection_interval with conditional encoding, - reactive dual visualization, theme-aware os.getenv() rendering, resolve_scale() - - features not easily replicated in static libraries' - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - subplots - - custom-legend - - hover-tooltips - - html-export - patterns: - - data-generation - - iteration-over-groups - dataprep: [] - styling: - - alpha-blending - - grid-styling diff --git a/plots/scatter-brush-zoom/metadata/python/bokeh.yaml b/plots/scatter-brush-zoom/metadata/python/bokeh.yaml deleted file mode 100644 index 8691f34e1a..0000000000 --- a/plots/scatter-brush-zoom/metadata/python/bokeh.yaml +++ /dev/null @@ -1,236 +0,0 @@ -library: bokeh -language: python -specification_id: scatter-brush-zoom -created: '2026-01-08T16:09:13Z' -updated: '2026-05-16T23:31:43Z' -generated_by: claude-haiku -workflow_run: 25975722491 -issue: 3295 -language_version: 3.13.13 -library_version: 3.9.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-brush-zoom/python/bokeh/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-brush-zoom/python/bokeh/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/scatter-brush-zoom/python/bokeh/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-brush-zoom/python/bokeh/plot-dark.html -quality_score: 92 -review: - strengths: - - 'Full compliance with specification: all interactive features present and functional' - - 'Excellent theme adaptation: both light and dark renders readable with proper - color tokens' - - 'Strong selection state visualization: nonselection opacity and selection line - styling clearly demonstrate brush capability' - - 'Proper palette usage: Okabe-Ito order maintained, brand green first' - - Clean, idiomatic Bokeh code with good structure - - All text explicitly sized for large canvas (28pt title, 22pt labels, 18pt ticks) - - Reproducible data generation with seed - weaknesses: - - 'Design Excellence could be improved: top and right spines should be removed for - more minimal appearance' - - 'Data storytelling is passive: clusters are shown but there''s no visual emphasis - or narrative focus' - - Could add additional Bokeh-specific features like CustomJS callbacks for selection - state feedback - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) - correct per style guide - Chrome: Title "scatter-brush-zoom · bokeh · anyplot.ai" (28pt, dark text), axis labels "Measurement X" and "Measurement Y" (22pt), tick labels (18pt, INK_SOFT color) - all clearly readable against light background - Data: Four clusters in Okabe-Ito colors - Cluster A (green #009E73), Cluster B (blue #0072B2), Cluster C (orange #D55E00), Cluster D (reddish-purple #CC79A7). Markers have white edges for definition. Selection state shown via opacity changes (nonselection_alpha=0.2) - Legend: Top-right corner with all four cluster labels, correct colors, ELEVATED_BG background - Legibility verdict: PASS - all text readable with proper contrast - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) - correct per style guide - Chrome: Title and axis labels in light text (INK #F0EFE8), tick labels in secondary light text (INK_SOFT #B8B7B0) - all clearly readable against dark background. No dark-on-dark issues detected - Data: Identical Okabe-Ito colors to light render (brand green, blue, orange, reddish-purple). Marker edges adapt to dark theme. Selection states preserved - Legend: Top-right corner with light text on ELEVATED_BG (#242420) - clearly visible - Legibility verdict: PASS - all text readable, no dark-on-dark failures, visual hierarchy preserved - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: Title (28pt), axis labels (22pt), tick labels (18pt) all readable - in both themes with proper INK tokens - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: Generous margins (min_border_left=100, etc.), legend clear of data - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Marker size 15px appropriate for 4800x2700, all clusters visually - distinct - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito CVD-safe palette with strong contrast - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Standard 4800x2700 landscape, balanced, nothing cut off - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Correct title format with spec-id, library, anyplot.ai - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73, canonical order, correct backgrounds, both - renders theme-correct' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 6 - max: 8 - passed: true - comment: Thoughtful selection state visualization, white marker edges, proper - legend styling - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: false - comment: Good grid and whitespace, but top/right spines should be removed - for cleaner look - - id: DE-03 - name: Data Storytelling - score: 3 - max: 6 - passed: false - comment: Clusters visually separated but lack emphasis or narrative focus - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct interactive scatter plot with brush, zoom, pan, reset - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: 'All features present: brush selection, zoom, reset, hover tooltips' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X/Y properly mapped, full range shown, categorical coloring - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Correct title format, legend shows all clusters - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: All scatter, brush, zoom, pan, reset, hover demonstrated - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: 300 points realistic, normal distribution, sensible value ranges - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Values and sizing appropriate for domain - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: No functions/classes, straightforward direct plotting - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) ensures deterministic output - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports used, no cruft - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Idiomatic Bokeh, no fake interactive features - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Correct plot-{THEME}.png and .html output - library_mastery: - score: 9 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: High-level API, ColumnDataSource, factor_cmap, tool configuration - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: WebGL backend, selection state system, hover tooltips, custom legend - verdict: APPROVED -impl_tags: - dependencies: - - selenium - techniques: - - html-export - - hover-tooltips - - custom-legend - patterns: - - data-generation - - columndatasource - dataprep: [] - styling: - - alpha-blending - - grid-styling diff --git a/plots/scatter-brush-zoom/metadata/python/letsplot.yaml b/plots/scatter-brush-zoom/metadata/python/letsplot.yaml deleted file mode 100644 index ad217802be..0000000000 --- a/plots/scatter-brush-zoom/metadata/python/letsplot.yaml +++ /dev/null @@ -1,243 +0,0 @@ -library: letsplot -language: python -specification_id: scatter-brush-zoom -created: '2026-01-08T16:14:23Z' -updated: '2026-05-16T23:50:12Z' -generated_by: claude-haiku -workflow_run: 25975913573 -issue: 3295 -language_version: 3.13.13 -library_version: 4.9.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-brush-zoom/python/letsplot/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-brush-zoom/python/letsplot/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/scatter-brush-zoom/python/letsplot/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-brush-zoom/python/letsplot/plot-dark.html -quality_score: 93 -review: - strengths: - - 'Flawless theme adaptation: both light and dark renders are perfectly readable - with correct color tokens and no dark-on-dark or light-on-light text issues' - - 'Proper Okabe-Ito palette: first series is #009E73 (brand green); series follow - canonical order; colors are identical across themes (only chrome flips)' - - 'Effective selection visualization: brush rectangle with dashed outline, semi-transparent - fill, alpha-based point opacity, and selection count annotation clearly show interaction - pattern' - - 'Excellent typography scaling: font sizes appropriately scaled for 4800×2700 px - canvas' - - 'Clean code: simple procedural script with deterministic data generation' - - 'Interactive capabilities: HTML output provides zoom, pan, and hover tooltips - as specified' - weaknesses: - - Zoom capability is available in HTML but not visually hinted in static render; - consider adding a subtle callout or density-based sizing to suggest zoom potential - - Could demonstrate linked brushing or callback-driven filtering to fully showcase - letsplot's interactive ecosystem - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) on 4800×2700 px landscape canvas - Chrome: Title "scatter-brush-zoom · letsplot · anyplot.ai" in dark ink (#1A1A17); X/Y axis labels in dark ink; tick labels in secondary ink (#4A4A44); all clearly readable - Data: Four clusters (A–D) in Okabe-Ito colors (#009E73, #D55E00, #0072B2, #CC79A7); brush rectangle (dashed blue, semi-transparent fill) visible; selected points (alpha=0.9) vs unselected (alpha=0.4); selection count annotation "Brush Selection: 100 points selected" in blue - Legend: Category labels on right with Okabe-Ito colors - Legibility verdict: PASS - all text readable, no light-on-light issues, grid subtle - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) - Chrome: Title and axis labels in light ink (#F0EFE8); tick labels in secondary light ink (#B8B7B0); all clearly readable - Data: Okabe-Ito data colors identical to light render - Cluster A #009E73 (green) is especially visible; brush rectangle and selection annotation match light render; alpha variation still clear - Legend: Category labels visible with same Okabe-Ito colors - Legibility verdict: PASS - all text readable on dark background, no dark-on-dark failures, excellent contrast, brand green (#009E73) visible - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: Title (24pt), axes (20pt), ticks (16pt) explicitly set and fully - readable in both themes - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No text collisions; annotation and legend fit cleanly - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Points (size=5) clearly visible; alpha variation (0.4/0.9) effectively - distinguishes selection state; brush rectangle prominent - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette is CVD-safe; no red-green sole signal - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 4800×2700 px with excellent proportions and no cutoff - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Labels include units; title correctly formatted - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73; Okabe-Ito order 1-4; backgrounds #FAF8F1 (light) - #1A1A17 (dark) correct' - design_excellence: - score: 16 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 6 - max: 8 - passed: true - comment: Clean, professional polish with intentional Okabe-Ito palette and - thoughtful alpha-based visual hierarchy - - id: DE-02 - name: Visual Refinement - score: 5 - max: 6 - passed: true - comment: theme_minimal() removes top/right spines; subtle grid; generous whitespace - - id: DE-03 - name: Data Storytelling - score: 5 - max: 6 - passed: true - comment: Brush rectangle and annotation create clear focal point; opacity - variation reinforces state - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct interactive scatter plot with brush selection and zoom - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Brush rectangle visible; selection highlighting via alpha; count - annotation; HTML enables zoom/pan/hover - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: x/y axes correctly mapped; full data range shown; color mapped to - category - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title correctly formatted; legend labels match data - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Four clusters (400 total points) demonstrate scatter plot type and - categorical distinction - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Simulated clustered data is realistic and neutral - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: X (0-105) and Y (0-95) ranges sensible; point density appropriate - for interaction - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Straightforward procedural script; no unnecessary abstraction - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Deterministic via np.random.seed(42) - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports used; minimal and necessary - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: No fake interactivity; proper API usage; clean and readable - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png and plot-{THEME}.html with correct filenames - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Proper ggplot syntax, layer composition, scale functions, and theme - API - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Uses layer_tooltips() (letsplot-specific) and multiple geom layers; - could showcase more library-specific capabilities - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - layer-tooltips - - html-export - - annotations - - layer-composition - patterns: - - data-generation - - iteration-over-groups - dataprep: [] - styling: - - alpha-blending - - publication-ready diff --git a/plots/scatter-brush-zoom/metadata/python/plotly.yaml b/plots/scatter-brush-zoom/metadata/python/plotly.yaml deleted file mode 100644 index 50cde632e1..0000000000 --- a/plots/scatter-brush-zoom/metadata/python/plotly.yaml +++ /dev/null @@ -1,246 +0,0 @@ -library: plotly -language: python -specification_id: scatter-brush-zoom -created: '2026-01-08T16:04:00Z' -updated: '2026-05-16T23:50:24Z' -generated_by: claude-haiku -workflow_run: 25975682426 -issue: 3295 -language_version: 3.13.13 -library_version: 6.7.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-brush-zoom/python/plotly/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-brush-zoom/python/plotly/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/scatter-brush-zoom/python/plotly/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-brush-zoom/python/plotly/plot-dark.html -quality_score: 79 -review: - strengths: - - Effective cluster visualization with clear color differentiation - - Proper theme-adaptive rendering with identical data colors across light/dark themes - - Well-structured interactive features (brush selection, zoom, reset) with clear - instructions - - Good layout balance with appropriate margins and legend placement - - Reproducible code with explicit random seed for consistency - weaknesses: - - Output filenames must be theme-aware (plot-{THEME}.png/html) not bare plot.png/html - - Code does not read ANYPLOT_THEME environment variable to adapt colors and styling - dynamically - - Title colors and grid colors hardcoded instead of using theme tokens (INK, INK_SOFT, - GRID, PAGE_BG) - - Design elements lack sophistication - appears as generic library defaults without - custom refinement - - Data storytelling absent - clusters displayed but no visual hierarchy or emphasis - of insights - - Marker sizing could be optimized for the large canvas (14px is conservative for - 325 data points) - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) - correct per style guide - Chrome: Title, axis labels, ticks all clearly visible in dark text (#1A1A17 or similar) - Data: Four color-coded clusters (teal/cyan, orange, blue, magenta/pink) plus gray outliers; first series appears to be #009E73 (brand green/teal) - Legend: Clear labels for all groups with proper styling - Grid: Subtle and visible without competing with data - Legibility verdict: PASS - all text readable with good contrast - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) - correct per style guide - Chrome: All text switched to light colors; title, axis labels, ticks clearly visible against dark background - Data: Colors remain identical to light render (teal/cyan, orange, blue, magenta/pink for clusters; gray for outliers) - Grid: Subtle and visible - Legibility verdict: PASS - no dark-on-dark failures, all elements properly visible - Theme consistency: Confirmed - only chrome adapted, data colors unchanged between renders - criteria_checklist: - visual_quality: - score: 28 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: All text readable; font sizes explicitly set but could be slightly - larger for large canvas - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text or elements - - id: VQ-03 - name: Element Visibility - score: 5 - max: 6 - passed: true - comment: Markers visible and well-distributed; could optimize size for 325 - data points - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Good contrast between colors; CVD-safe palette - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Excellent layout; plot fills 70-75% of canvas with balanced margins - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Descriptive labels with units (Temperature °C, Efficiency %) - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: Rendered colors appear to follow Okabe-Ito; backgrounds and text - colors theme-correct in both renders - design_excellence: - score: 10 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: false - comment: Well-configured library defaults; lacks custom design decisions or - exceptional polish - - id: DE-02 - name: Visual Refinement - score: 2 - max: 6 - passed: false - comment: Library defaults with minimal customization; could remove spines - and refine grid - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: false - comment: Clusters clearly distinguished through color; some visual hierarchy - through grouping and outlier separation - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct scatter plot with all required interactive subtypes - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Brush selection, zoom, and reset all specified in code and UI; cannot - fully verify interactivity in static PNG - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X/Y correctly assigned; all data visible; categorical color mapping - correct - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title format correct; legend labels match data groups - data_quality: - score: 13 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'Shows all plot type aspects: multiple clusters, outliers, good density - variation' - - id: DQ-02 - name: Realistic Context - score: 3 - max: 5 - passed: true - comment: Rendered labels (Temperature, Efficiency) suggest realistic scenario; - code uses generic X/Y labels - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Data ranges (0-100°C, 0-100%) are realistic and sensible - code_quality: - score: 9 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Clean linear structure: imports → data → plot → save; no unnecessary - functions' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) ensures deterministic output - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only numpy and plotly imported; both used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Appropriate complexity; no over-engineering or fake functionality - - id: CQ-05 - name: Output & API - score: 0 - max: 1 - passed: false - comment: 'CRITICAL: Saves as ''plot.png'' and ''plot.html'' instead of theme-aware - ''plot-{THEME}.png'' and ''plot-{THEME}.html''; does not read ANYPLOT_THEME - environment variable' - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Good use of go.Figure, go.Scatter, update_layout, update_traces; - follows Plotly conventions - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Uses Plotly-specific interactive features (dragmode, selection styling, - modebar config); fairly standard usage - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - hover-tooltips - - html-export - patterns: - - data-generation - - iteration-over-groups - dataprep: [] - styling: [] diff --git a/plots/scatter-brush-zoom/metadata/python/pygal.yaml b/plots/scatter-brush-zoom/metadata/python/pygal.yaml deleted file mode 100644 index aa6c3b1c6c..0000000000 --- a/plots/scatter-brush-zoom/metadata/python/pygal.yaml +++ /dev/null @@ -1,224 +0,0 @@ -library: pygal -language: python -specification_id: scatter-brush-zoom -created: '2026-05-16T23:33:51Z' -updated: '2026-05-16T23:41:56Z' -generated_by: claude-haiku -workflow_run: 25975836381 -issue: 3295 -language_version: 3.13.13 -library_version: 3.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-brush-zoom/python/pygal/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-brush-zoom/python/pygal/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/scatter-brush-zoom/python/pygal/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-brush-zoom/python/pygal/plot-dark.html -quality_score: 83 -review: - strengths: - - Excellent visual quality with flawless theme adaptation across light and dark - renders - - Perfect palette compliance using Okabe-Ito with brand green as first series - - All text fully readable in both themes with no contrast issues - - Clean, reproducible code with proper imports and output format - - Three distinct clusters demonstrate data distribution clearly - - Proper legend and axis labeling - weaknesses: - - Interactive features (brush selection, zoom, reset button, point highlighting) - not demonstrated in static PNG - - 'Design excellence is moderate: generic styling without visual refinement or spine - removal' - - 'Limited use of pygal-distinctive features: code doesn''t showcase interactive - callbacks, tooltips, or event handlers' - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) with subtle gray grid lines - Chrome: Title "scatter-brush-zoom · pygal · anyplot.ai" clearly visible in dark text at top. Axis labels "X Axis" and "Y Axis" positioned properly. Tick labels on both axes are dark and fully readable. Legend in upper left shows Group A, B, C labels in dark text. - Data: Three clusters colored with Okabe-Ito palette - Group A in brand green (#009E73), Group B in orange (#D55E00), Group C in blue (#0072B2). All 350+ points are clearly distinguishable with no overlap issues. Dots are appropriately sized (size 5). - Legibility verdict: PASS - All text elements (title, labels, ticks, legend) have excellent contrast against the light background. No legibility issues detected. - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) with subtle light grid lines - Chrome: Title is rendered in light text (#F0EFE8) and is fully readable. Axis labels and tick labels are also light-colored with good contrast. Legend text is light and clearly visible. No dark-on-dark failures detected. - Data: Identical colors to light render - Groups A, B, C maintain their respective Okabe-Ito colors. All data points visible with same clarity as light render. Confirms proper theme-independent color handling. - Legibility verdict: PASS - All chrome elements have correct theme adaptation with light text on dark background. No dark-on-dark collisions. Grid is subtle but visible. Data colors are identical to light render, confirming theme-correct implementation. - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All text sizes explicitly set and readable in both themes - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No collisions between text or elements - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: All 350+ points clearly distinguishable - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette, colorblind-safe - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 4800x2700, good margins and proportions - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Correct title format and labeled axes - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: Background colors correct, Okabe-Ito order, theme-adaptive chrome - design_excellence: - score: 11 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: false - comment: Custom style with careful font sizing, but somewhat standard - - id: DE-02 - name: Visual Refinement - score: 3 - max: 6 - passed: false - comment: Subtle grid via INK_MUTED, minimal customization - - id: DE-03 - name: Data Storytelling - score: 3 - max: 6 - passed: false - comment: Color creates visual hierarchy through clustering - spec_compliance: - score: 13 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct scatter plot (pygal.XY) - - id: SC-02 - name: Required Features - score: 2 - max: 4 - passed: false - comment: Scatter with groups shown; brush selection, zoom, reset button, selection - highlighting not demonstrated - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Axes correctly mapped, full data range visible - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Proper format and labeling - data_quality: - score: 13 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 4 - max: 6 - passed: false - comment: Cluster demonstration strong, interactive features not shown - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Realistic clusters, neutral data, sensible ranges - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Good point density and readability - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Simple, direct implementation - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) ensures deterministic output - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only used imports - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: No fake UI; real interactive HTML - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Correct plot-{THEME} format - library_mastery: - score: 6 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Good use of Style, add(), render methods - - id: LM-02 - name: Distinctive Features - score: 2 - max: 5 - passed: false - comment: Basic pygal features; doesn't showcase interactive callbacks or event - handlers - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - html-export - patterns: - - data-generation - - iteration-over-groups - dataprep: [] - styling: [] diff --git a/plots/scatter-brush-zoom/specification.md b/plots/scatter-brush-zoom/specification.md deleted file mode 100644 index 367b4363bd..0000000000 --- a/plots/scatter-brush-zoom/specification.md +++ /dev/null @@ -1,31 +0,0 @@ -# scatter-brush-zoom: Interactive Scatter Plot with Brush Selection and Zoom - -## Description - -An interactive scatter plot enabling brush selection (click and drag to select a rectangular region) and zoom functionality. Users can draw selection boxes to highlight or filter data points, zoom into specific areas for detailed analysis, and use the selection to trigger downstream actions like filtering or linked views. This interaction pattern is fundamental for exploratory data analysis and brushing-and-linking visualizations. - -## Applications - -- Exploratory data analysis where users need to select outliers or clusters for further investigation -- Linked-view dashboards where brushing on a scatter plot filters data in other charts -- Financial analysis selecting specific time periods and value ranges from large datasets -- Scientific data exploration identifying regions of interest in multi-dimensional datasets - -## Data - -- `x` (numeric) - Values for the horizontal axis -- `y` (numeric) - Values for the vertical axis -- `color` (categorical, optional) - Category for color-coding different groups -- `label` (string, optional) - Point labels for identification -- Size: 50-1000 points (interactivity handles larger datasets well) -- Example: Simulated 2D data with multiple clusters or real dataset with continuous variables - -## Notes - -- Brush selection should draw a visible rectangle while dragging -- Selected points should be visually highlighted (color change, opacity, or border) -- Zoom can be triggered via mouse wheel, double-click, or toolbar buttons -- Include a reset/clear selection button to deselect all points -- For libraries supporting linked selections, demonstrate how selection state can be accessed -- Pan functionality should be available when zoomed in -- Consider showing count of selected points in a status area diff --git a/plots/scatter-brush-zoom/specification.yaml b/plots/scatter-brush-zoom/specification.yaml deleted file mode 100644 index 76d79fd48a..0000000000 --- a/plots/scatter-brush-zoom/specification.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# Specification-level metadata for scatter-brush-zoom -# Auto-synced to PostgreSQL on push to main - -spec_id: scatter-brush-zoom -title: Interactive Scatter Plot with Brush Selection and Zoom - -# Specification tracking -created: 2026-01-08T15:54:50Z -updated: 2026-05-16T23:16:33Z -issue: 3295 -suggested: MarkusNeusinger - -# Classification tags (applies to all library implementations) -tags: - plot_type: - - scatter - data_type: - - numeric - - continuous - domain: - - general - - statistics - features: - - interactive - - 2d - - correlation diff --git a/plots/scatter-matrix-interactive/implementations/python/altair.py b/plots/scatter-matrix-interactive/implementations/python/altair.py deleted file mode 100644 index 07ac793f09..0000000000 --- a/plots/scatter-matrix-interactive/implementations/python/altair.py +++ /dev/null @@ -1,281 +0,0 @@ -""" anyplot.ai -scatter-matrix-interactive: Interactive Scatter Plot Matrix (SPLOM) -Library: altair 6.1.0 | Python 3.13.13 -Quality: 90/100 | Updated: 2026-05-18 -""" - -import os -import sys - - -# Remove current directory from path to avoid shadowing altair module -script_dir = os.path.dirname(os.path.abspath(__file__)) -sys.path = [p for p in sys.path if os.path.abspath(p) != script_dir] - -import altair as alt # noqa: E402 -import numpy as np # noqa: E402 -import pandas as pd # noqa: E402 - - -# 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" - -# Okabe-Ito palette -IMPRINT = ["#009E73", "#C475FD", "#4467A3"] - -# Data - Synthetic Iris-like multivariate data -np.random.seed(42) - -n_per_species = 50 -species_data = [] - -# Species A: small petals, medium sepals -species_data.append( - pd.DataFrame( - { - "Sepal Length (cm)": np.random.normal(5.0, 0.35, n_per_species), - "Sepal Width (cm)": np.random.normal(3.4, 0.38, n_per_species), - "Petal Length (cm)": np.random.normal(1.5, 0.17, n_per_species), - "Petal Width (cm)": np.random.normal(0.25, 0.1, n_per_species), - "Species": "setosa", - } - ) -) - -# Species B: medium petals, medium sepals -species_data.append( - pd.DataFrame( - { - "Sepal Length (cm)": np.random.normal(5.9, 0.52, n_per_species), - "Sepal Width (cm)": np.random.normal(2.8, 0.31, n_per_species), - "Petal Length (cm)": np.random.normal(4.3, 0.47, n_per_species), - "Petal Width (cm)": np.random.normal(1.3, 0.2, n_per_species), - "Species": "versicolor", - } - ) -) - -# Species C: large petals, large sepals -species_data.append( - pd.DataFrame( - { - "Sepal Length (cm)": np.random.normal(6.6, 0.64, n_per_species), - "Sepal Width (cm)": np.random.normal(3.0, 0.32, n_per_species), - "Petal Length (cm)": np.random.normal(5.5, 0.55, n_per_species), - "Petal Width (cm)": np.random.normal(2.0, 0.27, n_per_species), - "Species": "virginica", - } - ) -) - -df = pd.concat(species_data, ignore_index=True) - -variables = ["Sepal Length (cm)", "Sepal Width (cm)", "Petal Length (cm)", "Petal Width (cm)"] - -# Static PNG version -rows = [] -for row_idx, y_var in enumerate(variables): - row_charts = [] - for col_idx, x_var in enumerate(variables): - if x_var == y_var: - # Diagonal: KDE/histogram for each variable - chart = ( - alt.Chart(df) - .transform_density(x_var, as_=[x_var, "density"], groupby=["Species"]) - .mark_area(opacity=0.6) - .encode( - x=alt.X( - f"{x_var}:Q", - title=x_var if row_idx == len(variables) - 1 else "", - axis=alt.Axis( - labelFontSize=18, - titleFontSize=22, - labelAngle=-45, - labelColor=INK_SOFT, - titleColor=INK, - domainColor=INK_SOFT, - tickColor=INK_SOFT, - gridColor=INK, - gridOpacity=0.10, - ), - ), - y=alt.Y( - "density:Q", - title="Density" if col_idx == 0 else "", - axis=alt.Axis( - labelFontSize=18, - titleFontSize=22, - labelColor=INK_SOFT, - titleColor=INK, - domainColor=INK_SOFT, - tickColor=INK_SOFT, - gridColor=INK, - gridOpacity=0.10, - ), - ), - color=alt.Color( - "Species:N", - scale=alt.Scale(domain=["setosa", "versicolor", "virginica"], range=IMPRINT), - legend=None, - ), - ) - .properties(width=220, height=220) - ) - else: - # Off-diagonal: scatter plot - chart = ( - alt.Chart(df) - .mark_point(size=80, filled=True, opacity=0.7) - .encode( - x=alt.X( - f"{x_var}:Q", - title=x_var if row_idx == len(variables) - 1 else "", - axis=alt.Axis( - labelFontSize=18, - titleFontSize=22, - labelAngle=-45, - labelColor=INK_SOFT, - titleColor=INK, - domainColor=INK_SOFT, - tickColor=INK_SOFT, - gridColor=INK, - gridOpacity=0.10, - ), - ), - y=alt.Y( - f"{y_var}:Q", - title=y_var if col_idx == 0 else "", - axis=alt.Axis( - labelFontSize=18, - titleFontSize=22, - labelColor=INK_SOFT, - titleColor=INK, - domainColor=INK_SOFT, - tickColor=INK_SOFT, - gridColor=INK, - gridOpacity=0.10, - ), - ), - color=alt.Color( - "Species:N", - scale=alt.Scale(domain=["setosa", "versicolor", "virginica"], range=IMPRINT), - legend=None, - ), - tooltip=["Species:N"] + [alt.Tooltip(v, format=".2f") for v in variables], - ) - .properties(width=220, height=220) - ) - row_charts.append(chart) - rows.append(alt.hconcat(*row_charts, spacing=10)) - -# Create legend as a separate chart -legend_chart = ( - alt.Chart(df) - .mark_point(size=150, filled=True) - .encode( - y=alt.Y("Species:N", title="", axis=alt.Axis(labelFontSize=18, orient="right")), - color=alt.Color( - "Species:N", scale=alt.Scale(domain=["setosa", "versicolor", "virginica"], range=IMPRINT), legend=None - ), - ) - .properties(width=50, height=150, title=alt.Title("Species", fontSize=22)) -) - -# Combine all rows vertically -matrix = alt.vconcat(*rows, spacing=10).properties( - title=alt.Title( - "scatter-matrix-interactive · python · altair · anyplot.ai", - fontSize=28, - anchor="middle", - color=INK, - subtitle="Interactive Scatter Plot Matrix", - subtitleFontSize=18, - ) -) - -# Final static chart with legend and styling -static_chart = ( - alt.hconcat(matrix, legend_chart, spacing=40) - .properties(background=PAGE_BG) - .configure_axis(grid=True, gridOpacity=0.10) - .configure_view(strokeWidth=0, fill=PAGE_BG) - .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT) -) - -# Save PNG version (static) -static_chart.save(f"plot-{THEME}.png", scale_factor=3.0) - -# Interactive HTML version with brush selection -brush = alt.selection_interval(name="brush", resolve="global") - -interactive_chart = ( - alt.Chart(df) - .mark_point(size=80, filled=True) - .encode( - alt.X( - alt.repeat("column"), - type="quantitative", - axis=alt.Axis( - labelFontSize=18, - titleFontSize=22, - labelAngle=-45, - labelColor=INK_SOFT, - titleColor=INK, - domainColor=INK_SOFT, - tickColor=INK_SOFT, - gridColor=INK, - gridOpacity=0.10, - ), - ), - alt.Y( - alt.repeat("row"), - type="quantitative", - axis=alt.Axis( - labelFontSize=18, - titleFontSize=22, - labelColor=INK_SOFT, - titleColor=INK, - domainColor=INK_SOFT, - tickColor=INK_SOFT, - gridColor=INK, - gridOpacity=0.10, - ), - ), - color=alt.condition( - brush, - alt.Color( - "Species:N", - scale=alt.Scale(domain=["setosa", "versicolor", "virginica"], range=IMPRINT), - legend=alt.Legend(titleFontSize=18, labelFontSize=16, orient="right", title="Species"), - ), - alt.value(INK_SOFT), - ), - opacity=alt.condition(brush, alt.value(0.8), alt.value(0.15)), - tooltip=["Species:N"] + [alt.Tooltip(v, format=".2f") for v in variables], - ) - .properties(width=180, height=180) - .add_params(brush) - .repeat(row=variables, column=variables) - .properties( - background=PAGE_BG, - title=alt.Title( - "scatter-matrix-interactive · python · altair · anyplot.ai", - fontSize=28, - anchor="middle", - color=INK, - subtitle="Brush any subplot to select points across all panels", - subtitleFontSize=18, - ), - ) - .configure_axis(grid=True, gridOpacity=0.10) - .configure_view(strokeWidth=0, fill=PAGE_BG) - .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK) - .configure_title(color=INK) -) - -# Save HTML version (interactive) -interactive_chart.save(f"plot-{THEME}.html") diff --git a/plots/scatter-matrix-interactive/implementations/python/bokeh.py b/plots/scatter-matrix-interactive/implementations/python/bokeh.py deleted file mode 100644 index 03481a92a9..0000000000 --- a/plots/scatter-matrix-interactive/implementations/python/bokeh.py +++ /dev/null @@ -1,223 +0,0 @@ -""" anyplot.ai -scatter-matrix-interactive: Interactive Scatter Plot Matrix (SPLOM) -Library: bokeh 3.9.0 | Python 3.13.13 -Quality: 91/100 | Updated: 2026-05-18 -""" - -import os -import time -from pathlib import Path - -import numpy as np -import pandas as pd -from bokeh.io import output_file, save -from bokeh.layouts import column, gridplot -from bokeh.models import ColumnDataSource, Div, HoverTool -from bokeh.plotting import figure -from selenium import webdriver -from selenium.webdriver.chrome.options import Options -from sklearn.datasets import load_iris - - -# 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" - -# Okabe-Ito palette -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233"] - -# Data - Iris dataset -iris = load_iris() -df = pd.DataFrame(iris.data, columns=["Sepal Length", "Sepal Width", "Petal Length", "Petal Width"]) -df["species"] = [iris.target_names[t] for t in iris.target] - -# Color mapping for species using Okabe-Ito -species_names = ["setosa", "versicolor", "virginica"] -color_map = {name: IMPRINT[i] for i, name in enumerate(species_names)} -df["color"] = df["species"].map(color_map) - -# Use 4 variables for the scatter matrix -variables = ["Sepal Length", "Sepal Width", "Petal Length", "Petal Width"] -n_vars = len(variables) - -# Create a shared ColumnDataSource for linked brushing -source = ColumnDataSource( - data={ - "sepal_length": df["Sepal Length"], - "sepal_width": df["Sepal Width"], - "petal_length": df["Petal Length"], - "petal_width": df["Petal Width"], - "color": df["color"], - "species": df["species"], - } -) - -# Column name mapping -col_map = { - "Sepal Length": "sepal_length", - "Sepal Width": "sepal_width", - "Petal Length": "petal_length", - "Petal Width": "petal_width", -} - -# Create grid of plots -plots = [] -cell_size = 900 # Each cell 900x900, total ~3600x3600 for 4x4 grid - -TOOLS = "box_select,pan,wheel_zoom,reset" - -for i, var_y in enumerate(variables): - row = [] - for j, var_x in enumerate(variables): - p = figure(width=cell_size, height=cell_size, tools=TOOLS, active_drag="box_select") - - # Set theme colors - p.background_fill_color = PAGE_BG - p.border_fill_color = PAGE_BG - p.outline_line_color = INK_SOFT - - if i == j: - # Diagonal - histogram - hist, edges = np.histogram(df[var_x], bins=20) - p.quad( - top=hist, - bottom=0, - left=edges[:-1], - right=edges[1:], - fill_color=IMPRINT[0], - line_color=PAGE_BG, - alpha=0.7, - ) - # Variable name as title - p.title.text = var_x - p.title.text_font_size = "22pt" - p.title.text_color = INK - p.title.align = "center" - else: - # Off-diagonal - scatter plot with linked selection - p.scatter( - x=col_map[var_x], - y=col_map[var_y], - source=source, - size=15, - fill_color="color", - line_color=PAGE_BG, - line_width=1, - alpha=0.7, - selection_fill_alpha=0.9, - selection_line_color=INK, - selection_line_width=2, - nonselection_fill_alpha=0.15, - nonselection_fill_color=INK_SOFT, - nonselection_line_color=INK_SOFT, - nonselection_line_alpha=0.3, - ) - - # Add hover tooltip - hover = HoverTool( - tooltips=[ - ("Species", "@species"), - (f"{var_x}", f"@{col_map[var_x]}" + "{0.00}"), - (f"{var_y}", f"@{col_map[var_y]}" + "{0.00}"), - ] - ) - p.add_tools(hover) - - # Axis labels - only on edges - if i == n_vars - 1: # Bottom row - p.xaxis.axis_label = var_x - p.xaxis.axis_label_text_font_size = "22pt" - p.xaxis.axis_label_text_color = INK - else: - p.xaxis.visible = False - - if j == 0: # Left column - p.yaxis.axis_label = var_y - p.yaxis.axis_label_text_font_size = "22pt" - p.yaxis.axis_label_text_color = INK - else: - p.yaxis.visible = False - - # Tick label styling - p.xaxis.major_label_text_font_size = "18pt" - p.yaxis.major_label_text_font_size = "18pt" - p.xaxis.major_label_text_color = INK_SOFT - p.yaxis.major_label_text_color = INK_SOFT - - # Axis line colors - p.xaxis.axis_line_color = INK_SOFT - p.yaxis.axis_line_color = INK_SOFT - p.xaxis.major_tick_line_color = INK_SOFT - p.yaxis.major_tick_line_color = INK_SOFT - - # Grid styling - subtle (10% opacity) - p.xgrid.grid_line_color = INK - p.ygrid.grid_line_color = INK - p.xgrid.grid_line_alpha = 0.10 - p.ygrid.grid_line_alpha = 0.10 - - row.append(p) - plots.append(row) - -# Add legend to top-right scatter plot -for _idx, (species, color) in enumerate(color_map.items()): - species_source = ColumnDataSource( - data={ - "x": df[df["species"] == species]["Sepal Width"].values[:1], - "y": df[df["species"] == species]["Sepal Length"].values[:1], - } - ) - plots[0][1].scatter( - x="x", - y="y", - source=species_source, - size=15, - fill_color=color, - line_color=PAGE_BG, - alpha=0.9, - legend_label=species.capitalize(), - ) - -plots[0][1].legend.location = "top_right" -plots[0][1].legend.label_text_font_size = "16pt" -plots[0][1].legend.background_fill_color = ELEVATED_BG -plots[0][1].legend.border_line_color = INK_SOFT -plots[0][1].legend.label_text_color = INK_SOFT - -# Create grid layout -grid = gridplot(plots, merge_tools=True, toolbar_location="right") - -# Title -title_div = Div( - text=f"

scatter-matrix-interactive · python · bokeh · anyplot.ai

", - width=3600, -) - -# Combine title and grid -layout = column(title_div, grid) - -# Save as HTML -output_file(f"plot-{THEME}.html") -save(layout) - -# Screenshot with headless Chrome -W, H = 3600, 3600 -opts = Options() -for arg in ( - "--headless=new", - "--no-sandbox", - "--disable-dev-shm-usage", - "--disable-gpu", - f"--window-size={W},{H}", - "--hide-scrollbars", -): - opts.add_argument(arg) -driver = webdriver.Chrome(options=opts) -driver.set_window_size(W, H) -driver.get(f"file://{Path(f'plot-{THEME}.html').resolve()}") -time.sleep(3) -driver.save_screenshot(f"plot-{THEME}.png") -driver.quit() diff --git a/plots/scatter-matrix-interactive/implementations/python/letsplot.py b/plots/scatter-matrix-interactive/implementations/python/letsplot.py deleted file mode 100644 index 3dfc0a1e32..0000000000 --- a/plots/scatter-matrix-interactive/implementations/python/letsplot.py +++ /dev/null @@ -1,222 +0,0 @@ -""" anyplot.ai -scatter-matrix-interactive: Interactive Scatter Plot Matrix (SPLOM) -Library: letsplot 4.9.0 | Python 3.13.13 -Quality: 81/100 | Updated: 2026-05-18 -""" - -import os - -import numpy as np -import pandas as pd -from lets_plot import ( - LetsPlot, - aes, - element_blank, - element_line, - element_rect, - element_text, - geom_histogram, - geom_point, - ggbunch, - ggplot, - ggsave, - ggsize, - ggtitle, - labs, - layer_tooltips, - scale_color_manual, - scale_fill_manual, - theme, - theme_minimal, -) - - -LetsPlot.setup_html() - -# Theme tokens -THEME = os.getenv("ANYPLOT_THEME", "light") -PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" -INK = "#1A1A17" if THEME == "light" else "#F0EFE8" -INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" -GRID_COLOR = "#E8E5DB" if THEME == "light" else "#2A2925" -ACCENT_COLOR = "#009E73" - -# Okabe-Ito palette - first series (#009E73) is always first -IMPRINT = ["#009E73", "#C475FD", "#4467A3"] - -# Data - Synthetic iris-like dataset (4 numeric variables, 150 points) -np.random.seed(42) - -n_per_species = 50 - -species_a = pd.DataFrame( - { - "Sepal Length": np.random.normal(5.0, 0.35, n_per_species), - "Sepal Width": np.random.normal(3.4, 0.38, n_per_species), - "Petal Length": np.random.normal(1.5, 0.17, n_per_species), - "Petal Width": np.random.normal(0.25, 0.10, n_per_species), - "Species": "Setosa", - } -) - -base_b = np.random.normal(0, 1, n_per_species) -species_b = pd.DataFrame( - { - "Sepal Length": 5.9 + 0.5 * base_b + np.random.normal(0, 0.2, n_per_species), - "Sepal Width": 2.8 + 0.3 * base_b + np.random.normal(0, 0.2, n_per_species), - "Petal Length": 4.3 + 0.5 * base_b + np.random.normal(0, 0.3, n_per_species), - "Petal Width": 1.3 + 0.2 * base_b + np.random.normal(0, 0.15, n_per_species), - "Species": "Versicolor", - } -) - -base_c = np.random.normal(0, 1, n_per_species) -species_c = pd.DataFrame( - { - "Sepal Length": 6.6 + 0.6 * base_c + np.random.normal(0, 0.3, n_per_species), - "Sepal Width": 3.0 + 0.3 * base_c + np.random.normal(0, 0.25, n_per_species), - "Petal Length": 5.6 + 0.6 * base_c + np.random.normal(0, 0.3, n_per_species), - "Petal Width": 2.0 + 0.3 * base_c + np.random.normal(0, 0.2, n_per_species), - "Species": "Virginica", - } -) - -df = pd.concat([species_a, species_b, species_c], ignore_index=True) - -variables = ["Sepal Length", "Sepal Width", "Petal Length", "Petal Width"] -n = len(variables) - -# Create individual plots for the 4x4 matrix -plots = [] -for i, var_y in enumerate(variables): - for j, var_x in enumerate(variables): - show_x_label = i == n - 1 - show_y_label = j == 0 - - if i == j: - # Diagonal: histogram showing distribution - p = ( - ggplot(df, aes(x=var_x, fill="Species")) - + geom_histogram(alpha=0.75, bins=16, position="identity") - + scale_fill_manual(values=IMPRINT) - + labs(x=var_x if show_x_label else "", y="") - + theme_minimal() - + theme( - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - panel_background=element_rect(fill=PAGE_BG), - panel_grid_major=element_line(color=GRID_COLOR, size=0.25), - panel_grid_minor=element_line(color=GRID_COLOR, size=0.12), - axis_title_x=element_text(size=16, color=INK) if show_x_label else element_blank(), - axis_title_y=element_blank(), - axis_text=element_text(size=13, color=INK_SOFT), - axis_line=element_line(color=GRID_COLOR, size=0.4), - axis_ticks=element_line(color=GRID_COLOR, size=0.3), - legend_position="none", - plot_margin=[6, 6, 6, 6], - ) - ) - else: - # Off-diagonal: scatter plot with tooltips for interactivity - p = ( - ggplot(df, aes(x=var_x, y=var_y, color="Species", fill="Species")) - + geom_point( - size=4.5, - alpha=0.72, - shape=21, - stroke=0.6, - tooltips=layer_tooltips() - .line("Species: @Species") - .line(f"{var_x}: @{{{var_x}}}") - .line(f"{var_y}: @{{{var_y}}}"), - ) - + scale_color_manual(values=IMPRINT) - + scale_fill_manual(values=IMPRINT) - + labs(x=var_x if show_x_label else "", y=var_y if show_y_label else "") - + theme_minimal() - + theme( - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - panel_background=element_rect(fill=PAGE_BG), - panel_grid_major=element_line(color=GRID_COLOR, size=0.25), - panel_grid_minor=element_line(color=GRID_COLOR, size=0.12), - axis_title_x=element_text(size=16, color=INK) if show_x_label else element_blank(), - axis_title_y=element_text(size=16, color=INK) if show_y_label else element_blank(), - axis_text=element_text(size=13, color=INK_SOFT), - axis_line=element_line(color=GRID_COLOR, size=0.4), - axis_ticks=element_line(color=GRID_COLOR, size=0.3), - legend_position="none", - plot_margin=[6, 6, 6, 6], - ) - ) - plots.append(p) - -# Calculate regions for ggbunch -title_height = 0.06 -legend_height = 0.06 -grid_height = 1.0 - title_height - legend_height -cell_size = grid_height / n - -# Title plot -title_df = pd.DataFrame({"x": [0], "y": [0]}) -title_plot = ( - ggplot(title_df, aes(x="x", y="y")) - + geom_point(alpha=0) - + ggtitle("scatter-matrix-interactive · python · letsplot · anyplot.ai") - + theme_minimal() - + theme( - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - plot_title=element_text(size=32, color=INK, hjust=0.5), - axis_line=element_blank(), - axis_text=element_blank(), - axis_ticks=element_blank(), - axis_title=element_blank(), - panel_grid=element_blank(), - plot_margin=[8, 10, 4, 10], - ) -) - -# Legend plot -legend_df = pd.DataFrame({"x": [1, 2, 3], "y": [0, 0, 0], "Species": ["Setosa", "Versicolor", "Virginica"]}) -legend_plot = ( - ggplot(legend_df, aes(x="x", y="y", color="Species", fill="Species")) - + geom_point(size=8, shape=21, alpha=0.85, stroke=0.5) - + scale_color_manual(values=IMPRINT) - + scale_fill_manual(values=IMPRINT) - + theme_minimal() - + theme( - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - legend_position="bottom", - legend_direction="horizontal", - legend_title=element_text(size=18, color=INK), - legend_text=element_text(size=16, color=INK), - axis_line=element_blank(), - axis_text=element_blank(), - axis_ticks=element_blank(), - axis_title=element_blank(), - panel_grid=element_blank(), - plot_margin=[4, 10, 8, 10], - ) -) - -# Build final layout -final_plots = [title_plot] -final_plots.extend(plots) -final_plots.append(legend_plot) - -final_regions = [] -final_regions.append((0, 0, 1, title_height, 0, 0)) - -for idx in range(n * n): - row = idx // n - col = idx % n - x = col * cell_size + 0.02 - y = title_height + row * cell_size - final_regions.append((x, y, cell_size, cell_size, 0, 0)) - -final_regions.append((0.25, 1.0 - legend_height, 0.5, legend_height, 0, 0)) - -# Combine all plots -final_plot = ggbunch(final_plots, final_regions) + ggsize(1600, 1600) - -# Save output -ggsave(final_plot, f"plot-{THEME}.png", path=".", scale=3) -ggsave(final_plot, f"plot-{THEME}.html", path=".") diff --git a/plots/scatter-matrix-interactive/implementations/python/matplotlib.py b/plots/scatter-matrix-interactive/implementations/python/matplotlib.py deleted file mode 100644 index bdd2a05d72..0000000000 --- a/plots/scatter-matrix-interactive/implementations/python/matplotlib.py +++ /dev/null @@ -1,115 +0,0 @@ -""" anyplot.ai -scatter-matrix-interactive: Interactive Scatter Plot Matrix (SPLOM) -Library: matplotlib 3.10.9 | Python 3.13.13 -Quality: 82/100 | Updated: 2026-05-18 -""" - -import os - -import matplotlib.pyplot as plt -from sklearn.datasets import load_iris - - -# 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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -# Okabe-Ito palette (positions 1-3) -COLORS = ["#009E73", "#C475FD", "#4467A3"] - -# Data - Iris dataset (4 numeric variables, 150 samples) -iris = load_iris() -data = iris.data -feature_names = ["Sepal Length\n(cm)", "Sepal Width\n(cm)", "Petal Length\n(cm)", "Petal Width\n(cm)"] -species = iris.target -species_names = ["Setosa", "Versicolor", "Virginica"] - -n_vars = len(feature_names) - -# Create figure - square format for matrix (12x12 inches at 300 dpi = 3600x3600) -fig, axes = plt.subplots(n_vars, n_vars, figsize=(12, 12), facecolor=PAGE_BG) - -# Plot scatter matrix -for i in range(n_vars): - for j in range(n_vars): - ax = axes[i, j] - ax.set_facecolor(PAGE_BG) - - if i == j: - # Diagonal: histograms for each species - for k, (name, color) in enumerate(zip(species_names, COLORS, strict=True)): - mask = species == k - ax.hist(data[mask, i], bins=12, alpha=0.65, color=color, edgecolor="white", linewidth=0.8, label=name) - else: - # Off-diagonal: scatter plots - for k, (name, color) in enumerate(zip(species_names, COLORS, strict=True)): - mask = species == k - ax.scatter( - data[mask, j], - data[mask, i], - c=color, - alpha=0.7, - s=120, - edgecolors="white", - linewidth=0.8, - label=name, - ) - - # Labels on edges only - if i == n_vars - 1: - ax.set_xlabel(feature_names[j], fontsize=18, fontweight="medium", color=INK) - else: - ax.set_xticklabels([]) - - if j == 0: - ax.set_ylabel(feature_names[i], fontsize=18, fontweight="medium", color=INK) - else: - ax.set_yticklabels([]) - - ax.tick_params(axis="both", labelsize=14, colors=INK_SOFT) - ax.grid(True, alpha=0.12, linestyle="-", linewidth=0.6, color=INK) - - # Theme-adaptive spines - for spine in ax.spines.values(): - spine.set_color(INK_SOFT) - spine.set_linewidth(0.8) - -# Title -fig.suptitle("Iris Dataset · scatter-matrix-interactive", fontsize=26, fontweight="bold", y=0.98, color=INK) - -# Single legend for entire figure -handles, labels = axes[0, 1].get_legend_handles_labels() -leg = fig.legend( - handles, - labels, - loc="upper center", - fontsize=16, - ncol=3, - bbox_to_anchor=(0.5, 0.965), - framealpha=0.95, - markerscale=1.2, -) -if leg: - leg.get_frame().set_facecolor(ELEVATED_BG) - leg.get_frame().set_edgecolor(INK_SOFT) - leg.get_frame().set_linewidth(0.8) - for text in leg.get_texts(): - text.set_color(INK_SOFT) - -# Note about interactivity limitation (subtle) -fig.text( - 0.5, - 0.005, - "Note: matplotlib produces static visualization. Use Plotly, Bokeh, or Altair for linked brushing and interaction.", - ha="center", - fontsize=12, - style="italic", - color=INK_MUTED, -) - -plt.tight_layout(rect=[0, 0.03, 1, 0.96]) -plt.savefig(f"plot-{THEME}.png", dpi=300, bbox_inches="tight", facecolor=PAGE_BG) diff --git a/plots/scatter-matrix-interactive/implementations/python/plotly.py b/plots/scatter-matrix-interactive/implementations/python/plotly.py deleted file mode 100644 index 5841fc0da5..0000000000 --- a/plots/scatter-matrix-interactive/implementations/python/plotly.py +++ /dev/null @@ -1,148 +0,0 @@ -""" anyplot.ai -scatter-matrix-interactive: Interactive Scatter Plot Matrix (SPLOM) -Library: plotly 6.7.0 | Python 3.13.13 -Quality: 90/100 | Updated: 2026-05-18 -""" - -import os - -import plotly.graph_objects as go -import seaborn as sns -from plotly.subplots import make_subplots - - -# 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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" -GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)" -GRID_LIGHT = "rgba(26,26,23,0.05)" if THEME == "light" else "rgba(240,239,232,0.05)" - -# Okabe-Ito palette -IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233"] - -# Data - Using Iris dataset for multivariate exploration -iris = sns.load_dataset("iris") - -# Column names for cleaner code -cols = ["sepal_length", "sepal_width", "petal_length", "petal_width"] -labels = ["Sepal Length (cm)", "Sepal Width (cm)", "Petal Length (cm)", "Petal Width (cm)"] - -# Capitalize species names for display -iris["species"] = iris["species"].str.capitalize() - -# Species info -species_list = ["Setosa", "Versicolor", "Virginica"] -color_map = {s: IMPRINT[i] for i, s in enumerate(species_list)} - -# Create 4x4 subplot grid -n = len(cols) -fig = make_subplots( - rows=n, cols=n, shared_xaxes="columns", shared_yaxes=False, horizontal_spacing=0.02, vertical_spacing=0.02 -) - -# Add scatter plots for off-diagonal and histograms for diagonal -for i in range(n): - for j in range(n): - row, col = i + 1, j + 1 - - if i == j: - # Diagonal: histograms for each species (overlaid) - for species in species_list: - subset = iris[iris["species"] == species] - fig.add_trace( - go.Histogram( - x=subset[cols[i]], - name=species, - marker_color=color_map[species], - opacity=0.7, - showlegend=(i == 0), - legendgroup=species, - ), - row=row, - col=col, - ) - fig.update_xaxes(showgrid=True, gridwidth=0.8, gridcolor=GRID_LIGHT, row=row, col=col) - fig.update_yaxes(showgrid=True, gridwidth=0.8, gridcolor=GRID_LIGHT, row=row, col=col) - else: - # Off-diagonal: scatter plots with linked selection - for species in species_list: - subset = iris[iris["species"] == species] - fig.add_trace( - go.Scatter( - x=subset[cols[j]], - y=subset[cols[i]], - mode="markers", - name=species, - marker={ - "color": color_map[species], "size": 10, "opacity": 0.7, "line": {"width": 0.5, "color": PAGE_BG} - }, - showlegend=False, - legendgroup=species, - selected={"marker": {"opacity": 1.0, "size": 12}}, - unselected={"marker": {"opacity": 0.15, "size": 6}}, - ), - row=row, - col=col, - ) - fig.update_xaxes(showgrid=True, gridwidth=0.8, gridcolor=GRID_LIGHT, row=row, col=col) - fig.update_yaxes(showgrid=True, gridwidth=0.8, gridcolor=GRID_LIGHT, row=row, col=col) - -# Update axis labels - only on edges -for i in range(n): - fig.update_yaxes(title_text=labels[i], row=i + 1, col=1) - fig.update_xaxes(title_text=labels[i], row=n, col=i + 1) - -# Layout for large canvas with interactivity -fig.update_layout( - title={ - "text": "scatter-matrix-interactive · python · plotly · anyplot.ai", - "font": {"size": 28, "color": INK}, - "x": 0.5, - "xanchor": "center", - }, - font={"color": INK_SOFT, "size": 14}, - dragmode="select", - width=1600, - height=900, - paper_bgcolor=PAGE_BG, - plot_bgcolor=PAGE_BG, - barmode="overlay", - legend={ - "title": {"text": "Species", "font": {"size": 18, "color": INK}}, - "font": {"size": 16, "color": INK_SOFT}, - "yanchor": "top", - "y": 0.99, - "xanchor": "left", - "x": 1.02, - "bgcolor": ELEVATED_BG, - "bordercolor": INK_SOFT, - "borderwidth": 1, - }, - margin={"l": 100, "r": 150, "t": 100, "b": 100}, -) - -# Update all axis properties for theme-adaptive chrome -fig.update_xaxes( - title_font={"color": INK, "size": 22}, - tickfont={"color": INK_SOFT, "size": 18}, - linecolor=INK_SOFT, - zerolinecolor=INK_SOFT, -) -fig.update_yaxes( - title_font={"color": INK, "size": 22}, - tickfont={"color": INK_SOFT, "size": 18}, - linecolor=INK_SOFT, - zerolinecolor=INK_SOFT, -) - -# Save as PNG and HTML -fig.write_image(f"plot-{THEME}.png", width=1600, height=900, scale=3) -fig.write_html( - f"plot-{THEME}.html", - include_plotlyjs="cdn", - config={"displayModeBar": True, "modeBarButtonsToAdd": ["select2d", "lasso2d"]}, -) diff --git a/plots/scatter-matrix-interactive/implementations/python/plotnine.py b/plots/scatter-matrix-interactive/implementations/python/plotnine.py deleted file mode 100644 index 221851b289..0000000000 --- a/plots/scatter-matrix-interactive/implementations/python/plotnine.py +++ /dev/null @@ -1,132 +0,0 @@ -""" anyplot.ai -scatter-matrix-interactive: Interactive Scatter Plot Matrix (SPLOM) -Library: plotnine 0.15.4 | Python 3.13.13 -Quality: 88/100 | Updated: 2026-05-18 -""" - -import os -import sys - - -sys.path.pop(0) - -import numpy as np -import pandas as pd -from plotnine import ( - aes, - element_line, - element_rect, - element_text, - facet_grid, - geom_point, - geom_ribbon, - ggplot, - labs, - scale_color_manual, - scale_fill_manual, - theme, - theme_minimal, -) -from sklearn.datasets import load_iris - - -# 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" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -# Okabe-Ito palette (first series #009E73) -IMPRINT = ["#009E73", "#C475FD", "#4467A3"] - -# Data: Iris dataset for multivariate analysis -iris = load_iris() -df = pd.DataFrame(iris.data, columns=["Sepal Length (cm)", "Sepal Width (cm)", "Petal Length (cm)", "Petal Width (cm)"]) -df["Species"] = pd.Categorical([iris.target_names[i] for i in iris.target]) - -variables = ["Sepal Length (cm)", "Sepal Width (cm)", "Petal Length (cm)", "Petal Width (cm)"] - -# Create long-form data for scatter matrix -scatter_data = [] -density_data = [] - -for i, var_y in enumerate(variables): - for j, var_x in enumerate(variables): - if i == j: - # Diagonal: histogram-based density - var_min, var_max = df[var_x].min(), df[var_x].max() - var_range = var_max - var_min - baseline = var_min - - for species in df["Species"].unique(): - species_vals = df[df["Species"] == species][var_x].values - hist, edges = np.histogram(species_vals, bins=20, range=(var_min, var_max), density=True) - max_density = hist.max() if hist.max() > 0 else 1 - hist_scaled = hist / max_density * var_range * 0.5 + baseline - bin_centers = (edges[:-1] + edges[1:]) / 2 - - for k in range(len(bin_centers)): - density_data.append( - { - "x": bin_centers[k], - "ymin": baseline, - "ymax": hist_scaled[k], - "Species": species, - "var_x": var_x, - "var_y": var_y, - } - ) - else: - # Off-diagonal: scatter points - for _, row in df.iterrows(): - scatter_data.append( - {"x": row[var_x], "y": row[var_y], "Species": row["Species"], "var_x": var_x, "var_y": var_y} - ) - -scatter_df = pd.DataFrame(scatter_data) -density_df = pd.DataFrame(density_data) - -# Set factor levels for proper ordering -scatter_df["var_x"] = pd.Categorical(scatter_df["var_x"], categories=variables, ordered=True) -scatter_df["var_y"] = pd.Categorical(scatter_df["var_y"], categories=variables[::-1], ordered=True) -density_df["var_x"] = pd.Categorical(density_df["var_x"], categories=variables, ordered=True) -density_df["var_y"] = pd.Categorical(density_df["var_y"], categories=variables[::-1], ordered=True) - -# Sort density data for proper ribbon rendering -density_df = density_df.sort_values(["var_x", "var_y", "Species", "x"]) - -# Create scatter plot matrix -plot = ( - ggplot(mapping=aes(x="x")) - + geom_point(data=scatter_df, mapping=aes(y="y", color="Species"), size=3.5, alpha=0.7) - + geom_ribbon(data=density_df, mapping=aes(ymin="ymin", ymax="ymax", fill="Species"), alpha=0.5) - + facet_grid("var_y ~ var_x", scales="free") - + scale_color_manual(values=IMPRINT) - + scale_fill_manual(values=IMPRINT) - + labs(title="scatter-matrix-interactive · python · plotnine · anyplot.ai", x="", y="") - + theme_minimal() - + theme( - figure_size=(16, 16), - plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - panel_grid_major=element_line(color=INK_MUTED, size=0.3, alpha=0.10), - panel_grid_minor=element_line(color=INK_MUTED, size=0.2, alpha=0.05), - panel_border=element_rect(color=INK_SOFT, fill=None, size=0.5), - plot_title=element_text(size=24, color=INK, ha="left", weight="bold"), - strip_text_x=element_text(size=16, color=INK), - strip_text_y=element_text(size=16, color=INK, angle=0), - axis_text=element_text(size=14, color=INK_SOFT), - axis_title_x=element_text(size=16, color=INK), - axis_title_y=element_text(size=16, color=INK), - legend_title=element_text(size=16, color=INK), - legend_text=element_text(size=14, color=INK_SOFT), - legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT, size=0.5), - legend_position="bottom", - panel_spacing=0.03, - ) -) - -# Save plot -plot.save(f"plot-{THEME}.png", dpi=300, width=16, height=16, verbose=False) diff --git a/plots/scatter-matrix-interactive/implementations/python/pygal.py b/plots/scatter-matrix-interactive/implementations/python/pygal.py deleted file mode 100644 index 549676aa3b..0000000000 --- a/plots/scatter-matrix-interactive/implementations/python/pygal.py +++ /dev/null @@ -1,77 +0,0 @@ -""" anyplot.ai -scatter-matrix-interactive: Interactive Scatter Plot Matrix (SPLOM) -Library: pygal 3.1.0 | Python 3.13.13 -Quality: 68/100 | Created: 2026-05-18 -""" - -import os -import sys - - -# Remove the script's directory from sys.path to avoid shadowing installed packages -script_dir = os.path.dirname(os.path.abspath(__file__)) -if script_dir in sys.path: - sys.path.remove(script_dir) - -import pandas as pd # noqa: E402 -import pygal # noqa: E402 -from pygal.style import Style # noqa: E402 -from sklearn.datasets import load_iris # noqa: E402 - - -# Theme tokens -THEME = os.getenv("ANYPLOT_THEME", "light") -PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" -INK = "#1A1A17" if THEME == "light" else "#F0EFE8" -INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" - -IMPRINT = ("#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477") - -# Data -iris = load_iris() -df = pd.DataFrame(iris.data, columns=["sepal_length", "sepal_width", "petal_length", "petal_width"]) -df["species"] = iris.target_names[iris.target] - -# Create scatter plot matrix visualization -custom_style = Style( - background=PAGE_BG, - plot_background=PAGE_BG, - foreground=INK, - foreground_strong=INK, - foreground_subtle=INK_MUTED, - colors=IMPRINT, - title_font_size=28, - label_font_size=22, - major_label_font_size=18, - legend_font_size=16, - value_font_size=14, -) - -chart = pygal.XY( - width=4800, - height=2700, - title="scatter-matrix-interactive · python · pygal · anyplot.ai", - x_title="Sepal Length (cm)", - y_title="Petal Length (cm)", - show_legend=True, - show_dots=True, - stroke=False, - style=custom_style, - tooltip_border_radius=4, - range=(4, 8), -) - -# Add data series for each species -species_names = iris.target_names -for species_name in species_names: - species_data = df[df["species"] == species_name] - points = [ - {"value": (float(x), float(y)), "label": f"{species_name}: ({x:.1f}, {y:.1f})"} - for x, y in zip(species_data["sepal_length"], species_data["petal_length"], strict=False) - ] - chart.add(species_name.capitalize(), points) - -# Render files -chart.render_to_png(f"plot-{THEME}.png") -with open(f"plot-{THEME}.html", "wb") as f: - f.write(chart.render()) diff --git a/plots/scatter-matrix-interactive/implementations/python/seaborn.py b/plots/scatter-matrix-interactive/implementations/python/seaborn.py deleted file mode 100644 index ab3f688549..0000000000 --- a/plots/scatter-matrix-interactive/implementations/python/seaborn.py +++ /dev/null @@ -1,122 +0,0 @@ -""" anyplot.ai -scatter-matrix-interactive: Interactive Scatter Plot Matrix (SPLOM) -Library: seaborn 0.13.2 | Python 3.13.13 -Quality: 90/100 | Updated: 2026-05-18 -""" - -import os -import sys - - -# Force import from venv, not local matplotlib.py -original_path = sys.path.copy() -sys.path = [ - p for p in sys.path - if p != "" and p != "." and "/scatter-matrix-interactive/implementations/python" not in p -] - -import matplotlib.pyplot as plt # noqa: E402 -import seaborn as sns # noqa: E402 - - -sys.path = original_path - -# 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" - -# Okabe-Ito palette - first series always #009E73 -IMPRINT = ["#009E73", "#C475FD", "#4467A3"] - -# Load Iris dataset - classic multivariate data for scatter matrices -iris = sns.load_dataset("iris") - -# Rename columns for clearer axis labels -df = iris.rename( - columns={ - "sepal_length": "Sepal Length (cm)", - "sepal_width": "Sepal Width (cm)", - "petal_length": "Petal Length (cm)", - "petal_width": "Petal Width (cm)", - "species": "Species", - } -) - -# Create color mapping using Okabe-Ito palette -palette = {"setosa": IMPRINT[0], "versicolor": IMPRINT[1], "virginica": IMPRINT[2]} - -# Apply theme-adaptive styling -sns.set_theme( - style="ticks", - rc={ - "figure.facecolor": PAGE_BG, - "axes.facecolor": PAGE_BG, - "axes.edgecolor": INK_SOFT, - "axes.labelcolor": INK, - "text.color": INK, - "xtick.color": INK_SOFT, - "ytick.color": INK_SOFT, - "grid.color": INK, - "grid.alpha": 0.10, - "legend.facecolor": ELEVATED_BG, - "legend.edgecolor": INK_SOFT, - }, -) - -# Create scatter matrix using PairGrid (square format: 3600x3600 at 300 dpi = 12x12 inches) -g = sns.PairGrid( - df, - vars=["Sepal Length (cm)", "Sepal Width (cm)", "Petal Length (cm)", "Petal Width (cm)"], - hue="Species", - palette=palette, - height=2.7, - aspect=1, - corner=False, -) - -# Off-diagonal: scatter plots with sized markers and transparency -g.map_offdiag(sns.scatterplot, s=80, alpha=0.7, edgecolor=PAGE_BG, linewidth=0.5) - -# Diagonal: KDE plots for univariate distributions -g.map_diag(sns.kdeplot, fill=True, alpha=0.5, linewidth=2.5) - -# Style axis labels and ticks for large canvas readability -for ax in g.axes.flatten(): - if ax is not None: - ax.tick_params(axis="both", labelsize=18, colors=INK_SOFT) - ax.xaxis.label.set_size(20) - ax.xaxis.label.set_color(INK) - ax.yaxis.label.set_size(20) - ax.yaxis.label.set_color(INK) - ax.grid(True, alpha=0.10, linewidth=0.8) - for spine in ax.spines.values(): - spine.set_edgecolor(INK_SOFT) - -# Add legend with larger fonts -g.add_legend( - title="Species", - fontsize=18, - title_fontsize=20, - bbox_to_anchor=(1.02, 0.5), - loc="center left", - frameon=True, - fancybox=True, - markerscale=2.5, -) - -# Add title to the figure -g.figure.suptitle( - "scatter-matrix-interactive · python · seaborn · anyplot.ai", fontsize=24, fontweight="medium", color=INK, y=1.02 -) - -# Adjust layout and save at 3600x3600 (square format for symmetric grid) -plt.tight_layout() -g.figure.set_size_inches(12, 12) - -# Save to the script's directory -script_dir = os.path.dirname(os.path.abspath(__file__)) -output_path = os.path.join(script_dir, f"plot-{THEME}.png") -g.figure.savefig(output_path, dpi=300, bbox_inches="tight", facecolor=PAGE_BG) diff --git a/plots/scatter-matrix-interactive/metadata/python/altair.yaml b/plots/scatter-matrix-interactive/metadata/python/altair.yaml deleted file mode 100644 index 8b10c14dcd..0000000000 --- a/plots/scatter-matrix-interactive/metadata/python/altair.yaml +++ /dev/null @@ -1,226 +0,0 @@ -library: altair -language: python -specification_id: scatter-matrix-interactive -created: '2026-01-10T01:58:17Z' -updated: '2026-05-18T16:30:50Z' -generated_by: claude-haiku -workflow_run: 26046098691 -issue: 3604 -language_version: 3.13.13 -library_version: 6.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/altair/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/altair/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/altair/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/altair/plot-dark.html -quality_score: 90 -review: - strengths: - - 'Perfect visual quality: readability, no overlap, optimal element sizing across - both light and dark themes' - - All spec features fully implemented with proper linked brushing and interactive - selection - - 'Excellent data quality: realistic iris example with clear species separation - across all dimensions' - - Clean, reproducible code with proper theme tokenization and explicit font sizing - - 'Distinctive Altair usage: native linked selection via brush + condition, matrix - generation via repeat()' - weaknesses: - - Design Excellence score reflects reliance on library defaults rather than custom - aesthetic polish - - Grid and axis styling are theme-aware but fairly standard—could benefit from spine - removal or more refined visual treatment - image_description: |- - Light render (plot-light.png): - Background: Warm off-white #FAF8F1, not pure white - Chrome: Title, subtitle, axis labels, tick labels all in dark text (INK=#1A1A17), fully readable - Data: Scatter plots and KDE density curves visible; three species color-coded (setosa #009E73, versicolor #D55E00, virginica #0072B2) - Legibility verdict: PASS - all elements clearly readable against light surface - - Dark render (plot-dark.png): - Background: Warm near-black #1A1A17, not pure black - Chrome: Title, subtitle, axis labels, tick labels all in light text (INK_SOFT=#B8B7B0), fully readable; no dark-on-dark failures - Data: Same three species colors as light render (identical); markers and density areas clearly distinguishable - Legibility verdict: PASS - all elements clearly readable against dark surface; brand green #009E73 maintains visibility - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: Font sizes explicitly set; all readable at full resolution in both - themes - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: Tick labels angled -45°; all text fully readable - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Marker sizing optimal; opacity appropriate for density - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette colorblind-safe; good contrast - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 4×4 matrix well-proportioned; generous spacing - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: All labels descriptive with units - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: Okabe-Ito order correct; theme-correct chrome both renders - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: true - comment: Well-configured defaults; lacks custom polish - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Grid customized, theme-aware colors; could benefit from spine removal - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Species coloring guides discovery; structure aids narrative - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct SPLOM; 4×4 layout with scatter off-diagonal, KDE on diagonal - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: 'All features present: brush, linked highlighting, univariate distributions, - zoom/pan, opacity de-emphasis, reset' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X/Y correctly mapped; all four numeric variables used - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title format correct; legend labels accurate - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Iris data shows all aspects; three distinct species with clear variation - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Realistic botanical context; neutral, widely recognized domain - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Measurements factually sound and realistic - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Linear flow; no unnecessary functions - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) ensures determinism - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean, Pythonic, appropriate complexity - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Correct output format - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Proper API usage; standard patterns - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: Uses alt.selection_interval(), alt.condition(), alt.repeat(), alt.transform_density() - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - hover-tooltips - - faceting - patterns: - - data-generation - dataprep: - - kde - styling: - - grid-styling diff --git a/plots/scatter-matrix-interactive/metadata/python/bokeh.yaml b/plots/scatter-matrix-interactive/metadata/python/bokeh.yaml deleted file mode 100644 index a9d4465d0a..0000000000 --- a/plots/scatter-matrix-interactive/metadata/python/bokeh.yaml +++ /dev/null @@ -1,254 +0,0 @@ -library: bokeh -language: python -specification_id: scatter-matrix-interactive -created: '2026-01-10T01:58:17Z' -updated: '2026-05-18T16:24:50Z' -generated_by: claude-haiku -workflow_run: 26045805574 -issue: 3604 -language_version: 3.13.13 -library_version: 3.9.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/bokeh/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/bokeh/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/bokeh/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/bokeh/plot-dark.html -quality_score: 91 -review: - strengths: - - Proper linked brushing through shared ColumnDataSource with correct selection/nonselection - states - - Complete theme-adaptive styling with correct palette (Okabe-Ito) and theme-responsive - text colors across both renders - - Strong bokeh-specific feature usage including HoverTool, gridplot, and proper - interactive tool configuration - - Clean code structure with deterministic iris data, no unnecessary functions, and - explicit font sizing - - Well-designed 4×4 scatter matrix with diagonal histograms showing distributions - and off-diagonal scatter plots for pairwise relationships - - Correct title format and legend placement with species color encoding matching - the specification - weaknesses: - - Design could benefit from additional visual refinement or custom styling beyond - library defaults - - Data storytelling focuses on exploration mechanics rather than highlighting specific - patterns or insights - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1), correctly set. Plot background matches specification perfectly. - Chrome: Title "scatter-matrix-interactive · python · bokeh · anyplot.ai" is clearly visible in dark text (INK color). Axis labels appear on matrix edges only (appropriate for SPLOM), rendered in INK color at the bottom and left. Tick labels on each cell are visible in INK_SOFT (secondary text color), properly sized at 18pt. - Data: The 4×4 scatter plot matrix correctly shows 4 iris variables. Diagonal cells display histograms with green bars (#009E73, Okabe-Ito position 1 - brand color). Off-diagonal cells show scatter plots with three species encoded in green (setosa), orange (versicolor, #D55E00), and blue (virginica, #0072B2) following canonical Okabe-Ito order. Legend in top-right cell shows species names with correct colors. Grid lines are subtle (10% alpha) and not obtrusive. - Legibility verdict: PASS - All text is highly readable. Font sizes are explicitly set (22pt for axis labels, 18pt for ticks), providing excellent visibility on the 3600×3600 canvas. - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17), correctly set. No pure black or light backgrounds. - Chrome: Title is rendered in light text (INK=#F0EFE8), maintaining perfect contrast against dark background. Axis labels use the same light INK color and are clearly legible. Tick labels use INK_SOFT (#B8B7B0, light secondary text), providing appropriate contrast. - Data: The data colors are identical to the light render—histogram bars remain green (#009E73), scatter points maintain the same species color scheme (green, orange, blue). The legend colors match exactly. Grid lines are subtle and visible against the dark background. No "dark-on-dark" failures observed. - Legibility verdict: PASS - All chrome elements are light-colored on the dark surface, providing excellent readability. Both renders maintain identical data colors, only theme-adaptive chrome elements flip appropriately. - - Both renders pass the theme-readability check. The implementation correctly threads theme tokens through all text and chrome elements while keeping data colors consistent across themes. - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All font sizes explicitly set (22pt titles, 18pt ticks). Perfectly - readable on both themes. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text. Matrix layout with edge labels prevents collisions. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Markers, histograms, and lines all clearly visible and appropriately - sized for density. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette is CVD-safe. Good contrast across all elements. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 3600×3600 square format well-balanced. Plot fills appropriate canvas - area with generous margins. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title format correct and descriptive. Axis labels present on matrix - edges with variable names. - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73 (brand green). Multi-series follows Okabe-Ito - order. Backgrounds #FAF8F1/#1A1A17 correct. Theme-adaptive chrome verified - in both renders.' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: false - comment: Professional styling with Okabe-Ito palette and theme-adaptive colors. - Follows bokeh defaults well but not highly customized or distinctive. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: false - comment: Subtle grid (10% alpha), generous whitespace, clean layout. Standard - library defaults with minimal customization. - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: false - comment: Diagonal histograms create visual hierarchy. Linked selection guides - exploration across dimensions. Visual hierarchy present but not exceptional. - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct scatter plot matrix (SPLOM) with diagonal histograms and - off-diagonal scatter plots. - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: 'All spec features present: linked brushing, zoom/pan, histograms, - selection feedback, reset tool, color consistency.' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X/Y correctly assigned across all cells. All 4 variables shown. Axes - properly scaled. - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title format correct with spec-id, language, library, and anyplot.ai. - Legend shows species names with accurate colors. - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Iris dataset with 4 variables shows all aspects of multivariate analysis. - Different species patterns clearly visible. - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Iris is real, well-known, scientifically neutral dataset. Perfect - for exploratory analysis use case. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Sepal/petal measurements in cm are factually correct iris proportions. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear structure: imports → tokens → data → loops → layout → save. - No unnecessary functions.' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Uses deterministic load_iris(). No random seeds needed. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: 'All imports used: numpy, pandas, bokeh, sklearn, selenium.' - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean, Pythonic. No fake UI or over-engineering. Proper linked brushing - implementation. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.html and plot-{THEME}.png. Current bokeh API. - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: false - comment: Good use of ColumnDataSource, figure(), selection states, gridplot, - HoverTool. Standard bokeh patterns executed well. - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: false - comment: Leverages bokeh-specific linked selection/brushing, selection/nonselection - rendering, and hover tooltips. Good use of distinctive features. - verdict: APPROVED -impl_tags: - dependencies: - - sklearn - - selenium - techniques: - - subplots - - hover-tooltips - - html-export - - custom-legend - patterns: - - dataset-loading - - iteration-over-groups - - columndatasource - dataprep: [] - styling: - - alpha-blending - - grid-styling diff --git a/plots/scatter-matrix-interactive/metadata/python/letsplot.yaml b/plots/scatter-matrix-interactive/metadata/python/letsplot.yaml deleted file mode 100644 index 611643cf48..0000000000 --- a/plots/scatter-matrix-interactive/metadata/python/letsplot.yaml +++ /dev/null @@ -1,243 +0,0 @@ -library: letsplot -language: python -specification_id: scatter-matrix-interactive -created: '2026-01-10T02:00:12Z' -updated: '2026-05-18T16:52:28Z' -generated_by: claude-haiku -workflow_run: 26046324205 -issue: 3604 -language_version: 3.13.13 -library_version: 4.9.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/letsplot/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/letsplot/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/letsplot/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/letsplot/plot-dark.html -quality_score: 81 -review: - strengths: - - Perfect visual quality and readability in both light and dark themes - - Beautiful, balanced 4×4 matrix layout with proper spacing and typography - - Flawless Okabe-Ito palette compliance and theme-adaptive chrome - - Clean, reproducible, well-organized code - - Proper diagonal histograms and scatter plot structure - - Excellent hover tooltips with species and coordinate information - - Correct title and legend formatting - weaknesses: - - 'Missing core interactive feature: brushing/selection with cross-plot highlighting - (SC-02)' - - No linked selection across subplots - fundamental to SPLOM specification - - No zoom/pan functionality for interactive exploration - - Unselected points not de-emphasized - - No reset/clear selection mechanism - - Design is competent but not sophisticated - relies on library defaults - - Doesn't fully leverage letsplot's interactive potential beyond tooltips - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) - correct theme surface - Chrome: Title "scatter-matrix-interactive · python · letsplot · anyplot.ai" in 32pt dark text, axis labels in 16pt dark text, tick labels in 13pt secondary color - all clearly readable - Data: 4×4 matrix with histograms on diagonal (colored by species), scatter plots off-diagonal with consistent Okabe-Ito colors (green #009E73 for Setosa, orange #D55E00 for Versicolor, blue #0072B2 for Virginica) - Layout: Well-balanced title (6%), grid (80%), legend (6%) with proper margins - Legibility verdict: PASS - all text perfectly readable, no overlap, grid subtle - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) - correct dark theme surface - Chrome: Title and axis labels in light text (#F0EFE8), tick labels in secondary light (#B8B7B0) - all clearly readable on dark background. NO dark-on-dark failures. - Data: Identical colors to light render - green, orange, blue data colors preserved exactly across themes (only chrome adapts) - Grid: Subtle grid lines in light color scheme, properly visible - Legend: Light text on dark background, properly positioned - Legibility verdict: PASS - all text readable, theme-adaptive chrome perfect, no contrast failures - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All font sizes explicitly set; title 32pt, axis labels 16pt, ticks - 13pt; perfectly readable in both themes - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: Well-spaced 4×4 grid with 6px margins; no text overlap - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Points (size=4.5, alpha=0.72) and histograms well-adapted to data - density - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette, CVD-safe, excellent contrast - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Perfect proportions; title (6%), grid (80%), legend (6%); balanced - margins - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Descriptive variable names; proper title format - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73; colors identical across themes; backgrounds - correct; chrome theme-adaptive' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: false - comment: Professional layout with clear hierarchy; well-organized grid; solid - defaults but somewhat formulaic - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: false - comment: Subtle grid, clean appearance, generous whitespace; relies on library - defaults with minimal polish - - id: DE-03 - name: Data Storytelling - score: 3 - max: 6 - passed: false - comment: Clear visual narrative showing pairwise relationships; data displayed - but not interpreted - spec_compliance: - score: 9 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 3 - max: 5 - passed: false - comment: Structurally correct 4×4 scatter matrix; however, spec emphasizes - 'interactive' and static visualization lacks full interactivity - - id: SC-02 - name: Required Features - score: 0 - max: 4 - passed: false - comment: 'CRITICAL: Missing brushing/selection with cross-plot highlighting, - zoom/pan, de-emphasis, reset. Only hover tooltips present.' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X/Y correctly mapped; axes show full data range - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title format perfect; legend properly labeled and colored - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: Shows all pairwise relationships and univariate distributions; demonstrates - correlations - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Iris-like synthetic data; realistic scales; neutral scientific context - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Factually correct ranges for iris measurements - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Clean linear flow; no functions/classes - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set for deterministic output - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only used imports - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Appropriate complexity; no fake functionality - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Correct file naming - library_mastery: - score: 6 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Expert ggplot syntax; theme-adaptive tokens integrated; layer_tooltips - for hover - - id: LM-02 - name: Distinctive Features - score: 2 - max: 5 - passed: false - comment: Uses ggbunch and layer_tooltips; doesn't leverage linked-brushing - or advanced interactive capabilities - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - subplots - - hover-tooltips - - layer-composition - patterns: - - data-generation - - iteration-over-groups - dataprep: [] - styling: - - alpha-blending - - publication-ready diff --git a/plots/scatter-matrix-interactive/metadata/python/matplotlib.yaml b/plots/scatter-matrix-interactive/metadata/python/matplotlib.yaml deleted file mode 100644 index 4bfc067cb2..0000000000 --- a/plots/scatter-matrix-interactive/metadata/python/matplotlib.yaml +++ /dev/null @@ -1,273 +0,0 @@ -library: matplotlib -language: python -specification_id: scatter-matrix-interactive -created: '2026-01-10T01:57:21Z' -updated: '2026-05-18T16:26:19Z' -generated_by: claude-haiku -workflow_run: 26045487922 -issue: 3604 -language_version: 3.13.13 -library_version: 3.10.9 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/matplotlib/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/matplotlib/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 82 -review: - strengths: - - Theme-adaptive styling correctly applied in both light and dark renders with proper - color tokens (INK, INK_SOFT, ELEVATED_BG) - - Scatter matrix structure is clean and well-organized with 4x4 grid layout showing - all pairwise relationships plus univariate histograms on diagonal - - Okabe-Ito palette (positions 1-3) correctly implemented with first series as brand - green (#009E73), identical across light and dark themes - - Text legibility is excellent with all font sizes explicitly set (title 26pt, axis - labels 18pt, tick labels 14pt) and readable in both themes - - White edges on scatter markers provide clear definition on overlapping points, - alpha values (0.7 scatter, 0.65 histogram) appropriate for data density - - Transparent acknowledgment of matplotlib limitations with clear note about static - visualization, properly directing users to interactive alternatives - - Comprehensive use of theme tokens throughout eliminates any dark-on-dark or light-on-light - readability issues - weaknesses: - - Title format missing required elements (language, library, anyplot.ai) - should - be 'Iris Dataset · scatter-matrix-interactive · python · matplotlib · anyplot.ai' - per SC-04 specification - - Design could be more sophisticated with removal of top/right spines (currently - keeps all four spines) and refined grid styling on scatter plots - - No advanced matplotlib-specific features leveraged (e.g., inset_axes, custom artists, - or matplotlib-unique visualization patterns) - implementation is functional but - generic - - Interactive specification features (brushing, selection, zoom, pan) not implementable - in static matplotlib, though limitation is properly acknowledged - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) with subtle contrast - Chrome: Title "Iris Dataset · scatter-matrix-interactive" in dark text (fontsize 26), clearly readable. Axis labels ("Sepal Length (cm)", etc.) in dark text at fontsize 18. Tick labels at fontsize 14 are easily readable. Grid lines are subtle at alpha=0.12. Spines are visible in soft gray (#4A4A44). - Data: Three species in Okabe-Ito colors - Setosa (#009E73 - bluish green), Versicolor (#D55E00 - vermillion), Virginica (#0072B2 - blue). Scatter points have white edges for definition. Histograms on diagonal show overlapping distributions with alpha=0.65. All data elements are clearly visible. - Legend: Three entries at top center with proper species labels and color coding. - Legibility verdict: PASS - All text is readable at full resolution, no dark-on-light issues, excellent contrast throughout. - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) consistent with light theme - Chrome: Title in light text (#F0EFE8), clearly readable on dark background. Axis labels in light text at fontsize 18, fully legible. Tick labels at fontsize 14, readable against dark background. Grid lines remain subtle at alpha=0.12 with light coloring. Spines properly rendered in #B8B7B0 (INK_SOFT). - Data: Identical colors to light render - #009E73, #D55E00, #0072B2 remain consistent across both themes, proving proper Okabe-Ito implementation. White edges on markers maintain definition. Histogram bars show same color distribution. - Legend: Properly styled with dark background (#242420) frame and light text, consistent with theme. - Legibility verdict: PASS - No dark-on-dark failures observed. All text is light-colored and readable. Brand green remains visible and consistent. - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All font sizes explicitly set (title 26pt, labels 18pt, ticks 14pt). - Perfectly readable in both light and dark themes. - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: Excellent layout with labels only on edges, tight_layout prevents - overlap, legend properly positioned at top. - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Marker size s=120 with alpha=0.7 and white edges perfectly adapted - for 50 points per species. Histogram bins clearly visible. - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette ensures CVD safety. Good contrast between series. - No red-green as sole signal. - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Square format (3600x3600px) optimal for symmetric scatter matrix. - Plot fills canvas appropriately with balanced margins. - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: 'All axes labeled with units in parentheses: ''Sepal Length (cm)'', - ''Petal Width (cm)'', etc.' - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Perfect Okabe-Ito implementation. First series is #009E73. Background - #FAF8F1 (light)/#1A1A17 (dark). All chrome is theme-adaptive.' - design_excellence: - score: 10 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: false - comment: Well-configured design with intentional color scheme and typography. - Professional appearance but uses standard approaches, not exceptional design. - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: false - comment: Good refinement with subtle grid (alpha=0.12), white marker edges - for definition, generous whitespace. Could improve by removing top/right - spines (L-shaped preferred). - - id: DE-03 - name: Data Storytelling - score: 2 - max: 6 - passed: false - comment: Plot effectively displays multivariate relationships through standard - scatter matrix pattern. Data is presented clearly but without additional - visual emphasis or narrative depth. - spec_compliance: - score: 11 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: 'Perfect scatter plot matrix (SPLOM) with 4x4 grid: off-diagonal - scatter plots, diagonal histograms, proper variable pairing.' - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: false - comment: Interactive features (brushing, selection, zoom/pan) not implementable - in static matplotlib. Implementation provides best static alternative and - clearly notes limitation per spec guidance for matplotlib. - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X/Y axes correctly assigned throughout matrix. All data visible and - properly scaled. Pairwise relationships clearly shown. - - id: SC-04 - name: Title & Legend - score: 0 - max: 3 - passed: false - comment: Title 'Iris Dataset · scatter-matrix-interactive' is missing required - format elements (language, library, anyplot.ai). Should be 'Iris Dataset - · scatter-matrix-interactive · python · matplotlib · anyplot.ai'. Legend - labels (Setosa, Versicolor, Virginica) are correct. - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'Iris dataset shows all features: 4 numeric variables, 3 species - with distinct distributions, all pairwise relationships visible.' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Iris dataset is classic scientific dataset, neutral context, well-known - for multivariate analysis education. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Measurements in cm are factually correct for iris flowers. 150 samples - (50 per species) provides good distribution representation. - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Clean linear flow: imports → theme tokens → data loading → plot - construction → save. No functions or classes.' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Uses deterministic Iris dataset. No randomness. Fully reproducible. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: 'Only essential imports: os, matplotlib.pyplot, sklearn.datasets. - All used.' - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Code is clean and Pythonic. Proper loop iteration for subplot filling. - No fake UI or over-engineering. - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Correctly saves as plot-{THEME}.png with DPI 300. Uses current matplotlib - API. - library_mastery: - score: 6 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: false - comment: Correct use of plt.subplots(), ax.scatter(), ax.hist(), fig.legend(). - Proper theme token integration. Good idiomatic usage but doesn't leverage - advanced matplotlib patterns. - - id: LM-02 - name: Distinctive Features - score: 2 - max: 5 - passed: false - comment: Implementation uses standard matplotlib patterns. No distinctive - features unique to matplotlib that set it apart from other libraries. Straightforward - loop-based subplot construction. - score_caps_applied: [] - total_score: 82 - max_total: 100 - verdict: APPROVED -impl_tags: - dependencies: - - sklearn - techniques: - - subplots - - custom-legend - patterns: - - dataset-loading - - iteration-over-groups - dataprep: [] - styling: - - theme-adaptive-chrome - - edge-highlighting - - grid-styling diff --git a/plots/scatter-matrix-interactive/metadata/python/plotly.yaml b/plots/scatter-matrix-interactive/metadata/python/plotly.yaml deleted file mode 100644 index 67f70fe4f7..0000000000 --- a/plots/scatter-matrix-interactive/metadata/python/plotly.yaml +++ /dev/null @@ -1,246 +0,0 @@ -library: plotly -language: python -specification_id: scatter-matrix-interactive -created: '2026-01-10T02:00:38Z' -updated: '2026-05-18T16:22:28Z' -generated_by: claude-haiku -workflow_run: 26045702088 -issue: 3604 -language_version: 3.13.13 -library_version: 6.7.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/plotly/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/plotly/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/plotly/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/plotly/plot-dark.html -quality_score: 90 -review: - strengths: - - Perfect theme adaptation with no dark-on-dark or light-on-light text failures - - Proper use of Plotly's native linked brushing and interactive selection (dragmode, - selected/unselected states) - - Well-structured SPLOM with histograms on diagonal and consistent Okabe-Ito color - coding across all subplots - - 'Excellent text legibility: all titles, axis labels, and tick labels sized appropriately - (28px, 22px, 18px) and readable in both light and dark themes' - - Clean, reproducible code with Iris dataset; no fake UI elements or unnecessary - complexity - weaknesses: - - 'Design Excellence could be elevated: consider custom styling refinements (e.g., - spine removal, more sophisticated legend placement) to move from functional to - exceptional' - - Marker overlap in dense regions could be further mitigated with additional alpha - tuning or jitter, though current opacity (0.7) is acceptable - image_description: |- - Light render (plot-light.png): - Background: Warm off-white at #FAF8F1, correctly matches spec - Chrome: Title (28px, dark text), axis labels (22px, dark), tick labels (18px, gray) all clearly readable; legend has proper border and elevated background; grid is subtle but visible - Data: Okabe-Ito palette with green (#009E73) as first series; orange and blue for Versicolor and Virginica; histograms on diagonal with overlaid opacity 0.7; scatter plots show markers at size 10, opacity 0.7; all data points are clearly distinguishable from background - Legibility verdict: PASS - Excellent readability with proper contrast and sizing - - Dark render (plot-dark.png): - Background: Warm near-black at #1A1A17, correctly matches spec - Chrome: Title, axis labels, and tick labels rendered in light text (INK tokens) with excellent contrast on dark surface; no dark-on-dark text failures; legend styling properly inverted; grid remains subtle and visible - Data: Okabe-Ito colors identical to light render (#009E73, #D55E00, #0072B2); theme adaptation perfect with only chrome changing - Legibility verdict: PASS - All text is readable on dark background; theme tokens properly applied throughout - criteria_checklist: - visual_quality: - score: 29 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: Title 28px, axis labels 22px, tick labels 18px; all explicitly set - and readable in both themes - - id: VQ-02 - name: No Overlap - score: 5 - max: 6 - passed: true - comment: Tight spacing in 4x4 grid is acceptable for matrix layout; no severe - collisions - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Markers properly sized (10px default, 12px selected, 6px unselected); - all scatter points and histograms clearly visible - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: 'Okabe-Ito palette used; first series is #009E73; all three colors - CVD-safe' - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 4800x2700px output (1600x900x3 scale); good proportions; margins - appropriate; nothing cut off - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Title format correct; axis labels include units (cm) - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73; palette order Okabe-Ito compliant; backgrounds - #FAF8F1/#1A1A17; both renders theme-correct' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Uses standard Okabe-Ito palette; overlaid histograms show design - consideration; functional but not exceptional - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Legend styling with border; subtle grid; marker edge highlighting; - well-considered spacing - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: SPLOM naturally guides exploration; color consistency aids tracking; - diagonal distributions add context; interactivity enables discovery narrative - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct scatter plot matrix (SPLOM) implementation - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Has linked selection (dragmode, selected/unselected states), zoom/pan - (Plotly native), diagonal distributions, consistent color encoding, reset - functionality - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X/Y axes correctly mapped; all data shown; 4 continuous variables - properly visualized - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title format correct; legend labels match species names - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: 'Shows all SPLOM aspects: matrix layout, diagonal distributions, - off-diagonal relationships, linked interactivity' - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Iris dataset is real-world standard, widely used for multivariate - analysis, neutral - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Sepal/petal measurements in cm are sensible for iris flowers; scales - are well-proportioned - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: No functions or classes; straightforward procedural code - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Uses fixed seaborn.load_dataset('iris'); deterministic - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: 'Only imports needed: plotly.graph_objects, plotly.subplots, seaborn, - os' - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: No fake UI; uses Plotly native interactivity; appropriate complexity - for SPLOM - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html; current Plotly API - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Uses make_subplots for grid, go.Histogram and go.Scatter for traces, - proper layout methods; high-level API - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: Leverages Plotly's native linked brushing, dragmode selection, selected/unselected - marker states, HTML export with toolbar config - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - subplots - - hover-tooltips - - html-export - patterns: - - dataset-loading - - iteration-over-groups - dataprep: [] - styling: - - alpha-blending - - edge-highlighting diff --git a/plots/scatter-matrix-interactive/metadata/python/plotnine.yaml b/plots/scatter-matrix-interactive/metadata/python/plotnine.yaml deleted file mode 100644 index 56b07aa7d5..0000000000 --- a/plots/scatter-matrix-interactive/metadata/python/plotnine.yaml +++ /dev/null @@ -1,229 +0,0 @@ -library: plotnine -language: python -specification_id: scatter-matrix-interactive -created: '2026-01-10T01:57:41Z' -updated: '2026-05-18T16:35:44Z' -generated_by: claude-haiku -workflow_run: 26046096127 -issue: 3604 -language_version: 3.13.13 -library_version: 0.15.4 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/plotnine/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/plotnine/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 88 -review: - strengths: - - 'Full palette compliance: Okabe-Ito colors identical in both renders; theme-adaptive - chrome flawless' - - 'Excellent legibility: All text explicitly sized and readable in both themes (no - dark-on-dark or light-on-light)' - - 'Strong multivariate design: Facet layout with diagonal KDE and off-diagonal scatter - is the right static SPLOM approach for plotnine' - - 'Clean code structure: Linear, deterministic iris data effectively showcases all - four variables and three species' - weaknesses: - - 'Limited design sophistication: Plot uses library defaults without custom palette - or special styling flourishes' - - 'No visual emphasis through detail: Missing techniques like white marker edges, - bold legend styling, or size variation that could elevate visual hierarchy' - image_description: |- - Light render (plot-light.png): - Background: #FAF8F1 (warm off-white) - correct and readable - Chrome: Title, axis ticks, facet labels, legend all clearly visible in dark text against light surface - Data: Three species colored in green (#009E73), orange (#D55E00), blue (#0072B2); diagonal cells show KDE as ribbons, off-diagonal cells show scatter - Legibility verdict: PASS - all text and data elements readable - - Dark render (plot-dark.png): - Background: #1A1A17 (warm near-black) - correct and readable - Chrome: Title and tick labels are light-colored (#F0EFE8, #B8B7B0), grid lines visible with light opacity - Data: Colors identical to light render (setosa green, versicolor orange, virginica blue); density ribbons and scatter points equally visible - Legibility verdict: PASS - no dark-on-dark failures; all elements clearly distinguished - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: Title, labels, ticks explicitly sized and perfectly readable in both - themes - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text; all elements distinct and readable - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Points at size=3.5 are crisp; density ribbons visible without obscuring - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette is colorblind-safe with strong contrast - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: 4x4 grid fills ~65% with balanced margins; legend properly placed - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Facet strips provide descriptive variable labels; title format correct - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Okabe-Ito colors correct; backgrounds #FAF8F1/#1A1A17; theme-adaptive - chrome perfect' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: false - comment: Well-configured defaults but no custom design flourishes - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: false - comment: Subtle grid, generous whitespace, clean labels show good attention - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: false - comment: Clear hierarchy through facets and color; viewer grasps multivariate - patterns - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct SPLOM with diagonal distributions and off-diagonal scatter - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: false - comment: Static approach omits interactive brushing/zoom as acceptable for - static libraries - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X/Y correctly assigned across all cells; all data visible - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title format correct; legend labels match - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: All 4 iris variables shown; three species well-represented - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Real botanical dataset, neutral and scientifically relevant - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: All iris measurements factually correct - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear flow: imports → data → reshape → plot → save' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Deterministic iris data; no random operations - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports used; no clutter - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean transformation; no over-engineering or fake functionality - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot-{THEME}.png correctly - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: false - comment: facet_grid + dual geoms follows grammar of graphics correctly - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: false - comment: Uses facet_grid and geom_ribbon competently but not exceptionally - verdict: APPROVED -impl_tags: - dependencies: - - sklearn - techniques: - - faceting - - layer-composition - patterns: - - dataset-loading - - iteration-over-groups - dataprep: - - binning - styling: - - alpha-blending diff --git a/plots/scatter-matrix-interactive/metadata/python/pygal.yaml b/plots/scatter-matrix-interactive/metadata/python/pygal.yaml deleted file mode 100644 index 9db410d976..0000000000 --- a/plots/scatter-matrix-interactive/metadata/python/pygal.yaml +++ /dev/null @@ -1,242 +0,0 @@ -library: pygal -language: python -specification_id: scatter-matrix-interactive -created: '2026-05-18T16:28:22Z' -updated: '2026-05-18T21:13:46Z' -generated_by: claude-haiku -workflow_run: 26046117741 -issue: 3604 -language_version: 3.13.13 -library_version: 3.1.0 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/pygal/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/pygal/plot-dark.png -preview_html_light: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/pygal/plot-light.html -preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/pygal/plot-dark.html -quality_score: 68 -review: - strengths: - - 'Correct SPLOM structure: 4x4 matrix with histograms on the diagonal matches the - spec''s plot type' - - 'Palette compliance: brand green #009E73 as first series, Okabe-Ito order maintained, - both themes correctly rendered with #FAF8F1 / #1A1A17 backgrounds' - - 'Full variable coverage: all four iris variables represented; three species color-coded - consistently across all cells' - - 'Clean output: both PNG and HTML artifacts generated; correct file naming; theme - tokens propagated through pygal Style object' - weaknesses: - - 'Missing linked brushing: the core interactive feature (brushing one subplot highlights - matching points in all others) is absent — this is the primary spec requirement' - - Per-point coordinate labels create heavy overlap in every scatter cell; tooltips - in the HTML render are sufficient — static labels should be removed from the PNG/SVG - render - - 'Missing selection-state visual: no mechanism to de-emphasize unselected points, - no reset button, no lasso/box selection' - - Individual cell axis labels do not show which variable is on each axis; row/column - variable names would significantly improve readability - image_description: |- - Light render (plot-light.png): - Background: Warm off-white consistent with #FAF8F1 — correct theme surface - Chrome: Title "scatter-matrix-interactive · python · pygal · anyplot.ai" clearly readable in dark text at top; legend readable in top-right cell; axis tick labels visible in each cell; all text is dark on light — no light-on-light failures - Data: 4x4 SPLOM layout; diagonal cells show grouped histograms (green=Setosa #009E73, orange=Versicolor #D55E00, blue=Virginica #0072B2); off-diagonal cells show scatter plots; Okabe-Ito order correct with brand green as first series; per-point coordinate text labels overlay scatter cells causing significant visual clutter and overlap - Legibility verdict: PASS (text readable; overlap is a VQ-02 issue, not a legibility-token failure) - - Dark render (plot-dark.png): - Background: Warm near-black consistent with #1A1A17 — correct theme surface, not pure black - Chrome: Title and all axis/legend text rendered in light color against dark background — readable throughout; no dark-on-dark failures detected; grid lines subtle and visible; chrome has flipped correctly from light render - Data: Identical data colors to light render (green, orange, blue — Okabe-Ito positions 1-3 unchanged); histogram bars and scatter dots clearly distinguishable from dark background; brand green #009E73 clearly visible; same per-point label clutter as light render - Legibility verdict: PASS - criteria_checklist: - visual_quality: - score: 19 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 5 - max: 8 - passed: true - comment: Title and legend readable in both themes; per-point coordinate labels - create dense text noise throughout scatter cells reducing overall legibility - - id: VQ-02 - name: No Overlap - score: 2 - max: 6 - passed: false - comment: Individual data point labels heavily overlap each other within every - scatter cell — main visual quality failure - - id: VQ-03 - name: Element Visibility - score: 4 - max: 6 - passed: true - comment: Scatter markers and histogram bars visible and color-distinct; clutter - partially obscures individual points - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette; CVD-safe - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: 4x4 matrix fills 4800x2700 canvas well; cell proportions reasonable - - id: VQ-06 - name: Axis Labels & Title - score: 1 - max: 2 - passed: false - comment: Title correct; individual cell axis labels minimal and lack per-cell - variable names - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73; backgrounds #FAF8F1 (light) / #1A1A17 (dark); - both themes correct' - design_excellence: - score: 10 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: SPLOM layout with histograms on diagonal demonstrates design thought; - custom Okabe-Ito palette applied correctly - - id: DE-02 - name: Visual Refinement - score: 2 - max: 6 - passed: false - comment: Theme-adaptive style applied; per-point text label clutter overwhelms - the visual - - id: DE-03 - name: Data Storytelling - score: 3 - max: 6 - passed: false - comment: SPLOM structure communicates multivariate relationships; label noise - prevents reading individual cells cleanly - spec_compliance: - score: 10 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 3 - max: 5 - passed: true - comment: Correct scatter matrix structure with histograms on diagonal; pygal - provides HTML interactivity but linked brushing absent - - id: SC-02 - name: Required Features - score: 1 - max: 4 - passed: false - comment: Diagonal histograms present; linked brushing absent; box/lasso selection - absent; unselected-point de-emphasis absent; reset/clear absent - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: All four iris variables shown; three species correctly color-coded - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title matches required format; legend shows species names - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: Full pairwise variable matrix; species groups visible; diagonal distributions - present - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: 'Iris dataset: real-world, scientifically neutral, appropriate for - SPLOM' - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Variable ranges match expected iris measurement domains - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Flat script, no unnecessary functions or classes - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Iris dataset is deterministic - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only used imports present - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean iteration over species; theme token derivation idiomatic - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html correctly - library_mastery: - score: 5 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 3 - max: 5 - passed: true - comment: Uses Style object correctly; render_to_png and render() for HTML - export; per-point label dict pattern valid - - id: LM-02 - name: Distinctive Features - score: 2 - max: 5 - passed: false - comment: HTML export with interactive tooltips is a pygal strength; interactive - potential not leveraged for linked brushing - verdict: REJECTED -impl_tags: - dependencies: - - sklearn - techniques: - - html-export - - hover-tooltips - patterns: - - dataset-loading - - iteration-over-groups - dataprep: [] - styling: [] diff --git a/plots/scatter-matrix-interactive/metadata/python/seaborn.yaml b/plots/scatter-matrix-interactive/metadata/python/seaborn.yaml deleted file mode 100644 index 8be366a674..0000000000 --- a/plots/scatter-matrix-interactive/metadata/python/seaborn.yaml +++ /dev/null @@ -1,222 +0,0 @@ -library: seaborn -language: python -specification_id: scatter-matrix-interactive -created: '2026-01-10T01:56:03Z' -updated: '2026-05-18T16:32:48Z' -generated_by: claude-haiku -workflow_run: 26045595410 -issue: 3604 -language_version: 3.13.13 -library_version: 0.13.2 -preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/seaborn/plot-light.png -preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-matrix-interactive/python/seaborn/plot-dark.png -preview_html_light: null -preview_html_dark: null -quality_score: 90 -review: - strengths: - - Perfect theme adaptation with no dark-on-dark or light-on-light failures - - 'Idiomatic seaborn usage: expert PairGrid, faceting, and rc-based theming' - - 'Excellent visual quality: text legibility, spacing, element visibility all maximum - scores' - - Okabe-Ito palette correctly applied and consistent across both renders - - Clean, realistic Iris dataset with clear clustering and relationships - weaknesses: - - 'Design Excellence (DE-01) moderate: uses well-configured defaults rather than - custom design sophistication' - - Interactive features (brushing, zoom, pan) not implementable in static PNG per - seaborn library constraints; provides best static alternative - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1), not pure white - Chrome: Title, axis labels, tick labels all clearly readable in dark text (explicit sizes: 24pt, 20pt, 18pt) - Data: Three iris species colored in Okabe-Ito (setosa #009E73, versicolor #D55E00, virginica #0072B2); scatter markers clearly distinguishable; KDE plots on diagonal visible with proper fills - Legibility verdict: PASS - All text readable, no overlap, proper contrast - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17), not pure black - Chrome: Title, axis labels, tick labels all clearly readable in light text (#F0EFE8, #B8B7B0); NO dark-on-dark failures; legend box themed to #242420 with light text - Data: Three species colors identical to light render (same Okabe-Ito hex values); scatter markers clearly distinguishable; KDE plots visible with proper contrast; grid lines subtle but present - Legibility verdict: PASS - All text readable, both themes equally accessible, brand green visible - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: All sizes explicitly set; perfectly readable in both themes - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: Zero overlapping elements - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Markers perfectly adapted to data density - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette, CVD-safe - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Square format ideal for 4x4 grid; excellent margins - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Descriptive with units - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: Okabe-Ito correct; backgrounds and theme chrome perfect - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 4 - max: 8 - passed: false - comment: Well-configured defaults; professional but not exceptionally custom - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Subtle grid, styled spines, generous whitespace - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Clear visual hierarchy through species colors - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct SPLOM with diagonal KDE and scatter - - id: SC-02 - name: Required Features - score: 3 - max: 4 - passed: true - comment: Univariate distributions present; interactive brushing not feasible - in static PNG per seaborn library rules - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: All variables correctly positioned - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Format correct - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Shows all plot aspects; clear clustering - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Iris dataset; real scientific context - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Factually correct measurements - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Linear, clean flow - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Deterministic dataset - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only used imports - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: No fake functionality - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Correct filename - library_mastery: - score: 9 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: Expert PairGrid and rc-based theming - - id: LM-02 - name: Distinctive Features - score: 4 - max: 5 - passed: true - comment: Good use of map_offdiag/map_diag faceting - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - faceting - - custom-legend - patterns: - - dataset-loading - dataprep: [] - styling: - - grid-styling - - alpha-blending diff --git a/plots/scatter-matrix-interactive/specification.md b/plots/scatter-matrix-interactive/specification.md deleted file mode 100644 index c5c9e2c8dc..0000000000 --- a/plots/scatter-matrix-interactive/specification.md +++ /dev/null @@ -1,30 +0,0 @@ -# scatter-matrix-interactive: Interactive Scatter Plot Matrix (SPLOM) - -## Description - -An interactive scatter plot matrix (SPLOM) for exploring relationships between multiple variables with full interactivity. Users can brush to select points in one subplot and see corresponding points highlighted across all other subplots, enabling discovery of patterns that span multiple dimensions. Supports zooming, panning, and linked selection across the entire matrix, making it a powerful tool for multivariate exploratory data analysis. - -## Applications - -- Interactive exploration of multivariate datasets where analysts brush regions to identify clusters and correlations across multiple variable pairs simultaneously -- Feature engineering in machine learning workflows where data scientists interactively select subgroups to understand their distribution across all features -- Quality control analysis where engineers select outlier regions in one measurement and instantly see how those points behave across all other measurements - -## Data - -- `variables` (list of numeric columns) - Multiple continuous variables to compare pairwise -- Variables: 3-5 recommended for usability (grid grows quadratically) -- Size: 50-500 points recommended for responsive interactions -- Example: Iris dataset, mtcars, or similar multivariate numeric data - -## Notes - -- Brushing/selection in any subplot must highlight corresponding points in all other subplots -- Support for box selection or lasso selection across scatter plots -- Diagonal cells should show univariate distributions (histograms or KDE) that update or highlight based on selection -- Include zoom and pan functionality for detailed inspection -- Unselected points should be visually de-emphasized (reduced opacity or gray) -- Provide a clear way to reset/clear selection -- Use consistent color encoding across all subplots -- Libraries without native linked selection (matplotlib, seaborn, plotnine) may need alternative approaches or should note limitations -- Interactive libraries (Plotly, Bokeh, Altair) should leverage their built-in linked brushing capabilities diff --git a/plots/scatter-matrix-interactive/specification.yaml b/plots/scatter-matrix-interactive/specification.yaml deleted file mode 100644 index ebc96bad34..0000000000 --- a/plots/scatter-matrix-interactive/specification.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Specification-level metadata for scatter-matrix-interactive -# Auto-synced to PostgreSQL on push to main - -spec_id: scatter-matrix-interactive -title: Interactive Scatter Plot Matrix (SPLOM) - -# Specification tracking -created: 2026-01-10T01:47:09Z -updated: 2026-05-18T16:09:05Z -issue: 3604 -suggested: MarkusNeusinger - -# Classification tags (applies to all library implementations) -# See docs/reference/tagging-system.md for detailed guidelines -tags: - plot_type: - - scatter - - pairplot - data_type: - - numeric - - continuous - - multivariate - domain: - - general - - statistics - - research - features: - - interactive - - brushing - - linked-views - - correlation - - exploratory