Skip to main content

Release 1.8

Β· 14 min read
Nikita Rokotyan
Creator, Key Contributor
Qian Liu
Maintainer, Key Contributor
Surya Hanumandla
Maintainer, Key Contributor

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

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, cornerRadius and innerCornerRadius default to fractions of the chart radius, so the shape keeps its proportions at any size (docs)
  • angleRange supports 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, color and showBackground
  • icon and iconSize place an icon at the inner end of each bar β€” an icon-font glyph, a Unicode symbol or a #href to a symbol in the container's svgDefs (docs)

Check out the documentation and the new gallery example: Seven readings, seven reference values.

Gallery

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 / scaleSqrt from d3-scale directly instead of going through the Scale object, which used to pull all sixteen d3 scales, d3-time and d3-time-format into every chart #938
  • Containers use the native ResizeObserver; the @juggle/resize-observer polyfill is gone #938
  • d3-interpolate-path is replaced with a small internal path interpolator used by Line, Area and Chord Diagram #955
  • The unused d3, d3-collection, geojson and tslib dependencies 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 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:

  • icon accepts a href to an SVG symbol defined in the container's svgDefs (e.g. '#heart'), an icon-font glyph or a Unicode symbol
  • iconSize defaults 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-family and --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 mousemove instead 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.Fit with 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/svelte now supports Svelte 4 and 5 from one package (^4.0.0 || ^5.0.0), including Svelte 5 applications using runes. Child data and config updates reliably redraw the chart, nested config mutations are detected, and an Axis changing its type re-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/vue also 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 data prop contract is unified across all five frameworks #949
  • React β€” the VisTimeSeries CSS is injected only where the composite is used, which also fixes webpack builds rendering it unstyled #938
  • All wrappers β€” generated from granular @unovis/ts imports; the five component generator drivers are consolidated into one shared implementation #876 #949

Worth Knowing​

  • The @juggle/resize-observer, d3, d3-collection, geojson and tslib dependencies (and @types/d3, @types/d3-collection) were removed from @unovis/ts. Apps that import d3 or tslib without declaring them need to add them #938
  • @unovis/svelte requires Svelte 4 or 5 #957
  • Treemap labels share one text baseline now, and the default labelOffsetY went from 4 to 1 to 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 of document.body #971
  • Radial Bar's maxValue no longer falls back to 1 when it resolves to 0 #947
  • onCrosshairMove(undefined, …) is no longer called when the initial data arrives, so examples feeding it back into forceShowAt keep their initial position on load #936
  • A Crosshair that sets only its own y in a container with stacked components used to line up with the container's color keys by accident and no longer does; set colorKeys on 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.txt index 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.md in 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 curveType or 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-simplest and world-110m-alpha #958
  • Core | Utils: Fix isAClassInstance throwing on null-prototype objects, which crashed config cloning and merging #967
  • Core | Utils: Fix the d3-interpolate-path replacement handling of multi-contour paths, arc flags and empty paths #955
  • SSR | Fonts: Stage the Inter download next to the cache to avoid EXDEV errors 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 config URL parameter, playback controls and a capture mode 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 PORT so several worktrees can run the dev servers at once #944
  • Core | Build, CI: pnpm size:ts bundle 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