Added
- XY and ordinal frames take
interactiveGraphics(throughframePropson any
HOC): focusable SVG controls drawn in plot coordinates, in a labelled
role="group"layer outside the chart'srole="img"overlay, so sliders stay
exposed to assistive technology. The function form receives
{ size, margin, scales, pointerToPlot };interactiveGraphicsLabelnames the
group. Client-only. DirectManipulationMarkers(semiotic/controls): ordered draggable handles,
such as a baseline and a max, each a slider bounded by its neighbors.
Coincident handles resolve by drag direction, so neither can trap the other.
pointerToLocalPointmaps a pointer into an SVG element's coordinate space.DirectManipulationControltakespointToValue(the pointer already in the
handle's coordinate space, so(point) => scales.y.invert(point.y)replaces
hand-rolled screen-matrix math),stepOrigin, andariaRoleDescription, and
handles PageUp/PageDown.diagnoseConfig/--doctorwarn withANNOTATION_UNKNOWN_FIELDwhen an
annotation key looks like a misspelled field (fil→fill); keys naming a
data field or accessor are coordinates and are never flagged.perspectiveon every network chart (ForceDirectedGraph, SankeyDiagram,
ProcessSankey, ChordDiagram, TreeDiagram, Treemap, CirclePack, OrbitDiagram,
NetworkCustomChart, and StreamNetworkFrame) draws the finished layout in a
parallel projection:"isometric"(true 30°),"pixel"(2:1),
"dimetric","military", or"cabinet". Every piece hasthickness
(6 layout px by default;0draws flat pieces): circles and symbols become
tokens lying on the ground at their pixel size with a shaded rim; rects,
chord arcs, and circle-pack circles become slabs with side walls; Treemap and
CirclePack levels stack on their parents; edges and bands ride one thickness
above their surface and cast a soft shadow (edgeShadow).marks: "billboard"keeps upright point marks. Standing marks paint back to front.
Layout coordinates
do not change, and hover, tooltips, keyboard focus, annotations, the
accessible table, the zoom camera, component SSR, andrenderToStaticSVG
follow the projected marks.- The object form adds
elevation(a field, constant, or callback, with drop
lines and ground shadows),marks: "extrude"prisms for leaf rects,
ground.gridandground.plate,regions(labelled plates under node ids
that seat their members; plates and regions default to the piece thickness), orthogonal ground routing (edges.route),
ground-aligned labels, pictogramglyphs, and an animatedtransition.
Grid, plates, regions, routing, and extrusion load on demand in browsers;
preloadNetworkPerspectiveExtras()loads them before hydration. semiotic/network/perspective:isometricGlyphs(box, server, database,
cylinder, tile, cloud, pin), builders (isoBox,isoStack,isoCylinder,
isoTile,isoCloud,isoPin), and an accessiblePerspectiveToggle.semiotic/networkexportsresolveNetworkPerspective,
createNetworkPerspectiveFrame,projectNetworkScene, and
useNetworkPerspective()(the fitted projection for custom-layout overlays).
Custom layouts readctx.perspectiveand can setperspective: "ground"on
their result to laybackgrounds/overlayson the projected ground, or
place individual decorations withNetworkPerspectiveGround(flat geometry,
optionally at piece height) andNetworkPerspectiveBillboard(upright
content that follows its anchor, or lies flat at pixel size with
onGround). Layouts list decorations that reach past their marks in
perspectiveBoundsso the projection's fit keeps them in the plot. A
development warning fires when a projected layout leaves decorations in
plot space. All built-in network recipes place their hulls,
arrowheads, node cards, headers and badges this way;mermaidDagLayout
emits solid shaped pieces and real edges under a perspective.diagnoseConfigwarns when a ProcessSankey (a time axis) is projected and
when a network chart is given the geo-onlyprojectionprop.- Threshold (
y-threshold,x-threshold),band, andx-bandannotations
acceptlabelColorfor the label text, which otherwise uses the annotation
color. WithlabelBackground: { type: "box", fill }it draws a
light-on-dark label chip on a colored line, in the browser and in
renderChartSVG. RealtimeHistogramandTemporalHistogramacceptvalueBands
([{ upTo?, fill }], wherefillis a color orHatchFill) to split each
unstacked bar's fill at value edges, e.g. solid up to 5, hatched up to 10, a
warning hatch above. Each bar stays one mark with its bin datum, so hover,
selection,hoverHighlight, tooltips, andgetSourceRowsare unchanged.
Stacked (categoryAccessor) bins ignore it.- The tooltip wrapper (
.stream-frame-tooltip) reports its placement as
data-placement("pending", then"placed") anddata-flip-x/
data-flip-y. A CSStransitiononleft/topnow eases only same-side
moves: the first placement and each flip set an inlinetransition: none,
so the tooltip no longer glides in from the pointer or swings across it. validateProps,diagnoseConfig, andnpx semiotic-ai --doctorreport
axis config keys that no renderer reads, inframeProps.axesand the
histograms' top-levelaxes, with a "Did you mean" suggestion (for
exampletickCont→tickCount, the alias ofticks). The check stays out
of the chart bundles, like the rest of prop validation.GradientConfigacceptsextent: "domain"for area fills, which anchors
the stops to the y-domain instead of each area's own top-to-baseline span.
Changed
-
DirectManipulationControlfiresonChangeStart/onChangeEnd(and the
matching observations) for each keyboard change, not only for pointer drags,
so a consumer that commits on release also hears keyboard users. It no longer
setsaria-roledescription="visualization control"by default, so screen
readers announce the standard "slider" role. -
Importing one chart no longer ships its neighbors. Published chunks merge
many modules, and module-scope side effects (displayNameassignments, top-level
plugin/layout registration, un-annotatedforwardRef/memo/createContext/lazy
calls, a prototype mixin on the pipeline stores) forced consumer bundlers to keep
every chart that shared a chunk. Components are now pure initializers
(withDisplayName(forwardRef(...), "Name"), so DevTools names survive
minification), charts register their plugins when they render, and the build
drops esbuild's side-effect-only cross-chunk imports when their targets are
inert. Initial-load gzip for one named import in a Vite (Rolldown) app, before →
after:semiotic/aisuggestCharts227 → 40 KiB,MotifBraidChart164 → 101,
GaltonBoardChart145 → 85,SankeyDiagram159 → 125,Scatterplot150 → 123,
RealtimeLineChart130 → 114,BarChart116 → 100,ChoroplethMap112 → 103,
LineChart120 → 117. esbuild consumers see similar reductions. -
XY transition interpolation (
animate/transition) loads on demand. An
animated chart holds its first paint until the engine arrives, which is
invisible because the intro starts from a blank frame;renderChart, SSR, and
the animated GIF renderer are unchanged. -
A bare
<StreamNetworkFrame chartType="force" />(no chart component and no
registerBuiltInNetworkLayouts()call) now restores its built-in layout on the
client from a split chunk instead of rendering an empty scene. Server rendering
still requires the explicit registration and warns without it. -
The README cold-consumer table bundles each named import with code splitting
through Rolldown (Vite's bundler, resolved from thevitedevDependency) and
reports initial load and on demand gzip separately
(benchmarks/setup/cold-consumer-imports.jsonschema 2). The previous table
inlined lazily loaded code into every row.npm run sizeadds
check:treeshake-isolation, which fails when a representative named import
retains an unrelated chart, and the newsemiotic/no-module-side-effects
custom lint keeps library module scope inert
(npm run lint:custom:adopt-rulerecords a new rule's baseline cursor). -
HatchFillandcreateHatchPatterndraw the same hatch on canvas and in
SVG. Positive angles turn clockwise from horizontal on both, soangle: 45
draws\and-45draws/(SVG used to draw them mirrored), and
spacingis the perpendicular gap between lines at every angle. Canvas
hatches at ±45° used to sitspacing ÷ √2apart, so they now read about 1.4×
sparser; dividespacingby 1.41 to keep the old density. Canvas also
honorslineOpacity, bakes the tile at device resolution, and tiles
without seams at any angle. Bar-funnel dropoff hatches use one pattern on
both backends. The recipe kit's SVGhatchFillhelper keeps its own
convention (angle measured from vertical, matchinglegendSwatches).
Fixed
- Update the development dependency lockfile to patched
brace-expansion,
fast-uri, andmarkdown-itreleases, clearing the release security audit. - The "Skip to data table" link and the collapsed "View data summary" trigger
are visible while keyboard-focused (they stayed clipped to 1px, hiding the
focus indicator). - Component SSR with
animateserialized the first frame of the intro
animation: Scatterplot and BubbleChart points atr="0", zero-height
WaterfallChart and BarChart bars, zero-sweep PieChart slices, and SankeyDiagram,
Treemap, and CirclePack with no marks at all. The server render and the
hydration pass that must match it now paint the final state; client-only mounts
still animate their intro. renderToStaticSVG/renderChartpaintframeProps.background, as the live
chart does; an undefined top-levelbackgroundno longer erases it.- Static ForceDirectedGraph
colorBycolors follow category order, matching the
live chart and the static legend, instead of hashing categories into a
different palette. - Filled
curvednetwork edges honorfillOpacityin SVG output (default
0.1, as on canvas); they previously rendered opaque in SSR. - AreaChart
semanticGradientfills anchor their stops to the y-domain, the
same span the semantic line bands use. When the data maximum sat below the
domain maximum (for exampleyExtent: [0, 100]with data topping out at
60), the fill stretched its stops over the area's own extent and changed
color at different values than the line. Below the lowest stop, the fill
and the line both keep that stop's color. StreamGeoFramesizes its interaction canvas on mount, so a map that is
never hovered no longer keeps the native 300×150 canvas box. XY and geo
canvases take their CSS size from the frame at commit, so a chart mounted
pausedor in a hidden tab lays out at its real size before its first
paint.- A top legend in a compact frame keeps its capped reservation in the
browser. The frame used to raise the top margin back to the 34px
top-legend floor after the chart had capped it, collapsing the plot to 1px
whererenderChartkept it. The physics static renderer had the same
floor. renderChart("ProcessSankey")caps its legend reservation to the frame
like the browser chart. A compact frame no longer computes a negative plot
height, which drew the grid upward from the axis and a zero-thickness
ribbon. Animated GIF frames keep a plot of at least 1px.- Static SVG no longer writes CSS variables that standalone renderers can't
resolve: avar()box-backgroundfillorstrokebecomes its literal
fallback, andenclose/rect-enclosedefault to the theme's secondary
text color. In the browser, avar()box-background paint goes through
style(like the halo), with its fallback kept as the attribute. renderChartbandandx-bandannotations withoutfillorcolor
default to the theme primary for the fill and the label, like the browser
chart, instead of the annotation text color.layer: "under"bands follow
suit.- AreaChart and the chart specs no longer describe the semantic line as
ignoring stop opacity; each band takes its stop's color and opacity.