Release 1.8
Version 1.8 of Unovis is here! It brings a new Circular Bar component, sixteen new interactive gallery examples with ready-to-copy agent prompts, noticeably smaller bundles and faster builds thanks to direct imports, automatic crosshair tooltips, icons in Donut charts, Svelte 5 support, a big batch of performance work in the core, and a long list of bug fixes.
We're excited to welcome our new first-time contributors to the Unovis community: @Eg0r0k and @smonist. Huge thanks as well to @50rayn for their continued contributions! π
Release Highlightsβ
π New Component: Circular Barβ
Circular Bar is a new Single Container component inspired by the Apple Health activity rings. Each data record becomes a petal-shaped bar growing from a central hole, drawn over a faded full-length background that shows the maximum:
innerRadius,barPadding,cornerRadiusandinnerCornerRadiusdefault to fractions of the chart radius, so the shape keeps its proportions at any size (docs)angleRangesupports partial and reversed layouts; one or two bars over the full circle fall back to regular arcs (docs)value,maxValue(an accessor, a number or a per-datum array),barMinLength,sortFunction,colorandshowBackgroundiconandiconSizeplace an icon at the inner end of each bar β an icon-font glyph, a Unicode symbol or a#hrefto a symbol in the container'ssvgDefs(docs)
Check out the documentation and the new gallery example: Seven readings, seven reference values.
πΌοΈ Gallery: Sixteen New Interactive Examples and Agent Promptsβ
The gallery grew by sixteen examples built on real datasets, each with React, Angular, Svelte, Vue, Solid and vanilla TypeScript implementations, data notes, and light and dark previews. Among them: A week on the electricity grid with a synchronized crosshair and an overview brush, Earthquakes across linked views, Where does U.S. energy go?, Inside the Unovis dependency graph, Penguin body-mass distributions, When does New York ride? and Three cities, readable without color.
Every example page now has a Copy prompt button next to Open in StackBlitz: it copies a prompt, tailored to the selected framework, that asks a coding agent to recreate the chart in your project. For agents reading the docs directly, the website now publishes an llms.txt index with fundamentals, framework guides, the MCP reference and generated API references for the core components and public types.
π¦ Smaller Bundles and Faster Buildsβ
The line chart bundle (XYContainer, Line, two Axis, Crosshair, Tooltip and BulletLegend, React, minified with esbuild) went from 68.7 KB to 58.1 KB gzipped β 15.5% smaller than 1.7.1 β without any behavior changes:
- XY components import
scaleLinear/scaleSqrtfrom d3-scale directly instead of going through theScaleobject, which used to pull all sixteen d3 scales,d3-timeandd3-time-formatinto every chart #938 - Containers use the native
ResizeObserver; the@juggle/resize-observerpolyfill is gone #938 d3-interpolate-pathis replaced with a small internal path interpolator used by Line, Area and Chord Diagram #955- The unused
d3,d3-collection,geojsonandtslibdependencies are dropped: a clean install goes from 189 to 159 packages #938
Direct imports. All framework wrappers now import granular @unovis/ts subpaths instead of the root barrel, and @unovis/svelte exposes per-component subpaths like @unovis/react, @unovis/vue and @unovis/solid already did. Importing components from their defining modules lets your bundler skip the map, graph and layout dependencies it never needed to visit β in our measurements it processes 76% fewer modules, builds 4β5Γ faster and uses 70% less peak memory #876:
import { VisXYContainer } from '@unovis/react/containers/xy-container'
import { VisLine } from '@unovis/react/components/line'
import { CurveType } from '@unovis/ts/types/curve'
Every published package ships an AGENTS.md with the supported import paths for its framework, and a new CI check fails pull requests that grow the core bundles.
π― Crosshair: Automatic Tooltipsβ
Crosshair templates used to require a separate Tooltip instance. XY Container now creates one automatically when a template is set, so the common setup gets shorter; an explicitly configured Tooltip in the same container still takes precedence (docs) #964:
<VisXYContainer data={data}>
<VisLine x={x} y={y} />
<VisCrosshair template={d => `${d.x}: ${d.y}`} />
</VisXYContainer>
Charts mixing stacked and regular series got a round of fixes too: per-circle strokeColor, strokeWidth and opacity and the container's colorKeys now follow the circle order, so each circle gets the style of its own series #946, and when circles overlap, the one of the largest segment is painted last so a thin series no longer covers a thick one #952.
π© Donut: Iconsβ
Donut can now show an icon in the middle of each segment, the same way Circular Bar does at the inner end of its bars (docs) #970:
iconaccepts a href to an SVG symbol defined in the container'ssvgDefs(e.g.'#heart'), an icon-font glyph or a Unicode symboliconSizedefaults to 50% of the arc width, capped at 15% of the radius and at 20px- Icons hide when their segment is too small to fit them, follow their segments through data and layout transitions, and don't capture pointer events, so segment tooltips keep working
- New CSS variables:
--vis-donut-icon-color,--vis-donut-icon-font-familyand--vis-donut-icon-font-weight
π§ Nested Donut: Percentage Layer Widthsβ
layerSettings.width now accepts a percentage of the donut's radius, e.g. '25%', so the hole keeps its proportions at any size (docs). When the layer widths and paddings add up to more than the available radius, the layers are scaled down proportionally instead of drawing crossed arcs, and a transparent central background element lets you handle clicks in the middle via NestedDonut.selectors.centralBackground #971.
π΅ Scatter: Dense Chartsβ
Scatter no longer creates a <text> element per point when there's no label accessor. On a chart of ~35k unlabelled points this cuts the DOM from 135,200 to 35,710 nodes and the mount time from 1,032 ms to 384 ms. For charts with more points than pixels, the new opt-in cullOverlappingPoints renders only the point that would end up on top of each pixel (docs). Thanks to @50rayn for this contribution! #932
πΊοΈ Leaflet Map: MapLibre Worker Loadingβ
Bundlers can't reliably resolve MapLibre's worker file, so Leaflet Map now starts it from a Blob URL of a prebuilt worker bundled with Unovis. It works with every bundler without extra configuration. If your Content Security Policy doesn't allow blob: in worker-src, set useMaplibreWorkerBlob: false to load the same worker from a same-origin file instead (docs) #926.
β‘ Performance and Core Improvementsβ
A broad pass over the core and the containers removed several per-render performance cliffs #949:
- Config merging no longer deep-clones the whole config on every
setConfig/updateContainerβ wrappers issue these on nearly every prop change, so animated charts paid a full deep clone per component per frame - Axis caches its auto-margin measurements and snapshots label rects once per tick collision pass, instead of building and throwing away a complete invisible axis on every render
- Graph no longer forces a layout recalculation per node, and the data models shallow-copy their inputs instead of deep-cloning every node and link
- XY Container computes scale domains once per render cycle and memoizes the expensive bleed getters of Scatter, Timeline and Sankey
- Crosshair bisects over cached sorted x values on
mousemoveinstead of sorting the dataset on every frame render()is awaitable and the render scheduling logic is shared between the containers- Single Container validates extended sizing support and falls back to
Sizing.Fitwith a warning instead of crashing - Nested Donut layer backgrounds and Radial Bar tracks tween their arcs by angle, so animated
angleRange, width and data changes no longer produce invalid paths #976
π§© Framework Updatesβ
- Svelte β
@unovis/sveltenow supports Svelte 4 and 5 from one package (^4.0.0 || ^5.0.0), including Svelte 5 applications using runes. Childdataand config updates reliably redraw the chart, nested config mutations are detected, and an Axis changing itstypere-registers correctly #957 - Vue β the wrappers notify the container explicitly instead of relying on implicit proxy tracking: child data updates now trigger a render, config updates no longer double-render, and components don't throw when used outside a container.
@unovis/vuealso marks its entry as having side effects so webpack keeps the component CSS #949 #938 - React, Vue, Solid β datasets and framework props are no longer passed into the component configs, and the
dataprop contract is unified across all five frameworks #949 - React β the
VisTimeSeriesCSS is injected only where the composite is used, which also fixes webpack builds rendering it unstyled #938 - All wrappers β generated from granular
@unovis/tsimports; the five component generator drivers are consolidated into one shared implementation #876 #949
Worth Knowingβ
- The
@juggle/resize-observer,d3,d3-collection,geojsonandtslibdependencies (and@types/d3,@types/d3-collection) were removed from@unovis/ts. Apps that importd3ortslibwithout declaring them need to add them #938 @unovis/svelterequires Svelte 4 or 5 #957- Treemap labels share one text baseline now, and the default
labelOffsetYwent from4to1to compensate. Explicit Y offsets keep their value, so custom spacing may need a ~3px adjustment #961 - Nested Donut
%layer widths are resolved against the donut's radius instead ofdocument.body#971 - Radial Bar's
maxValueno longer falls back to1when it resolves to0#947 onCrosshairMove(undefined, β¦)is no longer called when the initial data arrives, so examples feeding it back intoforceShowAtkeep their initial position on load #936- A Crosshair that sets only its own
yin a container with stacked components used to line up with the container's color keys by accident and no longer does; setcolorKeyson the Crosshair to control them #946
Other Changesβ
Enhancementsβ
- Component | GroupedBar: Bars of a removed series collapse to the baseline and fade out instead of freezing at full height #963
- Component | Plotline, Plotband: Enter with their complete geometry and fade in instead of growing from the top-left corner; clearing the value fades them out #959
- Component | NestedDonut: Show segment labels again once they fit after being hidden #971
- Website | Gallery: Composite charts first, separate Sankey and Network Graph sections, refreshed cards with larger previews, stable
?example=<slug>links and a More Examples link to the development app #962 - Website | Docs:
llms.txtindex with generated core API and public-type references for AI agents #962 - Dev | Examples: Streaming Line and Area configurations with individual and batched arrivals #955, and new examples for Crosshair, Grouped Bar, Scatter, Donut and Nested Donut
- Shared | Gallery: Strict CSP Leaflet Map example in the multi-framework playground #926
- Misc:
AGENTS.mdin every published package with the supported direct import paths #876
Bug Fixesβ
- Container | XY: Fix clipped components not rendering in Safari with zero duration #941
- Container | XY: Fix the false "Crosshair: Y accessors have not been configured" warning on load #936
- Component | XYLabels: Fix automatic text contrast when no text color is set #953
- Component | Line: Don't morph lines with gaps between paths of a different shape, which glitched when
curveTypeor the gaps changed #974 - Component | Graph: Fix icon alignment in Safari #960
- Component | Sankey: Fix extended sizing when the sizing is set after the data, and the previous layout's size leaking into the next one #969
- Component | Treemap: Fix clipped wrapped labels in WebKit and tiles with repeated names disappearing after data updates #961
- Component | NestedDonut: Fix the segment selector re-creating segments on every render #950
- Component | NestedDonut: Fix broken arcs when the layers don't fit into the available radius #971
- Component | NestedDonut | RadialBar: Fix invalid path errors while background arcs animate #976
- Component | Crosshair: Fix per-circle styles and color keys when mixing stacked and regular series #946
- Component | TopoJSONMap: Fix the Ukraine shape in
world-simplestandworld-110m-alpha#958 - Core | Utils: Fix
isAClassInstancethrowing on null-prototype objects, which crashed config cloning and merging #967 - Core | Utils: Fix the
d3-interpolate-pathreplacement handling of multi-contour paths, arc flags and empty paths #955 - SSR | Fonts: Stage the Inter download next to the cache to avoid
EXDEVerrors on GitHub runners #955 - Vue | Build: Keep the component CSS in webpack builds #938
- React | TimeSeries: Fix the missing styles in webpack builds #938
Quality of Life Improvementsβ
- Dev | Gallery: Redesigned navigation and source panels, example search, shareable component configuration via the
configURL parameter, playback controls and acapturemode for reproducible browser screenshots #956 - Dev | Gallery: Categorized search and theme switching in the multi-framework playground #962, per-framework gradient borders and hover effect #933
- Dev: FPS counter in the toolbar #928
- Misc | DevServers: Read the port from
PORTso several worktrees can run the dev servers at once #944 - Core | Build, CI:
pnpm size:tsbundle size check against a committed baseline #938 - Misc | Svelte: Compatibility tests against the packaged output with Svelte 4 and 5, run in CI #957
- Misc | AI: Visual testing and WebKit guidance, gallery metadata and preview requirements, and command reports before PR submission #956 #962
- Misc: Vulnerability fixes #940 #943 #972