DEV Community

Cover image for Live chart updates without re-rendering the DOM — st-core + one CSS variable write
FSCSS for FSCSS tutorial

Posted on

Live chart updates without re-rendering the DOM — st-core + one CSS variable write

When data changes every second, the expensive path is rebuilding markup or redrawing a canvas. With st-core@v2, the chart is already pure CSS (clip-path fill/line driven by --st-p1 … --st-pN). Live updates only need JavaScript to rewrite those variables on the chart layers.

Template: templates/live-shift

JS: dashboard.js


Why this is low-overhead

Approach Cost on each tick
Rebuild table / SVG nodes DOM create/destroy, layout
Full chart library redraw Often heavy paint + JS
st-core live shift Set element.style CSS variables; browser composites the existing layers

You need real-time, low-overhead updates: for fast-changing streaming data (crypto or stock ticker), updating an st-core chart means modifying the CSS custom properties on the fill/line elements. No list re-render, no virtual DOM diff for the plot itself.


How the template is split

File Role
dashboard.fscss @arr priceData[...], @st-chart-fill / line / grid, phone shell
dashboard.css Compiled CSS
dashboard.js Random-walk series → --st-pN + labels
index.html Markup only

Initial geometry and series length are declared in FSCSS:

@arr priceData[42, 55, 48, 68, 61, 79, 72, 85]

@st-chart-fill(.chart-fill, priceData)
@st-chart-line(.chart-line, priceData)
Enter fullscreen mode Exit fullscreen mode

JS keeps the same length and only pushes new values into the same --st-p* slots.


The update path

Normalize human values (0–100 “up is higher”) into st-core’s inverted Y vars, then assign in one cssText pass:

function normalize(v) {
  return 100 - Math.max(0, Math.min(100, v)) + '%';
}

function applySeries(arr) {
  const vars = arr
    .map((v, i) => `--st-p${i + 1}: ${normalize(v)};`)
    .join(' ');
  fill.style.cssText = vars;
  line.style.cssText = vars;
}
Enter fullscreen mode Exit fullscreen mode

Each tick: shift the window, append a new sample, apply:

function shift() {
  const last = series[series.length - 1];
  const next = /* stream or random-walk */;
  series = [...series.slice(1), next];
  applySeries(series);
  // optional: text for P&L, allocation bar vars
}

setInterval(shift, 1200); // or your websocket handler
Enter fullscreen mode Exit fullscreen mode

Allocation bars in the same demo use another token:

barEl.style.setProperty('--st-cat-bar-fill-range', eq + '%');
Enter fullscreen mode Exit fullscreen mode

Still no chart DOM rebuild — width/fill is CSS.


Wiring a real stream

Replace the random walk with your feed:

socket.addEventListener('message', (ev) => {
  const price = JSON.parse(ev.data).price; // map to 0–100 or your scale
  series = [...series.slice(1), scale(price)];
  applySeries(series);
});
Enter fullscreen mode Exit fullscreen mode

Keep N fixed (or regenerate CSS when N changes). Transitions on .chart-fill / .chart-line (clip-path transition in the template) give you motion “for free.”


When to use this

  • Tickers, live dashboards, status charts where N is stable
  • You already like CSS-first charts from st-core
  • You want updates that are basically style invalidation, not tree updates

When N changes often or you need hit-testing/tooltips per point, you may still add a thin JS layer — but the series itself can stay on CSS variables.


Try it

git clone https://github.com/fscss-ttr/st-core.fscss.git
cd st-core.fscss/templates/live-shift
# open index.html after dashboard.css is built, or use FSCSS runtime on dashboard.fscss
Enter fullscreen mode Exit fullscreen mode

Source

Top comments (0)