Conversation
…ter (#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.
This was referenced Jul 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release PR — integrates
devintomasterso thev0.1.1tag can be cut.Contents
feat(core)— pan/zoom with per-axis wheel targeting (feat: pan/zoom with per-axis wheel targeting (v0.1.1) #2)feat(page)—npm run dev, gallery gesture hints (feat: pan/zoom with per-axis wheel targeting (v0.1.1) #2)docs— zensical landing page + GitHub Pages deploy (feat: pan/zoom with per-axis wheel targeting (v0.1.1) #2)feat(render)— VL→matplotlib staged tree-walking interpreter (feat(render): rewrite VL→matplotlib as a staged tree-walking interpreter #1)chore(release)— lock npm / PyPI / workspace root to 0.1.1Publishing
The
v0.1.1tag firesrelease-core.yml(npm) andrelease-python.yml(PyPI)together, which is why all three version strings are bumped in one commit.
Note:
molcrafts-molplothas never been published, so 0.1.1 will be its firstPyPI release (there is no 0.1.0 on PyPI).
@molcrafts/molplotgoes 0.1.0 → 0.1.1on npm as normal.
Test plan
npm run test:core— 57 passingcd python && pytest— 36 passing (interpreter + pan/zoom specs together)