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
-
-
-
-
-
-
- All Regions
-
-
-
-
-"""
-
-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
-
-
-
← Back
-
- Total Sales
-
-
-
-
-
-
- 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("", r"<\/")
-level_svgs_json = json.dumps(level_svgs).replace("", r"<\/")
-
-html_content = f"""
-
-
-
- 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
-
- Treemap
- Sunburst
-
-
-
-
-
-
-
-
-"""
-
-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
- ◕ Sunburst
-
-
{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)}
-
Selected: {detail_label} · Full dataset: 2022–2024
- {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("
\n"
- " Multiple Linked Views with Selection Sync \n"
- ' linked-views-selection · python · pygal · anyplot.ai
\n'
- ' \n'
- " Filter species (click buttons or chart elements): \n"
- ' setosa \n'
- ' versicolor \n'
- ' virginica \n'
- ' Show all \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"
- "", 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