Release 1.7
Version 1.7 of Unovis is here! This is our biggest release to date. It brings three new components — Boxplot, Radial Bar and Heatmap; takes Unovis beyond the browser with server-side rendering and an MCP server that lets AI agents build charts; adds chart-wide color synchronization and pattern fills; and comes with smarter axes, a better crosshair, waterfall charts, Content Security Policy support, and a long list of performance improvements and bug fixes.
We're excited to welcome our new first-time contributors to the Unovis community: @Vincentdevreede and @kristof-mattei. Huge thanks as well to @devgru and @50rayn for their continued contributions! 🎉
Release Highlights
📊 New Components: Boxplot, Radial Bar & Heatmap
Introducing three new components:
Boxplot - Box-and-whisker plots for XY Container:
- Three grouped accessors —
median,quartiles([q1, q3]) andwhiskers([min, max]) — so a box can never render half-drawn - Use a subset of the accessors for quartiles-only or median-only plots
- Configurable
barWidth,barMaxWidth,barPadding,roundedCornersanddataStepfor missing data - Works with Axis, Crosshair, Tooltip, Brush and the other XY components
Radial Bar - Stacked-ring charts for Single Container:
- One circular track per data record, filled proportionally to its value
- Configurable
angleRangefor partial and reversed rings, stacking order and sorting barMinAnglekeeps tiny values visible, and0is now distinguished from missing data (docs)- Labels, background tracks, corner radius and a full set of
--vis-radial-bar-*CSS variables
Heatmap - A grid of colored cells driven by a flat data array, like the GitHub contributions calendar:
- Control the grid with
numRows/numColumns,layout(columnorrow) andoffset - Built-in quantized color scale, overridable with
colorRange+colorDomainor a per-cellcoloraccessor; empty cells get a dedicated fill cellSize,cellPadding,cellCornerRadius, and intrinsic sizing withSizing.Extend- Native
rowLabel/columnLabelaccessors with automatic overlap hiding
Check out Boxplot's documentation, Radial Bar's documentation, and Heatmap's documentation, or explore the new gallery examples: Basic Boxplot, Radial Bar Chart and GitHub-Style Heatmap.
🤖 MCP Server: Charts from an AI Agent
Unovis can now be driven by an AI agent. The new @unovis/mcp package is an MCP server that turns data into Unovis charts and hands back an SVG, a PNG, a live HTML file, or ready-to-paste component source — all rendered locally in Node, with no browser, no remote rendering service, and no data leaving the machine.
claude mcp add unovis -- npx -y @unovis/mcp
- Fifteen chart tools — line, area, bar, scatter, timeline, boxplot, donut, nested donut, radial bar, sankey, treemap, chord diagram, network graph, heatmap and choropleth map — plus
get_unovis_infofor capability discovery - Accessors by field name instead of functions (
x: "month",y: ["sales", "cost"]), validated against the data, so a typo comes back as a helpful error instead of an empty chart - Six output types, one spec —
svg,png,html(a self-contained interactive chart with tooltips, crosshair and legend),interactive(rendered inline by hosts supporting the MCP Apps extension),config(the chart spec as JSON) andcode(TypeScript, React, Svelte, Vue, Angular or Solid source) - Shared across the tools: light and dark themes, titles, axis labels, legends, custom palettes, locale-aware formatting, reference lines, reference bands and annotations
- Works with Claude Code, Claude Desktop, Cursor, Codex, VS Code and any other MCP client — stdio by default, streamable HTTP behind
--transport http
Check out the MCP Server documentation to get started, and the pull request for the full story.
🖥️ Server-Side Rendering with @unovis/ssr
Headless rendering ships as its own package, @unovis/ssr: a jsdom-based environment with real canvas text metrics, geometry polyfills and a flushable animation frame queue, plus a post-processing pass that turns the output into a standalone SVG — no CSS classes, no custom properties, no external references. renderToSvg takes any hand-built Unovis chart, and svgToPng rasterizes it:
import { renderToSvg } from '@unovis/ssr'
const { svg } = await renderToSvg({ width: 800, height: 400, theme: 'dark' }, ctx => {
const line = new ctx.unovis.Line({ x: d => d.x, y: d => d.y })
const container = new ctx.unovis.XYContainer(ctx.container, {
components: [line], width: ctx.width, height: ctx.height,
duration: 0, onRenderComplete: ctx.onRenderComplete,
}, data)
return { root: container.svg.node(), destroy: () => container.destroy() }
})
Charts in CI, in emails, in PDFs and on server-rendered pages all work from this one function. Along the way, a handful of fixes landed in @unovis/ts itself to make it friendlier to non-browser environments: containers fall back to their configured size when the element has no layout, getPixelsPerInch no longer recurses endlessly without layout, root index.js / maps.js entries resolve under a strict Node loader, and a malformed :not() selector in Graph was fixed. Requires Node.js 20 or newer.
🎨 Color Synchronization & Patterns
Color synchronization keeps the same category the same color across every chart, legend and tooltip on a dashboard, regardless of series order (docs):
colorFunction— a chart-wide(key) => colorfunction on XY Container, Single Container and Bullet LegendcolorKeys— a per-component array of stable keys aligned with theyaccessors; Bullet Legend items accept acolorKeyUnovisColorScale— the default D3 ordinal scale backed by the--vis-color*variables, exported so it can be re-ranged globally
const color = Scale.scaleOrdinal().range(['#4d8cfd', '#ff6b7e', '#00c19a'])
const keys = ['aws', 'azure', 'github']
<VisBulletLegend items={keys.map(name => ({ name, colorKey: name }))} colorFunction={color} />
<VisXYContainer data={data} colorFunction={color}>
<VisGroupedBar x={d => d.x} y={keys.map(k => d => d[k])} colorKeys={keys} />
</VisXYContainer>
Patterns add stripes, dots, hatches and line markers as a second visual channel on top of color — for color-blind-safe palettes, grayscale printing and dense charts (docs):
- A
patternaccessor on Area, Line, Scatter, GroupedBar, StackedBar, Timeline, Donut, NestedDonut, Sankey and ChordDiagram, withFillPatternTypeandLinePatternTypeenums for the built-in patterns - The
theme-patternsclass now runs on the same infrastructure, and its automatic palette is customizable via the--vis-pattern-fill{i},--vis-pattern-marker{i}and--vis-pattern-dasharray{i}CSS variables - Bullet Legend bullets render the matching pattern
🎯 Crosshair: XY Snapping and Horizontal Line
snapMode: CrosshairSnapMode.XYsnaps to the datum closest to the pointer in both X and Y, which makes Crosshair work great with Scatter (docs)showHorizontalLinerenders a horizontal line through the snapped point (docs)
📏 Axis: Adaptive Tick Sets
tickTextAdaptiveSetspicks the largest "nice" tick set whose labels fit and degrades to sparser sets on narrower charts, so labels never overlap while unlabeled ticks still render as tick marks (docs). Thanks to @devgru for this contribution!tickSpacingcontrols the default tick density of the X axis (docs)labelTextSeparatorconfigures where the axis label can be trimmed (docs)
📊 Bar Charts: Baseline, Waterfall Charts and Per-Bar Styles
- Stacked Bar has a new
baselineaccessor for floating bars — the primitive needed to build waterfall charts (docs, gallery example) - A new
barStyleaccessor on Stacked Bar and Grouped Bar applies custom inline styles per bar, e.g. dashed "projected value" bars (docs) - Entering bars now grow from the baseline, and interrupted exit transitions no longer leave ghost bars behind or remove live ones on rapid data updates
🧭 XY Container: Bleed Control
The new bleed config option accepts a Spacing object or a function receiving the container's components, and overrides the automatically calculated bleed. Combined with the bleed reported by onRenderComplete, it lets you synchronize the scale ranges of multiple charts placed next to each other. Read the new Bleed guide to learn more.
🍩 Donut: Minimum Segment Angle
minSegmentAngle guarantees a minimum angular sweep for every non-zero segment, so tiny values no longer disappear behind padAngle, while events, tooltips and legends keep reporting the raw values (docs).
🔗 Graph: Node Label Wrapping
Node labels and sub-labels can now wrap instead of being trimmed: set nodeLabelFitMode / nodeSubLabelFitMode to FitMode.Wrap and fine-tune with nodeLabelWidth, nodeLabelSeparator and nodeLabelForceWordBreak (plus their sub-label counterparts) (docs).
🌊 Sankey: Adaptive Node Padding
With many nodes, rigid vertical padding used to squash node heights down to a single pixel. Enable nodeAdaptivePadding to compress the padding instead, so node bodies keep their height (docs). Thanks to @devgru!
📍 Plotline & Plotband: Labels That Always Fit
- Both components now report
bleed, so the container reserves room for labels placed at the edge of the domain; when that isn't enough, the label shifts into the chart area instead of getting clipped labelTextacceptsUnovisText | UnovisText[]for styled multi-line labels, and plain strings support\nline breaks
🔒 Content Security Policy Support
Unovis now works with strict style-src policies. Set window.UNOVIS_NONCE before the library is imported and every injected <style> tag carries your nonce:
<script nonce="<SERVER_NONCE>">window.UNOVIS_NONCE = "<SERVER_NONCE>"</script>
The new CSP guide covers the recommended header and per-framework integration snippets.
⚡ Performance and Core Improvements
- Text is measured with a canvas and cached, and labels trim at precise positions instead of using an equal-width approximation;
UnovisTextbuilds SVG elements directly instead of re-parsing strings #817 config.eventscallbacks are resolved when the event fires, so handlers never run with stale state aftersetConfig#874GraphDataModelindexes nodes and links with maps instead of quadratic scans, Tooltip no longer re-queries the DOM on every event, and components get an_onDestroyteardown hook that fixes resource leaks #883- The pattern defs SVG is injected at zero size, fixing the Cumulative Layout Shift inflation on every page importing Unovis #848
🧩 Framework Updates
- Angular — as announced,
@unovis/angularnow supports Angular versions in Long-Term Support: 20 – 22 (previously 12 – 22), and requires RxJS 7.5+. The package entry paths were also fixed for the ng-packagr 20 output #704 #895 - Svelte —
@unovis/sveltemoves to Svelte 4 (^4.0.0); Svelte 3 is no longer supported. Charts now re-render when a child component's data or config changes #887 - React — component-level config changes reaching a component through context or parent state now trigger a container render #875
- Vue — the
dataprop of Crosshair, Timeline and Boxplot works again (it was silently dropped since 1.6.5) #857 - All packages are now built with Vite instead of Rollup, and the published code is no longer minified for easier debugging #869
Other Changes
Enhancements
- Component | RadialBar:
barMinAngleconfig property and0vs missing data handling #863 - Component | LeafletMap:
preserveDrawingBufferoption so the map canvas can be exported withtoDataURL()#894 - Component | Axis: Invalidate cached tick text style on
tickTextFontSizechange #870 - Core | Utils:
UnovisTextblocks acceptclassNameand per-block wrapping overrides #879 - Core | Utils: Export the
StyleDeclarationtype and theapplyInlineStyleshelper #896 - Container | XY: Warn when
updateComponentsreceives a mismatched number of configs #883 - Container: Unified tooltip hide behavior between XY and Single containers #883
- Website | Docs: New Bleed and Content Security Policy guides, and more detailed container docs covering color, tooltip, crosshair, annotations, animation and accessibility #829 #861 #893
- Website | Sidebar: Flattened navigation with all components under a single section, plus redirects for legacy URLs #892
- Dev | Gallery: Angular panel in the multi-framework playground, and framework logos in the panel headers #850 #849
- Shared | Examples: Add a
data-reactivityexample to the multi-framework gallery playground #903 - Misc: AI contributor guide (
AGENTS.md) and skills for Claude Code, Codex and Cursor; PR template; commit message validation in CI #837 - Misc: npm version and downloads badges in the README files #867
Bug Fixes
- Component | Tooltip: Fix fade-in animation and follow-cursor freeze over trigger gaps #859
- Component | Tooltip: Prevent event handling from the tooltip itself when
allowHoveris enabled #859 - Component | Donut: Fix jump before animation when switching between full and half donuts #858
- Component | Sankey: Fix label overlap caused by labels stuck in the hovered state #853
- Component | Sankey: Properly measure labels #877
- Component | Controls: Fix dark theme button border color variable reference #871
- Component | StackedBar: Don't remove re-adopted bars when an exit transition is interrupted #896
- Component | GroupedBar: Remove exiting groups from the DOM when their transition is interrupted #899
- Component | Graph: Fix missing label background when only a sub-label is used #879
- Component | All: Fix shared default config aliasing when constructed without a config #881
- Container | XY: Fix data change detection in
setData#883 - Container | XY, Single: Reconcile SVG children on
updateContainerinstead of a full teardown and re-append, so CSS transitions and per-element state survive config updates #903 - Core | Utils: Fix
isEqualfalse positive on repeated references #875 - Core | Utils: Fix
merge()class instance handling and clone-on-empty aliasing #881 - Core | Utils: Fix line spacing across differently sized text blocks #897
- Core | Utils | Color:
getColorkeeps accepting the legacy boolean fourth argument #856 - Dev | Examples: Clean up issues in the dev examples and the dev gallery #851 #852 #854
Quality of Life Improvements
- Misc | Dependency: Replace Rollup with Vite #869
- Misc | Vitest: Set up Vitest with SSR support and add it to the CI pipeline #886
- Core | Test: Introduce Playwright for browser-based E2E and visual testing in the dev app #906
- Misc | Build: Clear
vite-plugin-dtstype errors and fail the build on missing entry declarations #891 - Core | Dev: Migrate to the
@/*path alias #799 - Container: Consolidate component size, margin, and color propagation into a single helper #903
- Misc: Vulnerability fixes #864 #880
- Misc: Version script and
cmds-reportupdates #844, Percy test update #847