Skip to content

Add Markdown fenced block support for embedding charts#8

Merged
Roy-Kid merged 3 commits into
masterfrom
claude/molplot-html-markdown-3ha0bq
Jul 12, 2026
Merged

Add Markdown fenced block support for embedding charts#8
Roy-Kid merged 3 commits into
masterfrom
claude/molplot-html-markdown-3ha0bq

Conversation

@Roy-Kid

@Roy-Kid Roy-Kid commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a molplot fenced code block for Markdown documentation, allowing authors to embed live Vega-Lite charts directly in docs by writing a spec in YAML or JSON. The fence is a build-time text transform that compiles to a <molplot-chart> Web Component, which renders in the browser via the self-registering custom element.

Key Changes

  • Python Markdown formatter (python/src/molplot/mdx.py): New module implementing pymdownx.superfences custom fence support

    • render_element() — converts a Vega-Lite spec (YAML/JSON) to a <molplot-chart> HTML element with the spec embedded in a <script type="application/json"> block
    • molplot_fence() — superfences formatter entry point
    • molplot_validator() — validates fence-header options (preset, theme, type)
    • Graceful error handling: parse failures render inline error divs instead of breaking the build
  • Web Component (core/src/element.ts): New <molplot-chart> custom element

    • defineMolplotChart() — registers the element (idempotent, browser-only, no side effects on import)
    • parseSpec() — reads spec from child <script type="application/json"> or spec attribute
    • Wraps RawChart to inject unified preset and theme tracking
    • Dispatches molplot:ready event when chart mounts
  • Documentation (docs/getting-started/markdown.md): New guide covering

    • Fence syntax and options (preset, theme)
    • Raw <molplot-chart> element for hand-written HTML
    • Setup instructions (load elements bundle, register fence in zensical.toml)
  • Build configuration

    • core/rslib.config.ts: New elements library export (self-contained bundle with vega-embed bundled, vega lazy-loaded)
    • core/src/element_entry.ts: Entry point for elements bundle
    • core/package.json: Export map for ./elements subpath
    • zensical.toml: Load elements bundle via CDN, register molplot fence with validator
  • Preset refinements: Updated descriptions and typography

    • molplot preset: Changed default font to serif (Times New Roman), reduced line width from 2 to 1 pt, updated categorical palette to scienceplots' standard 7-colour cycle, adjusted light/dark theme colors for better contrast
    • molplot-paper preset: Updated description to clarify layering on both 'science' and 'nature' bases
    • All generated artifacts (core/src/presets/generated.ts, python/src/molplot/presets/_generated.py, .mplstyle files) regenerated
  • Tests

    • python/tests/test_mdx.py: Comprehensive fence formatter tests (YAML/JSON parsing, option forwarding, error handling, validator)
    • core/tests/element.test.ts: parseSpec() and RawChart integration tests with fake Vega
  • API exports: core/src/index.ts now exports defineMolplotChart, parseSpec, and RawChart constructor options for preset/theme

Implementation Details

  • The fence is a build-time text transform — it does not draw charts, only generates HTML. The browser's <molplot-chart> element (loaded via CDN) handles rendering.
  • Spec is embedded in <script type="application/json"> to survive Markdown/HTML sanitization of special characters.
  • Wrapped in <div class="molplot"> to prevent md_in_html from wrapping in <p> tags or reprocessing children.
  • Elements bundle is self-contained (vega-embed bundled) but vega runtime is lazy-loaded via dynamic import, keeping registration cheap (~350 KB deferred).
  • Preset changes align the web renderer with scienceplots' 'science' base for visual parity between browser and matplotlib figures.

https://claude.ai/code/session_01FwRZiMJXPcHWZLDUhduxmC

claude added 3 commits July 12, 2026 09:11
…mponent

Adds an HTML-in-Markdown authoring path for the Zensical docs: an author
writes a plain Vega-Lite spec (the existing intermediate language) and it
renders live as an interactive chart, with the unified preset and light/dark
theme tracking injected automatically. No bespoke config schema.

Two authoring syntaxes, both producing the same element:

- Raw `<molplot-chart>` custom element with the spec in a nested
  `<script type="application/json">` block (works via md_in_html).
- A `pymdownx.superfences` `molplot` fenced block that compiles to that
  element at build time (a pure text transform; it does not draw the chart).

Web component (core/src/element.ts):
- `<molplot-chart>` wraps the existing RawChart; light DOM; lifecycle wired
  to connected/disconnected/attributeChanged. `preset`/`theme`/`spec`
  attributes. The class is defined lazily inside `defineMolplotChart()` so
  importing the library never evaluates `class extends HTMLElement`, keeping
  it import-side-effect-free and SSR-safe.
- New self-contained browser bundle: a bundled `elements` rslib entry
  (autoExternal off) that self-registers the element; vega-embed stays a
  lazily code-split chunk, so registration is cheap and the runtime only
  downloads when a chart mounts. Exposed as the `./elements` package export.
- RawChart gains optional `preset`/`theme` config fields (backward compatible)
  so a named preset can be injected while preserving dark-mode tracking.

Markdown fence (python/src/molplot/mdx.py):
- `molplot_fence` formatter emits the `<molplot-chart>` element (wrapped in a
  block-level `<div class="molplot">` so md_in_html leaves it intact), and a
  `molplot_validator` that whitelists `preset`/`theme`/`type` options.
- zensical.toml loads the elements bundle via extra_javascript and registers
  the fence; because declaring markdown_extensions replaces Zensical's
  defaults, the full default set is re-listed alongside the new fence.

Tests: headless element/RawChart coverage (core) and formatter/validator
coverage (python); new docs page getting-started/markdown.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FwRZiMJXPcHWZLDUhduxmC
The preset's categorical palette drives both the web Vega range and the
matplotlib prop_cycle, but it shipped d3's category20 — which the generated
mplstyle then layered *over* scienceplots' own colours, so browser and paper
figures didn't actually match. Re-derive the tokens from scienceplots so the
"one preset, two renderers" promise holds.

- Categorical palette → scienceplots' standard seven-colour "science" cycle
  (0c5da5, 00b945, ff9500, ff2c00, 845b97, 474747, 9e9e9e), shared by both
  presets so web and paper stay colour-consistent.
- `molplot` default now matches the science base: serif type, 1.0 pt lines,
  black-on-white axes. `molplot-paper` keeps its tighter nature-sized figure
  and higher DPI, with markers at 3 pt.
- The Python side still layers this overlay on the scienceplots base, so it
  also inherits science's tick geometry (direction in, minor ticks, top/right).

Regenerated core/src/presets/generated.ts, the Python _generated.py, and the
four .mplstyle files from the two hand-edited presets/*.json. Updated the
palette assertions in the TS/Python tests and the palette references in the
docs. Full stack verified: molplot.use("molplot") renders lines in the science
cycle with serif type and 1.0 pt strokes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FwRZiMJXPcHWZLDUhduxmC
The test-python CI job installs `.[dev]`, but pyyaml lived only in the PEP-735
`doc` dependency group, so `import yaml` failed in molplot.mdx during CI and the
YAML-fed test_mdx.py cases hit the error branch (3 failed, 40 passed). It passed
locally only because pyyaml happened to be installed system-wide.

Declare pyyaml where it belongs: a new `mdx` extra (the fence formatter's
runtime dep) and add it to `dev` so the test env installs it. The `doc` group
already lists pyyaml for the Zensical build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FwRZiMJXPcHWZLDUhduxmC
@Roy-Kid
Roy-Kid merged commit 701ab66 into master Jul 12, 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.

2 participants