Skip to content

markvis 2.2.0

Choose a tag to compare

@geekplux geekplux released this 19 Oct 04:54
· 12 commits to master since this release

Draw a chart block anywhere Markdown is shown: one call from JavaScript, React with streaming, one script tag in any Markdown page, rehype for MDX hosts, and a GitHub Action. Four more chart types, the graphite theme, and a measured instruction for models. The language stays markvis: 2. A 2.1 block still parses unless it hits one of the stricter checks listed under Breaking.

Breaking

  • The ES module markvis.mjs no longer draws on import. A page that loads it with <script type="module" src=".../markvis@2/dist/markvis.mjs"> and relied on charts drawing by themselves now draws nothing. Import it and call run(), or load the classic markvis.min.js, which still draws once the page is ready.
  • The drop-in's global (window.markvis) and markvis.mjs export run, init, initialize, render, and parseBlock, and nothing else; the classic markvis.min.js global also carries startsOnLoad, which reads a script tag's data-start-on-load. replaceLanguageBlocks, replaceInDocument, autoReplace, wrapFence, chartBlockHtml, resultToHtml, htmlTable, escapeHtml, parseMarkdown, renderSvg, isChartBlock, enhanceChartSvg, and tipTextForMark are gone from it. The markvis npm package still exports the parser and renderer functions.
  • Stricter input checks. These blocks drew in 2.1 and now fail with a stable code, keeping their table: a non-numeric measure cell such as N/A (E_BAD_NUMBER); an empty cell on pie, funnel, sankey, treemap, waterfall, gauge, or a stacked/percent layout (E_MISSING_VALUE); a repeated category/series key, including a gauge with more than one row (E_DUP_KEY); a sankey cycle (E_SANKEY_CYCLE); and markvis: set to anything but 2 (E_BAD_VERSION). A gauge without max now draws against 0–100. See docs/release-2.2.md (Compatibility, Migration).

Added

  • Charts follow the host page through CSS variables, with fixed-color fallbacks. A chart drawn in a page writes each color as a fixed attribute plus a style that reads --markvis-ink, --markvis-muted, --markvis-grid, --markvis-paper, --markvis-series-1 … --markvis-series-8, and the rest of 18 names (and an opt-in --markvis-font), so light, dark, and brand colors carry into the chart. The list ships as CSS_VARIABLES and PAINT_NAMES from markvis/themes. colors: "host" | "static" on render(), renderSvg(), initialize(), and <Markvis>; --colors host|static on markvis render and bake. Page: Theming.
  • parseBlock(text, { filename? }) parses one chart block: the inside a Markdown renderer hands a code-block plugin, or the whole block.
  • render(text, options?) returns { ok, svg, html, chart } or { ok: false, error, html, table } in one call and never throws. html is the same figure, or table and one error line, that every adapter emits. Options: width, and theme / palette / surface for blocks that leave them out.
  • markvis/react: <Markvis source> or <Markvis chart data> draws one block at the width of its container; markvisComponents draws chart code blocks in react-markdown; remarkMarkvisStreaming shows a placeholder and the rows so far while a block streams, never an error, and <Markvis streaming> does the same for a host that passes a block's partial text to its own renderer. react is an optional peer. It starts with "use client"; so server-component frameworks load it as a client module.
  • One script tag in any Markdown page: the drop-in finds chart blocks in the HTML of marked, markdown-it, highlight.js, Jekyll (kramdown + rouge), Hugo, MkDocs (custom fence), Pandoc, and docsify, and draws the comment + table form from the page. markvis.run({ nodes, querySelector, root }), markvis.initialize({ theme, palette, surface, width }), and data-start-on-load="false".
  • markvis/rehype: chart code blocks become real hast (figure, SVG, table) for MDX hosts such as Docusaurus and Astro. No raw HTML nodes, no new runtime dependency.
  • A GitHub Action for any repository: uses: geekplux/markvis@master bakes chart blocks to SVG and commits them, so github.com shows the charts. Inputs paths, commit, message; a second run commits nothing. It runs the markvis release it ships with, commits only the files bake writes, and never pushes for a pull request from a fork.
  • A 250-token model instruction (docs/prompt.md, also the opening of llms.txt and Integrate's Tell a model section). pnpm eval-prompts --outputs <dir> scores a directory of model answers: valid blocks, error codes, and chart type against examples/prompts.md.
  • Site: a one-screen home whose first screen is a chart block that types itself while render() draws it, through each step from writing the block to its table fallback, on real data (the largest US energy flows in 2023, US EIA); a click opens it as a live editor. Below it: four ways in, a Your colors section (one SVG on a light and a dark page), and a card for each of the seventeen chart types. Charts on the site follow the site's light or dark mode, the social card is the hero's finished chart, and a Theming page lists the CSS variables. One generated page per chart type. /themes and /ai redirect to Theming and Integrate.
  • Wave 4 types: dumbbell (two values per category, first hollow and second filled, signed change at the right) bullet (actual against an optional target: column, optional min / max), boxplot (distributions from raw rows, type-7 quartiles, Tukey whiskers), and calendar (one cell per YYYY-MM-DD day; new code E_BAD_DATE).
  • graphite theme: editorial mono with a gray ladder, one accent on the largest value, pill bars, and heavy numbers. Inspired by lieflat-charts.
  • Playground Surface control (?surface=dark).
  • Horizontal bars (orient), surfaces (light / dark / export), heatmap color domain, waterfall role, and width-aware renderSvg. See docs/release-2.2.md.

Changed

  • Inline SVG bytes: a chart drawn in a page (render(), the drop-in, React, markdown-it, remark, rehype, renderSvg()) carries one style attribute per colored element and a font-family variable on the root, about 0.8 kB more per chart (about 0.1–0.2 kB gzip). bake, markvis render, and surface: export write the attribute only by default; host colors never change a baked file (--colors host opts in).
  • The drop-in renders through render(): an empty block's error table shows the block as the page holds it, not a fence wrapped around it.
  • The browser drop-in keeps cells past the header width in the error table, like every other path.
  • markdown-it, remark, rehype, React, the drop-in, the Playground, and the command line's preview and gallery share one HTML module, so their output is the same. The command line's error table now keeps cells past the header width too.
  • The themes now look distinct in structure, not just color: each owns its type ramp (floor: title 15, other roles 11), title alignment and subtitle, grid dash, axis lines and ticks, curve, point markers, legend keys, card frame, and dark paper. Every pair differs on at least four structural switches. See docs/themes.md.
  • Readable type, wrapped labels, explicit paper/dark/export surfaces, gauge range 0–100 when max is omitted, sankey thickness. Funnel stays a centered silhouette.

Fixed

  • Narrow widths: below the theme's width (720) a chart's height follows its width at the theme's aspect (never under 200), and waterfall step labels split over two lines no longer reach into the title. A title wraps at most once at any width and ends in an ellipsis; the full title stays in the tooltip.
  • A line or area over whole-number x (weeks 1–4, years) spans its data instead of padding to a 0 or 5 that no row has: up to 12 distinct values, each is a tick; past that, only whole-number ticks inside the data.
  • Values spanning about -1e308 to 1e308 no longer hang axis ticks (bar, dumbbell, bullet, boxplot) or crash histogram bins.
  • A fence cut off before its closing line, or an indented fence, keeps its rows when parsed as one block.
  • Chart ids no longer import node:crypto, so render() and markvis/react bundle for the browser without a shim. Ids are unchanged.
  • A top legend no longer sits on a wrapped title, and the y-axis title no longer runs through tick labels or repeats the unit.
  • Non-numeric cells, duplicate keys, markvis version, multi-chart check, bake references, ragged fallback cells, and line gaps for missing values.
  • Bullet charts keep a positive track at tiny widths; a value range that overflows to Infinity no longer writes NaN into a color or corner radius. Horizontal bar, dumbbell, boxplot, heatmap, pie and the axis charts also keep a positive plot (or radius) at tiny widths, and row charts drop their per-row value labels when there is no room for them.
  • A stacked or percent bar reads as one bar in a theme with rounded bars: only its outermost segment is capped, the segments under it are plain rectangles, and the cap sits on the last series that has a value in that category. Before, every segment carried its own rounded end.

What 2.2.0 ships. Language version stays markvis: 2. The new ways to draw a block (render, markvis/react, the script tag in any Markdown page, markvis/rehype, the GitHub Action, the model instruction) are listed in CHANGELOG.md; this note covers the chart changes and the site.

Charts follow the host page

The one chart-visible change beyond layout: a chart drawn in a page takes its colors from the page.

  • Every chart drawn in a page (the script tag, render(), renderSvg(), React, markdown-it, remark, rehype) writes each color twice: a fixed attribute, and a style that reads a CSS variable with the same color as its fallback, for example fill="#3B82F6" style="fill:var(--markvis-series-1,#3B82F6)". The root <svg> reads --markvis-font.
  • Eighteen color variables: --markvis-ink, --markvis-muted, --markvis-grid, --markvis-paper, --markvis-plate, --markvis-plot, --markvis-series-1 through --markvis-series-8, --markvis-accent, --markvis-up, --markvis-down, --markvis-total, plus the opt-in --markvis-font. They ship as CSS_VARIABLES and PAINT_NAMES from markvis/themes.
  • A page that sets none of them sees the same colors as before. Where styles are stripped, or the SVG is opened as an image, the fixed color shows.
  • Files keep fixed colors: markvis bake, markvis render, and surface: export write the attribute only; host colors never change a baked file (--colors host opts in). colors: "host" | "static" on render(), renderSvg(), initialize(), and <Markvis>, and --colors host|static on render and bake, choose either mode.
  • Heatmap and calendar color mixes, labels drawn on a mark, and the export plot border stay fixed. The Theming page lists every variable and three host snippets (system light or dark mode, a dark class on the page, and an editor theme's own variables).
  • Inline SVG grows by about 0.8 kB per chart (about 0.1 to 0.2 kB gzip).

Narrow widths

  • Below the theme's width (720) a chart's height follows its width at the theme's aspect, never under 200. A title wraps at most once and ends in an ellipsis; the full title stays in the tooltip.
  • A line or area over whole-number x (weeks, years) spans its data with whole-number ticks instead of padding to a value no row has.

Site

markvis.js.org, not an npm API change:

  • The home page fits its point on one screen. The first screen is a chart block that types itself, and the chart beside it is render() on that text, step by step: write, paste, change the kind (bar, sankey, treemap, heatmap), stack, theme, palette, page color, dark, width, and the table that stays when a block is invalid. The rows are real: the ten largest US source-to-sector energy flows in 2023 (US EIA, Monthly Energy Review). A click or a key opens the block as a live editor; reduced motion shows the finished chart with a Play button.
  • Below it: four ways in (script tag, JavaScript, React, GitHub), Your colors (one SVG drawn on a light and a dark page, each setting a few --markvis-* variables), and "Seventeen kinds of chart": one card per type, each drawing the first example of that type, with its block and an Open in Play link to the same file. A numbers line (58 kB gzip · 0 dependencies · under 1 ms a chart · plain SVG) is held to dist/markvis.min.js by a test.
  • Charts on the site, in the examples gallery, and in Play follow the site's light or dark mode through the same CSS variables, so no chart sits on a white card in dark mode.
  • The social card (og.png) is the hero's finished chart.
  • The docs sidebar runs Get started, Integrate, Theming, Chart types, Spec, Contributing. Theme content and the CSS variables live on the new Theming page; the model instruction moves to Integrate's "Tell a model". /themes and /ai redirect.
  • The Playground is called Play.
  • The README opens with a chart block and its picture, the numbers line, four ways in, and the model line.

User-visible improvements

  • Figures are laid out for the width you ask for. renderSvg(chart, { width: 390 }) reflows the title, labels, and plot. Type stays about 21px for titles and 12px for ticks instead of shrinking with the SVG.
  • Long titles wrap, including the unit on the last line, and the plot starts below those lines. Long category labels wrap or truncate, and the full string stays in an SVG <title>.
  • Bar charts accept orient: horizontal for long labels. Vertical fences stay vertical. Input order and a zero baseline stay.
  • Heatmaps draw a labeled color scale. A missing cell is hatched, not painted as the low color. min and max share that scale across figures.
  • Treemaps use squarified tiles, parent headings, and parent-related colors. Tiny cells are not labeled.
  • Sankey ribbons use one thickness for a value, at both ends and across the figure. Outer labels use that margin, and a truncated name keeps the full string in a <title>.
  • Waterfall bars show the delta and the level after it. Increases and decreases use different colors and a sign. An optional role column can mark total or subtotal. Step text is never treated as a total.
  • Gauges are a labeled range. If you omit max, the range is 0–100, and the unit sits next to the value. A value outside the range is marked above or below range.
  • Radar charts show numeric rings, one scale for every series, and lighter fills. A grouped bar is the clearer choice when the exact number matters.
  • Funnels stay centered bands that narrow from each stage to the next, with the stage name and value to the right of the shape.
  • Line and area charts leave a gap for an empty cell. They do not connect through it or drop it to zero.
  • surface: light (default) keeps a transparent canvas and dark ink, so the figure sits on the Markdown host. surface: dark and surface: export paint their own paper.

Correctness fixes

  • 0 stays zero. An empty measure cell is missing. N/A, abc, and any other non-numeric text is E_BAD_NUMBER with the row and column. The original table is kept.
  • A repeated category/series key is E_DUP_KEY. Scatter, histogram, and waterfall step labels may repeat. Nothing silently keeps the last row.
  • Tick and value labels use one formatter. 0.001, 0.002, and 0.003 stay distinct. Negative zero is shown as 0.
  • Sankey thickness is value × one scale. Equal values match. A cycle is E_SANKEY_CYCLE.
  • markvis check reads every chart in a file and in a directory. One invalid chart makes the exit code non-zero and names the file and chart. Valid siblings are still reported.
  • markvis bake rewrites an owned image reference when the chart count changes, overwrites the SVG that reference points at, and leaves author images alone. A second bake of an unchanged file does not rewrite it. A generated file is deleted only when this bake owned it and no chart still references it.
  • markvis: 99 is E_BAD_VERSION. Omitting markvis still means version 2.
  • Fallback HTML from markdown-it and remark keeps extra cells that the parser kept.

Intentional visual changes

  • Default type is larger. Titles are 21px, ticks 12px, values 13px.
  • The default figure does not paint a background. The host page does.
  • Category labels are no longer rotated 55 degrees.
  • Theme packs still differ in chrome, palette, and any type size already larger than the readable floor.

Compatibility

  • Existing vertical fences, themes, palettes, and layouts still parse.
  • These inputs now fail instead of drawing a misleading figure:
    • markvis set to anything other than 2
    • a non-numeric measure cell
    • an empty cell on pie, funnel, sankey, treemap, waterfall, gauge, or a stacked/percent layout
    • a repeated category/series key on bar, line, area, pie, heatmap, radar, sankey, or treemap
    • a sankey cycle
    • a gauge with more than one row
  • A gauge that omitted max used to draw a full arc (max(y, 1)). It now draws against 0–100 unless you set max.
  • Baked filenames for a single chart stay name.svg. Adding a second chart renames the owned reference to name-1.svg and writes name-2.svg.

Migration

  • Put max on a gauge whose value is not a percent of 100.
  • Replace N/A or blank required cells, or switch a line/area chart to a gap by leaving the cell empty.
  • Split or delete duplicate category/series rows. Do not rely on the last row winning.
  • Remove sankey cycles, or the chart will fail and keep the table.
  • Re-run markvis bake once so image references match the new filenames after you add or reorder charts.