Skip to content

release: v0.1.4 — VL annotations + layer0 zoom - #9

Merged
Roy-Kid merged 20 commits into
MolCrafts:masterfrom
Roy-Kid:feat/annotations-scalebar-arrow
Aug 5, 2026
Merged

release: v0.1.4 — VL annotations + layer0 zoom#9
Roy-Kid merged 20 commits into
MolCrafts:masterfrom
Roy-Kid:feat/annotations-scalebar-arrow

Conversation

@Roy-Kid

@Roy-Kid Roy-Kid commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Expand annotations (scaleBar / arrow) into pure Vega-Lite rule/text layers so regime markers pan/zoom with the chart.
  • Put bind:scales zoom params on layer[0] only — top-level params + multi-layer specs caused duplicate VL selection signals and silent render failure.
  • Keep interactive unit tests out of src/ so dist stays free of *.test.js; ignore staged docs/assets in biome.

Test plan

  • rstest + python tests + pre-commit
  • smoke: multi-layer + annotations compile/parse; 9 green rules + 3 labels in scenegraph
  • CI green on PR
  • After merge: tag v0.1.4 → npm + GitHub release

Roy-Kid and others added 20 commits July 4, 2026 15:58
…ter (MolCrafts#1)

Replace the ad-hoc per-mark render.py prototype with a small interpreter
package (molplot.vlmpl) structured as four compiler passes: normalize →
bind scales → dispatch marks → finalize axes. Mark encoders read typed
channels instead of hardcoded field names, so marks and scales are extensible.

Fixes two Vega-Lite → matplotlib parity gaps the prototype had:
- positional scales are applied: log axes (scale.type) and explicit domains
  (scale.domain → axis limits) now reach the figure.
- per-layer transform filters are honoured, so a plain line chart no longer
  draws the stray marker-layer points; the detail channel splits series that
  share a legend colour.

render.py becomes a thin shim re-exporting molplot.vlmpl.render, preserving the
public molplot.render API. Adds test_vlmpl.py for the fixed behaviour.
Every chart now declares one scale-bound interval selection per continuous
axis. Drag pans, a wheel over an axis gutter zooms that axis alone,
shift+wheel zooms every bound axis, double click resets. A bare wheel over
the plot is left untouched so an enclosing panel still scrolls.

Band scales (bar's category axis, gantt's task axis) get no param at all —
Vega-Lite refuses to bind a discrete domain — so they are inert by
construction rather than by special case.

Three Vega constraints drove the shape of this:

- Selection params must sit on exactly one unit layer. At the top level of a
  layered spec Vega-Lite copies them into every layer and Vega then throws
  "Duplicate signal name" at parse time, while vl.compile() stays silent.
  specs.test.ts compiles each spec and asserts no duplicate signal names.

- `bind: "scales"` defaults its wheel stream to `source: "scope"` — the plot
  group. Axes render with pointer-events: none, so an axis wheel falls
  through to the SVG root and never enters that group. Switching to `view:`
  catches it, and keeps sibling charts on the page from reacting.

- A Vega event filter is compiled without the signal scope object: it may
  call x()/y() but throws `ReferenceError: _ is not defined` on any signal
  read. That rules out `y() > height`, so VegaChart resolves the pointer
  against the scenegraph's axis bounds and hands the spec a boolean flag.
  `|| event.shiftKey` keeps the spec self-sufficient for a raw vegaEmbed.

The classification is a pure function (axisChannelAt) tested with plain
rectangles: grid boxes coincide with the plot rect, legends never carry
role "axis", and a wheel below a bottom legend must stay inert.

Marks are now clipped, without which a zoomed mark paints over the axes.

`params` is inert on paper: render.py reads only encoding/layer/mark, so the
matplotlib translator and vl-convert draw the initial view.
`npm run dev` now aliases dev:page, so the gallery starts from the repo root
like every other MolCrafts package.

Zoom is gated on hovering an axis or holding shift, neither of which is
discoverable. The header names the four gestures, and each card says which
of its axes can be zoomed — bar and gantt zoom only one, because the other
is a band scale.
Moves zensical.toml onto the [project] schema and gives docs/index.md a hero
front matter block (kicker, install command, npm/PyPI/licence badges).

Adds a `doc` dependency group to python/pyproject.toml pinning zensical and
the shared molcrafts-zensical-theme, and a Pages workflow that builds the
site from that group alone — the docs are hand-written Markdown, so the job
needs neither Node, the preset compiler, nor a wheel.
Lock the npm package, the Python package and the workspace root to one
version — the v* tag fires release-core.yml and release-python.yml together,
so a mismatch would publish two different versions under one tag.
The workflow was copied from molpack, which still publishes to
molcrafts.github.io. This repo has no Pages site, so `actions/configure-pages`
failed on the first push to master with "Get Pages site failed".

Docs here deploy the way molpy and molvis do: Cloudflare builds the Zensical
site straight from the repo, and neither of those ships a docs workflow. So
remove ours and move site_url onto the same <name>.molcrafts.org convention.

The `doc` dependency group stays — the Cloudflare build installs zensical and
the shared molcrafts theme from it.
The v0.1.1 release PR was rebase-merged, which rewrote dev's commits onto
master under new SHAs. dev and master then held the same tree under two
histories, so the next dev->master PR replayed five duplicate commits.

Merging master back in makes it an ancestor of dev again; future dev->master
merges fast-forward. Use a merge commit for the release PR next time.
Unify local hooks and CI on `uv run --extra dev tox -e lint|py`.
tox lives in optional-dependencies dev; prek language: system.
Add portable annotation layers (scaleBar |———|, arrow) in TS and Python.
RawChart/fence top-level `annotations` expand before render; matplotlib
gains rule/text mark encoders. Bump to 0.1.4.
Support absolute x2/y2 ends and geometric tickRatio for log axes;
region labels use Times italic.
fontScaleForWidth is 2–3× (200% at design width). Axis/text marks use
normal Times style; annotation labels no longer italic or fixed px.
scaleBar orientation=along draws a chord with log-space perpendicular
caps (matplotlib-style). fontScaleForWidth floor 3× paper, cap 4.5×.
Translate the bar along the log-space path normal (default 0.42) so it
does not overlap the curve; end-caps remain ⊥ to the bar.
One scale_bar() owns spine + both end-caps + label — never hand-draw
caps. Mirrors FancyArrowPatch(arrowstyle='|-|') / AnchoredSizeBar.
Zoom params bind at top-level when encoding is shared (docs layered
specs); layer0-only placement left bind:scales inert. Scale bars/arrows
are drawn as a screen-space SVG overlay from Vega scales after layout
and on pan/zoom — not pre-baked VL geometry.
Expand scaleBar/arrow into rule/text layers that share chart scales so they
pan/zoom with the plot. Put bind:scales params on layer[0] only — top-level
params with multi-layer specs make Vega-Lite emit duplicate zoom signals and
vega.parse fails (chart invisible).

Move interactive unit test out of src/ so dist stays free of *.test.js.
Ignore staged docs/assets in biome.
Annotations as pure Vega-Lite layers (scaleBar/arrow); zoom params on
layer[0] so multi-layer charts parse without duplicate signals. Docs
pan/zoom with regime markers.
@Roy-Kid
Roy-Kid merged commit a0be690 into MolCrafts:master Aug 5, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant