Skip to content
GitHub repository
Building UI

Charts

Anti-aliased line and area charts with monotone smoothing that never overshoots, fading gradient fills, dotted guides, bars and dots.

Updated pixi-silk 0.1.0

Source src/demos/area-chart.demo.ts
import { SilkGraphics, vertical } from 'pixi-silk';
import { defineDemo } from './runtime';

const FILL = vertical(
    [
        [0, 0x0a84ff, 0.55],
        [1, 0x0a84ff, 0],
    ],
    { easing: 'smooth' },
);

export default defineDemo({
    size: [480, 220],
    controls: {
        smooth: { type: 'toggle', label: 'monotone smoothing', value: true },
        dots: { type: 'toggle', label: 'dots', value: true },
    },
    setup({ stage, params, tick }) {
        const g = new SilkGraphics();
        const data = Array.from({ length: 13 }, (_, i) => 90 + Math.sin(i * 0.9) * 40);

        stage.addChild(g);
        tick((t) => {
            const pts = data.flatMap((v, i) => [30 + i * 35, v + Math.sin(t * 1.5 + i) * 12]);
            const smooth = params.smooth ? ('monotone' as const) : undefined;

            g.clear();
            for (const y of [40, 90, 140]) g.line(20, y, 460, y).stroke({ width: 1, color: 0x3a3a3c, dash: [2, 4] });
            g.line(20, 190, 460, 190).stroke({ width: 1, color: 0x636366 });
            // area between the series and a baseline; stroke() right after draws its top line
            g.area(pts, 190, { smooth }).fill(FILL).stroke({ width: 2.5, color: 0x0a84ff, cap: 'round' });
            if (params.dots)
                for (let i = 0; i < pts.length; i += 2)
                    g.circle(pts[i], pts[i + 1], 3.5)
                        .fill(0xffffff)
                        .stroke({ width: 2, color: 0x0a84ff });
        });
    },
});

Areas#

g.area(points, baseline, { smooth: 'monotone' });          // between a series and a y baseline
g.area(upperPoints, lowerPoints);                           // band between two series

area() fills the region under a series (or between two) with vertical columns. The columns tile exactly and share edges without seams, so translucent fills stay uniform. The top and bottom edges get analytic anti-aliasing.

Call stroke() right after to draw the series line on top:

const FILL = vertical([[0, 0x0a84ff, 0.55], [1, 0x0a84ff, 0]], { easing: 'smooth' });

g.area(points, 190, { smooth: 'monotone' }).fill(FILL).stroke({ width: 2.5, color: 0x0a84ff, cap: 'round' });

Smoothing that respects the data#

smooth: 'monotone' never overshoots. It uses Steffen’s method, like d3’s curveMonotoneX. Peaks stay on the data points, and flat runs stay flat. Use 'catmull' only for decorative curves. Silk exports the monotoneX() helper if you need the resampled points.

Guides, bars and dots#

for (const y of [40, 90, 140]) g.line(20, y, 460, y).stroke({ width: 1, color: 0x3a3a3c, dash: [2, 4] });
for (const [i, v] of values.entries()) g.roundRect(20 + i * 12, 190 - v, 8, v, 3).fill(0xffd60a);
for (const [x, y] of points) g.circle(x, y, 3.5).fill(0xffffff).stroke({ width: 2, color: 0x0a84ff });

One gradient across many shapes#

With units: 'local', a gradient uses the graphics’ coordinates instead of each shape’s box. A row of bars then samples one continuous ramp:

const HEAT = linear([[0, 0x30d158], [0.5, 0xffd60a], [1, 0xff453a]], { units: 'local', from: [0, 190], to: [0, 40] });

for (const [i, v] of values.entries()) g.roundRect(x(i), 190 - v, 8, v, 3).fill(HEAT);

The Lab has complete chart widgets built from these pieces: area charts, bar charts, sparklines, heart-rate pills, ranges and gauges.

API reference: SilkGraphics, monotoneX, vertical