# Changelog

All notable changes to **02viz - Geospatial Visualization Studio** are documented here.
Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) · versioning: [SemVer](https://semver.org/).

## [0.12.0] - 2026-07-14

### Added
- **Reference & statistics overlays.** Bar, line, area, scatter and bubble charts can now carry horizontal guide lines and shaded bands on the value axis: the **mean**, the **median**, a **target** value you type, a **±1σ** band (mean ± one standard deviation) and the **inter-quartile** band (Q1–Q3). Turn any combination on from a *Reference:* row in the Charts tab. The values are computed in pure Python from the plotted numbers (`core/overlays.py`) and drawn identically by every engine — ECharts mark-lines/areas, Plotly shapes, Vega-Lite rule/rect layers and matplotlib `axhline`/`axhspan` — so they read the same in light and dark themes and export with the chart. Overlays are skipped while a chart is animating (the play axis tells its own story).

### Changed
- **The studio no longer runs pip.** When the optional matplotlib/seaborn engine is missing, the dock now shows the exact install command to run yourself (with a Copy button) instead of shelling out to `pip install`. A QGIS plugin should not execute a package manager; this keeps 02viz offline and side-effect free and lets it pass the QGIS Plugin Hub security scan with zero findings.

### Fixed
- Replaced the remaining bare `try/except: pass` blocks with `contextlib.suppress`, clearing the last Bandit warnings so the security scan is clean.

## [0.11.1] - 2026-07-10

### Changed
- Remove the top-level "&02viz" QGIS menu registration; the studio now only adds its toggle icon to its own toolbar, so it no longer shows up as its own menu tab next to Help.

### Added
- Remember the last folder used to load an external table or export a chart as HTML/PDF, across QGIS sessions.

## [0.11.0] - 2026-07-10

- Added a validated custom Vega-Lite JSON editor with offline named-dataset injection and graceful parse errors.
- Added SVG, PNG and PDF export actions, including native QGIS PDF rendering.
- Added an Explore tile picker and pure profile section filtering.
- Extended cross-filter ids to line and area symbols and treemap/sunburst nodes.
- Refreshed the guide, README and release icon for the 0.11.0 workflow.
## [0.10.2] - 2026-06-18

- docs: add CITATION.cff for Zenodo DOI integration

## [0.10.1] - 2026-06-16

- **Hub upload compatibility fix.** `metadata.txt` no longer contains a raw percent sign in the long Hub description/changelog text. QGIS Plugin Hub parses metadata with INI interpolation rules, so `missing %` caused the upload parser to fail before reading the package. Plugin code and features are unchanged from 0.10.0.

## [0.10.0] - 2026-06-16

- **Map diagrams can normalise their fields.** A new *Normalize* control rewrites each diagram field as an expression so fields on very different scales become comparable on every feature, instead of one big-number field swamping the rest. **Min–max (0–1)** is the safe default for pie/bar; **Z-score** standardises (great for bars; can be negative); **Log** compresses heavy tails. Each field's min/max/mean/std are computed from the (optionally selected) features and baked into the diagram's category expressions — **nothing is written to your data**. A live hint warns when Z-score is paired with a pie/stacked diagram (which can't draw negative angles).
- **Labels are now expression-driven.** Beyond a single field you can **round** numbers, add a **thousands separator**, stack a **second field on its own line** (a real newline via `char(10)`), add a **prefix/suffix** or units, change **case**, **word-wrap**, or type **any QGIS expression** in an advanced box that overrides the controls. A live **preview** shows the exact expression and a sample from the first feature, and numeric formatting is applied **only to numeric fields**, so a name on a second line is never wrapped in `round()`. Built on `QgsPalLayerSettings` expressions (`core/expressions.py`).
- **A built-in, offline user guide (❔ Guide).** A designed, self-contained HTML guide covers every surface — engines and the 17 chart types, animation, Explore, map-diagram **normalisation** (with worked examples), **copy-ready label-expression recipes**, the smart assistant, troubleshooting — plus **copy-ready prompts for an external AI assistant** (ChatGPT/Claude) for those who want extra help. No JavaScript, so it renders on every QGIS web backend and exports/opens anywhere. When opened from the dock it includes a personalised *“For your current layer”* section.
- **Smart suggestions (💡 Suggest), fully offline.** The dock reads the active layer's fields and picks the most insightful chart — a correlated-pair scatter with a trend line, a mean-by-category bar, a count bar or a histogram — configures the controls and renders it, telling you *why*. No account, no internet, no new dependencies (`core/assistant.py`).
- **Explore, levelled up.** The dashboard adds a collapsible **field-summary table** (type · missing % · distinct · range/top per field), an overall **completeness** KPI, a **normalised box plot** putting every numeric field on one 0–1 axis (the same comparability idea as the map-diagram normaliser), and deeper **insights** — dominant-category share, range, **skew with a log-transform hint**, **outliers** (1.5×IQR), near-constant and mostly-empty fields, and a couple more notable correlations.
- Verified end-to-end on **QGIS 3.44** and **QGIS 4**: expression labels evaluate to multi-line, formatted text; all four normalisation expressions parse and evaluate; the richer profile/dashboard build; the assistant and guide produce the expected output; and the dock wiring (label/diagram apply, Suggest, hints) was driven offscreen. No regression in the 48 static + 12 animated engine pages or the existing profile/dashboard tests.

## [0.9.2] - 2026-06-16

- **Plotly and Vega-Lite no longer leave a blank panel on QGIS builds without QtWebEngine.** When a QGIS build ships no QtWebEngine, the embedded panel falls back to the legacy QtWebKit viewer — and that old viewer cannot run the modern JavaScript (ES6+) that Plotly.js and Vega-Lite require, so their charts stayed blank in the dock even though they exported and opened correctly in a browser. ECharts and the matplotlib image engine were unaffected (they run on QtWebKit). The dock now detects the QtWebKit viewer and, for Plotly/Vega-Lite, shows a short explainer in the panel instead of a silent blank: your chart is still built — one click on **↗** opens it in your system browser, or use **Export HTML**. For a chart that renders inside the dock, switch to the **ECharts** engine, or install QtWebEngine for QGIS.
- Root-caused in real QGIS: under QtWebKit the engines fail with `ReferenceError: Can't find variable: Plotly` / `vega` (the bundles never parse), while ECharts renders normally; under QtWebEngine (QGIS 4 / Qt6) all engines render. Chart output, the engines themselves and exports are unchanged — this only affects what the embedded panel shows on a QtWebKit-only QGIS.

## [0.9.1] - 2026-06-16

- **Readable on QGIS 4 (Qt6).** The studio panel now pins its own light colours for every control, so it reads identically under any QGIS theme. On QGIS 4 the dark application palette bled into the parts the panel didn't explicitly colour: the drop-down lists rendered on a near-black background and the field labels and check-box text faded into the white cards. Combo pop-ups, line edits, spin boxes, the diagram field list, form labels, check boxes and tool-tips are now explicitly styled — white fields, dark text, teal selection — independent of the host palette. QGIS 3.x looked correct before (its palette is light) and is unchanged.
- The fix is purely the dock's stylesheet; the chart engines, rendering and exports are untouched. Re-verified end-to-end on **QGIS 4.0.2** and **QGIS 3.44**: all four engines render every supported type (ECharts 17, Plotly 17, Vega-Lite 14, matplotlib 11) plus the 12 animated pages, with zero JS exceptions in headless Chrome; the panel and an open drop-down were also grabbed under a simulated dark palette to confirm white fields and dark, legible text.

## [0.9.0] - 2026-06-16

- **Animated charts — a play axis.** Pick an *Animate by ▶* field (typically a year or sequence) and the chart plays through that field's values: a **bar-chart race**, **Gapminder-style bubbles**, or composition/trends unfolding over time. Available for **bar, line, area, scatter, bubble and pie** in the two interactive engines — **ECharts** (a timeline with auto-play) and **Plotly** (a slider with play / pause). A *Play speed* control sets the pace; the play axis greys out for engines and chart types that cannot animate (Vega-Lite, matplotlib, and types like box/heatmap/violin), consistent with the existing engine-first gating.
- **Things animate in place, not jump.** Every frame shares one axis: categories are merged into a stable, deterministically ordered union (a district keeps its slot and colour), the value axis is fixed to the global range across all frames, bubble sizes are scaled once globally so a value maps to the same radius throughout, and grouped series keep a consistent colour order. The current frame is shown as a subtitle (ECharts) and on the slider (Plotly).
- **Still linked to the map.** Animated bars and points carry their feature ids per frame, so clicking one during playback still selects the matching features on the canvas (cross-filter dimming pauses while animating, since each frame redraws the whole chart).
- **No new dependencies.** The frame builder is pure Python (`transform.build_frames` / `frame_groups` / `union_categories` / `align_values`); the vendored ECharts and Plotly engines do the rest entirely offline, and an animated chart still exports as one self-contained HTML file (with its play controls) like any other.
- Verified headless: real-QGIS checks for the frame builder (numeric/lexicographic frame ordering, union axis, global bounds, per-frame ids) and 12 animated engine×type pages, plus 12 animated pages in headless Chrome — auto-play stepping, slider/play controls present, zero JS exceptions; the bar-chart race, Gapminder bubbles, animated pie and lines eyeballed. No regression in the 48 static pages.

## [0.8.0] - 2026-06-13

- **One dock, three tabs** — the studio is now organised as **Charts · Map diagrams · Labels**, all sharing a single layer selector above the tabs. Same purpose throughout — turn a layer's data into an elegant visual — across three output surfaces.
- **Labels tab** — one click turns a field into well-placed, publication-grade labels with a preset (clean subtle-halo / strong halo / bold / plain), geometry-aware placement, built on native `QgsPalLayerSettings` (`core/labels.py`). Prints and exports like any QGIS labeling.
- **Embedded colour editor** — the palette is now an **inline swatch strip** in the Charts tab: click a swatch to recolour it, `+`/`−` to resize the palette, any edit switches to a custom palette — no modal dialog. Added a **chart-title override** field. (The old pop-up palette editor is gone.)
- **Optional advanced engine: matplotlib / seaborn** — a fourth engine renders the spec to **publication-grade static figures** (eleven chart types) and embeds them as a PNG in the same HTML shell, so the viewer and one-file export work unchanged. It appears in the engine picker; when its libraries are missing the dock offers a **one-click install** into the QGIS Python (`python -m pip install --user`, explicit consent only — `core/requirements.py`). The vendored JS engines stay zero-dependency and fully offline; this is the opt-in print/publication path (no chart→map interactivity on static images).
- **Zero2Visual** — the plugin's motto (“from zero to elegant visuals, fast”) now reads in the dock header and About box; the Hub name and "02viz" branding are unchanged.
- Verified headless: 86 checks on real QGIS Python (incl. the matplotlib engine across 11 types, on-canvas labels and the dependency detector) and 50 interactive chart pages in headless Chrome with zero JS exceptions; the matplotlib figures eyeballed.

## [0.7.0] - 2026-06-13

- **Map diagrams (on-canvas charts)** — a new *Map diagrams…* dialog draws native QGIS **pie / bar / stacked-bar / text** diagrams on every feature, directly on the map canvas, sized in millimetres and coloured with the studio palette. Built on `QgsDiagramRenderer`, so the diagrams print, export to print layouts and follow the layer like any other symbology. Numeric fields are pre-ticked (identifier columns skipped); attribute-only tables are detected and declined.
- **Custom colour palettes** — the *Colors* selector gains a **Custom…** entry that opens a swatch editor (add / remove / recolour via the system colour picker, seeded from the active palette). The result is written straight into the theme palette, so it recolours ECharts, Plotly and Vega-Lite identically — single charts and Explore dashboards alike.
- **Fixed: charts blank in the dock but fine on export** — the embedded viewer gave the chart container no definite height until after its first layout pass, so ECharts/Plotly/Vega measured a height of 0 and painted nothing (while export-to-browser worked). The chart box is now absolutely sized and re-fits once the view settles; the dock also nudges a resize on load. Added an **↗ open-in-browser** escape hatch and surfaced the active web backend (webengine / webkit) in the status line.
- **Engine-first workflow** — the dock now asks for the **Engine first**, then the chart **Type**, with unsupported types greyed out for the chosen renderer from the start.
- **Explore ignores identifier columns** — `fid`, `id`, `gid`, `oid`, `uuid`, `objectid`, `*_id`, and provider primary keys are no longer profiled into meaningless histograms or count bars on the dashboard.
- **Publication-grade visual polish** — one shared type system across all three engines, soft dashed horizontal gridlines (no vertical chart-junk), clean card-style tooltips, rounded bar tops and minimal value axes — output reads at Tableau quality.
- Verified headless: 73 checks on real QGIS Python (incl. the on-canvas diagram renderer and the identifier-column filter) and 50 chart pages in headless Chrome with zero JS exceptions; the polished output eyeballed across engines and themes.

## [0.6.0] - 2026-06-12

- **Six new advanced chart types** (17 total): **Mean ± σ band** (line with a shaded ±1 standard-deviation envelope), **Mean ± σ bars** (bars with std-dev whiskers), **Density (KDE)** (Gaussian kernel density curves, optionally one per group), **Violin plot** (mirrored KDE shapes with median dots), **Radar / spider** (multi-axis comparison with per-axis scaling) and **Pareto (80/20)** (descending bars plus a cumulative-share line on a second axis). All statistics — sample standard deviation, Silverman-bandwidth KDE, violin polygons, cumulative shares — are computed in pure Python and rendered by all three engines from one spec; only radar is greyed out for Vega-Lite (the grammar has no polar coordinates).
- **Color palettes** — a new *Colors* selector with 8 curated palettes (Vivid, Colorblind safe, Viridis, Sunset, Ocean, Earth, Berry, Grayscale print). The chosen palette overrides the theme palette identically in ECharts, Plotly and Vega-Lite, for single charts and Explore dashboards alike.
- Engine correctness fixes caught during visual verification: ECharts custom-series whiskers and violins now set explicit axis extents (custom series don't drive autoscaling, so +σ caps and violin tails were clipped) and error whiskers use a contrasting colour; Vega-Lite multi-series density sets `stack: null` (stacked-area zero-imputation turned smooth KDE curves into a sawtooth) and the violin ranged area dropped its `order` channel (it faceted every violin into degenerate per-row paths).
- Verified headless: 71 checks on real QGIS Python and 50 chart pages in headless Chrome with zero JS exceptions; every new engine×type screenshot eyeballed.

## [0.5.0] - 2026-06-12

- **Third engine: Vega-Lite** — vendored `vega` + `vega-lite` (BSD-3, compiled in the page, no vega-embed), fully offline. Renders 9 of the 11 chart types from the same spec contract (treemap/sunburst are not part of the Vega-Lite grammar), including layered box plots from precomputed quartiles and labelled heatmaps. Chart→map clicks and map→chart cross-filter dimming work exactly as in the other engines.
- **Engine-aware chart types** — engines declare what they can draw (`ChartEngine.supports`); the dock greys out unsupported types and jumps to the nearest supported one.
- **Heatmap label contrast fixed in all engines** — on sequential ramps, low-value cells are light, so white labels were invisible there; white is now used only on dark cells (both extremes on diverging ramps, upper end on sequential ones).

## [0.4.0] - 2026-06-12

- **Map → chart cross-filter**: selecting features on the canvas instantly dims every non-selected bar, slice and point — in the single chart and across all dashboard tiles — without a re-render (ECharts per-item opacity, Plotly native `selectedpoints`). A freshly rendered chart immediately reflects the current selection.
- **Crash-safe selection bridge**: chart clicks now reach QGIS through the page title (`titleChanged`) instead of `addToJavaScriptWindowObject`/`QWebChannel`, fixing an access-violation crash on QGIS builds that ship the legacy QtWebKit stack. One code path for WebKit and WebEngine; vendored `qwebchannel.js` removed.
- The chart→map bridge logic is now covered by headless real-QGIS tests (38 checks) and the cross-filter by the headless-Chrome harness (24 pages).

## [0.3.1] - 2026-06-12

- Package directory renamed from `02viz` to `zero2viz`: the QGIS Plugin Hub requires the zip's top-level directory to be a valid Python identifier (PEP 8), and the digit-first name was rejected at upload. The displayed plugin name, features and behaviour are unchanged.

## [0.3.0] - 2026-06-12

- ✨ **Explore**: one click profiles every field of a layer and builds a complete interactive dashboard — KPI cards, a count bar per categorical field, a histogram per numeric field, a Pearson correlation matrix (diverging colors), the strongest-relationship scatter with trend line, and plain-English insight chips. Exports as a single HTML file; every chart stays clickable (chart → map selection).
- Honest axes: bar, area and histogram value axes now always start at zero (a category could previously disappear).
- Heatmap polish: per-cell value labels with automatic contrast (white on intense cells) and a diverging red-teal ramp for correlation matrices in both engines.
- Human number formatting in titles and insights (219,250 instead of 2.192e+05).

## [0.2.0] - 2026-06-12

- Second rendering engine: Plotly.js (vendored, MIT) — switch engines per chart, same spec.
- Five new chart types: area (stackable), bubble, heatmap (matrix), treemap, sunburst — 11 total.
- Chart → map selection: clicking a bar, slice or point selects the matching features on the canvas (QtWebKit window-object bridge + QtWebEngine QWebChannel bridge).
- Live mode: optionally re-render the chart on every canvas selection change (with a loop guard for chart-driven selections).
- Group / Color-by field: grouped or stacked multi-series bars, multi-line, multi-area, colored scatter series.
- Data shaping: Top-N with "Other" collapse, value sorting, least-squares trend line, sqrt-scaled bubble sizes.
- Four themes (Studio Light, Ink Dark, Soft Pastel, Bold Print) applied consistently across both engines.
- Verified headless: 31 checks on real QGIS Python + all 22 engine×type combinations rendered in headless Chrome with zero JS errors.

## [0.1.0] - 2026-06-12

- Chart studio MVP: bar, line, scatter, histogram, pie/donut and box plot rendered by a vendored Apache ECharts engine (interactive HTML, fully offline).
- Data binding: any vector layer or table, selected-features-only mode, external CSV/XLSX/ODS tables via OGR.
- Pure-Python aggregation (count/sum/mean/median/min/max), histogram binning, box-plot quartiles.
- Embedded viewer with WebEngine → WebKit → system-browser fallback chain; one-file interactive HTML export.
