billboard.js 4.1.0: Live resizing, configurable subchart, React subpath & CSP-safe worker
DEV Community

billboard.js 4.1.0: Live resizing, configurable subchart, React subpath & CSP-safe worker

Glad to announce billboard.js v4.1.0! 🎉 This minor release is about giving back control over things that used to be fixed: how the chart behaves while its container is being dragged, what the subchart overview renders, and where the Web Worker source comes from. The React component also moved into the main package as a subpath export. For the detailed release info, please check out the release note: 📌 What's new? 1. Live resizing (resize.live) Until now resize.timer delayed the redraw, so while a container was being dragged the chart kept its previous pixel size and only snapped once the drag stopped. The new resize.live makes the chart follow the container in between. bb.generate({ data: { columns: [["data1", 30, 200, 100, 400]], type: "line" }, resize: { // follow the container size while resizing live: true } }); Dragging the container with resize.live: true . The counter below the chart shows the redraw count and the last redraw time. How the size is followed is measured, not configured. Two strategies are used: - Redraw: while a resize redraw fits in one animation frame (16ms), the chart is redrawn every frame, so every intermediate size is an exact rendering. - Stretch: once a redraw misses that budget, the rendering is stretched to the new size for the rest of the resize and redrawn when it settles. For SVG the viewBox is set to the drawn size and the width/height attributes to the new one; for canvas only the element's CSS box changes, with the backing store left as is. Within one resize the strategy only moves from redraw to stretch, never back, so frames can't alternate between an exact and a stretched rendering. The measured time outlives the resize, so a chart already known to be expensive stretches from the first frame of the next one. Resize redraws skip the shape data join and the axis tick measurement, so they cost far less than an initial render. A 5x1,000 line chart redraws in about 6ms (SVG) and 3ms (canvas); a 10x10,000 one in about 78ms and 53ms. Ordinary charts therefore redraw every frame, and only large ones fall back to stretching. Two things to keep in mind: - It applies only when resize.auto istrue or"parent" . - The budget is measured per chart, and charts don't know about each other. Eight charts of 2,000 points each spend about 75ms per frame together while each one measures about 9ms and keeps redrawing. Turn it on for the charts the user actually watches while resizing, not for every chart on a dense page. 2. Configurable subchart rendering The subchart used to be a small copy of the main chart. Now the overview can be rendered as its own chart, with its own type, its own axes, and without brush interaction. Before: the overview repeated the main chart's line type. bb.generate({ data: { columns: [ ["data1", 30, 200, 100, 400, 150, 250], ["data2", 130, 100, 140, 200, 150, 50] ], type: "line" }, subchart: { show: true, // render the overview with a different chart type than the main chart type: "bar", // or override the type per data series types: { data2: "area" }, brush: { // render the subchart as a static overview, without zoom interaction enabled: false }, grid: { // hide the x focus grid line in the subchart // focus: false, focus: { // draw one continuous focus line across main chart and subchart // NOTE: works only when 'brush.enabled=false' continuous: true } }, axis: { x: { tick: { format: x => ${x}, text: { show: false } } }, y: { show: true, tick: { count: 3 } } } } }); - Demo: https://naver.github.io/billboard.js/demo/#SubChart.SubchartType https://naver.github.io/billboard.js/demo/#SubChart.SubchartTypes https://naver.github.io/billboard.js/demo/#SubChart.SubchartAxis https://naver.github.io/billboard.js/demo/#SubChart.ContinuousFocusGrid https://naver.github.io/billboard.js/demo/#SubChart.CandlestickSubchartBar After: a line main chart with subchart.type: "bar" ,subchart.types.data2: "area" , subchart y axis ticks and a focus line continuous across both charts. The subchart axes accept the same tick options as the main axes: count , values , culling , outer , format and text.show , for x, y and y2. Candlestick values are projected with stock chart semantics, so a candlestick main chart can show a bar, line or area overview below it. SVG and canvas render the subchart axis, grid and interaction the same way. 3. Grid line class selectors on canvas Canvas mode has no SVG DOM, so grid.x.lines[].class and grid.y.lines[].class had nothing to style. canvas.theme.selectors now maps those class names to canvas draw styles. bb.generate({ render: { mode: "canvas" }, grid: { y: { lines: [ { value: 200, text: "Label 200", class: "threshold-good" }, { value: 350, text: "Label 350", class: "threshold-bad" } ] } }, canvas: { theme: { selectors: { ".bb-grid line": { stroke: "#ddd", "stroke-width": 1 }, ".threshold-good line": { stroke: "green" }, ".threshold-bad line": { stroke: "red", "stroke-dasharray": "2 2" } } } } }); Both the line and its label can be styled (. line and . text ), and the axis-specific forms .bb-xgrid-line. and .bb-ygrid-line. are supported too. They match optional grid lines only, not the tick grid lines generated by grid.x.show or grid.y.show . Direct canvas.theme keys still win over anything mapped from a selector. The full selector table is in CANVAS_THEME_SELECTORS.md. 4. React component as a package subpath The React component is no longer a separate @billboard.js/react package. It ships from the billboard.js/react subpath, and the billboard namespace is passed in through the bb prop, so importing it never pulls the root bundle into a non-React bundle. import bb, {line} from "billboard.js"; import BillboardJS from "billboard.js/react"; ; Without a bundler, load dist/billboard.react.js . It is a UMD build exposing the BillboardReact global, which holds the component as both .Chart and .default . Note that this path reads the React global, so it needs a UMD build of React: React 18 and below ship one, React 19 does not. 5. Worker source you can serve yourself (boost.workerUrl) boost.useWorker used to build its worker by stringifying a function, which breaks whenever a host toolchain rewrites function bodies. Coverage instrumentation, for one, made it fail with cov_xxx is not defined . The worker is now pre-bundled as a separate entry and work is addressed by op name, so no application function is ever stringified. On top of that, boost.workerUrl points at a static script instead of an inline Blob worker, for environments whose CSP disallows blob: : bb.generate({ boost: { // 'workerUrl' only selects where the worker source comes from. // It does not turn offloading on by itself. useWorker: true, workerUrl: "/billboard.worker.js" }, data: { columns: [["data1", 30, 200, 100]] } }); The script is shipped as dist/billboard.worker.js (~1.5KB). With a bundler, make sure it is emitted as a real asset: inlining turns it into a data: URI, which a strict CSP blocks exactly like blob: . Copying it into the static/public directory works everywhere. boost.useWorker also gains "auto" , which offloads only past ~5,000 cells, since smaller payloads lose more to structured cloning than they gain: boost: { useWorker: "auto" } Any failure (load error, unknown op, timeout, result mismatch) falls back to the main thread, as before. 6. TextOverlap plugin without d3-delaunay The TextOverlap plugin computed label positions from a Delaunay triangulation, which was its only third-party dependency and had to be loaded through a dynamic import() . It now clips bounded Voronoi cells by half-planes instead, so the dependency and the two lazy chunks it emitted are gone, and label positioning is synchronous again. Other improvements - The ESM build moved from Rollup to Rolldown. - Repeated data aggregation and legend updates during redraw were reduced. - chart.select() now honorsdata.selection.multiple: false andpoint.focus.only . - bar.radius no longer produces invalid SVG arc flags onload() . - The TableView plugin validates its constructor options and warns on invalid input. - Empty object detection is correct for objects with a polluted prototype. 👋 Going forward Most of this release came from issues reported on GitHub, and the subchart and resize work in particular follows what people said they were trying to build. Keep the feedback coming on GitHub and community (medium & dev.to) channels! Happy charting! Top comments (0)

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.