WakaChart

WakaChart is a chart plugin for wakaPAC. It produces display lists for rendering charts onto canvas components — charts are not drawn directly, they return a sequence of drawing instructions that wakaPAC.playMetaFile() executes during MSG_PAINT. This separates chart logic from rendering and enables hit testing, caching, and compositing.

explanation

Getting Started

Include the script after wakaPAC and register the plugin:

<script src="https://cdn.jsdelivr.net/gh/quellabs/wakapac@main/wakapac.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/quellabs/wakapac@main/plugins/wakachart.min.js"></script>

<script>
    wakaPAC.use(WakaChart); // must be called before any wakaPAC() calls
</script>

WakaChart injects a chart handle into the _chart property of every component automatically — no declaration is needed. The handle is available before init() runs:

<canvas data-pac-id="myChart" width="500" height="400"></canvas>
<div data-pac-id="hitResult"><p>{{ result }}</p></div>
const MSG_CHART_HIT = wakaPAC.MSG_USER + 0x100;

wakaPAC('myChart', {
    _lastDL: null,

    data: [
        { label: 'JavaScript', value: 38 },
        { label: 'PHP',        value: 26 },
        { label: 'Python',     value: 18 },
        { label: 'TypeScript', value: 11 },
        { label: 'Other',      value: 7  }
    ],

    msgProc(event) {
        switch (event.message) {
            case wakaPAC.MSG_PAINT: {
                const ctx = wakaPAC.getDC(this.pacId);
                const dl  = this._chart.pieChart(ctx, this.data);

                ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
                wakaPAC.playMetaFile(ctx, dl);
                wakaPAC.releaseDC(ctx);

                this._lastDL = dl;
                break;
            }

            case wakaPAC.MSG_LCLICK: {
                const pos = wakaPAC.MAKEPOINTS(event.lParam);
                const hit = wakaPAC.metaFileHitTest(this._lastDL, pos.x, pos.y);

                wakaPAC.postMessage('hitResult', MSG_CHART_HIT, 0, 0, { hit });
                break;
            }
        }
    }
});

wakaPAC('hitResult', {
    result: 'Click a slice...',

    msgProc(event) {
        switch (event.message) {
            case MSG_CHART_HIT: {
                const hit = event.detail.hit;

                this.result = hit
                    ? `${hit.label} — ${hit.value} (${hit.percent}%)`
                    : 'Click a slice...';

                break;
            }
        }
    }
});

The chart handle is injected before init() runs, so it is available immediately. Each chart function returns a display list — wakaPAC.playMetaFile() executes it during MSG_PAINT. The display list is retained between paint and click so metaFileHitTest() has something to test against.

Usage

Data Format

All chart functions accept data in either of two formats. Both can be mixed in the same array:

// Plain numbers — label defaults to empty string
const data = [38, 26, 18, 11, 7];

// Objects with label and value
const data = [
    { label: 'JavaScript', value: 38 },
    { label: 'PHP',        value: 26 },
    { label: 'Python',     value: 18 }
];

Non-finite and negative values are silently filtered out.

Hit Testing

Chart functions embed hitArea entries in the display list alongside drawing instructions. Two functions are available:

// Returns the data payload of the first hit area under the point, or null
const hit = wakaPAC.metaFileHitTest(dl, x, y, offsetX, offsetY);

// Returns true if the point falls within any hit area
const over = wakaPAC.ptInMetaFile(dl, x, y, offsetX, offsetY);

Coordinates are in container space. Pass the same offsetX/offsetY that were passed to playMetaFile — the functions subtract them internally. All chart types return hit area data with a consistent shape:

PropertyTypeDescription
indexnumberZero-based position in the data array
labelstringLabel string, or empty string if not provided
valuenumberData value
percentnumberValue as a percentage of the total (one decimal place)

Compositing Charts

Display lists can be played at an offset to composite multiple charts onto a single canvas. Each chart function works in (0, 0)-relative coordinates — positioning is the caller's responsibility:

wakaPAC.playMetaFile(ctx, dl1, 0,   0);    // top-left
wakaPAC.playMetaFile(ctx, dl2, 300, 0);    // top-right
wakaPAC.playMetaFile(ctx, dl3, 0,   200);  // bottom-left

Per-Component Options

To set per-component chart defaults or use a different property name, add a wakaChart key to the component's config. Any keys beyond property become chart option defaults, merged with global defaults and overridable per call:

wakaPAC('myChart', {
    wakaChart: {
        property:       '_chart',   // optional — defaults to '_chart'
        legendPosition: 'right',
        font:           '13px sans-serif'
    }
});

Sparklines in Foreach Lists

Sparklines are typically rendered inside foreach list items. The pattern uses a single parent component that listens for MSG_FOREACH_REBUILT and blits a sparkline onto each item's canvas element:

<div data-pac-id="productList">
    <ul data-pac-bind="foreach: products" data-pac-marker="productList">
        <li>
            {{ item.name }}
            <canvas class="spark" width="100" height="30"></canvas>
        </li>
    </ul>
</div>
wakaPAC('productList', {
    products: [
        { name: 'Widget A', sales: [12, 15, 11, 18, 22, 19, 25] },
        { name: 'Widget B', sales: [30, 28, 25, 27, 24, 22, 20] },
        { name: 'Widget C', sales: [5,  6,  8,  7,  9,  11, 10] }
    ],

    msgProc(event) {
        switch (event.message) {
            case wakaPAC.MSG_FOREACH_REBUILT: {
                if (event.detail.marker !== 'productList') {
                    break;
                }

                const arrayName = event.detail.arrayName;
                const canvases  = this.container.querySelectorAll('canvas.spark');

                for (let i = 0; i < canvases.length; i++) {
                    const offscreen = wakaPAC.createCompatibleDC(this.pacId);
                    const dl        = this._chart.sparkline(offscreen, this[arrayName][i].sales);

                    wakaPAC.playMetaFile(offscreen, dl);

                    const destCtx = wakaPAC.getDCFromElement(canvases[i]);
                    destCtx.clearRect(0, 0, canvases[i].width, canvases[i].height);
                    wakaPAC.stretchBlt(destCtx, offscreen, 0, 0, canvases[i].width, canvases[i].height);
                    wakaPAC.deleteCompatibleDC(offscreen);
                }

                break;
            }
        }
    }
});

API

wakaPAC.use(WakaChart, options?)

Registers the WakaChart plugin with the wakaPAC runtime. Must be called before any wakaPAC() calls.

ParameterTypeDescription
WakaChartobjectThe WakaChart plugin object
optionsobjectOptional global defaults applied to all chart calls. See Global Options below.
Returns void

chart.pieChart(ctx, data, opts?)

Produces a display list for a pie chart. Slices start at 12 o'clock and proceed clockwise. Visual gaps between slices are drawn as a background-colored stroke so boundaries always meet cleanly at the centre.

OptionDefaultDescription
showLabelstrueShow percentage labels inside slices
labelColor'#ffffff'Color of percentage labels inside slices
showLegendtrueShow legend
legendPosition'bottom'Legend position: 'bottom' or 'right'
gap2Gap between slices in pixels
minSliceAngle0.15Minimum slice angle in radians before percentage label is omitted
Returns arrayDrawing instructions to create the chart

chart.barChart(ctx, data, opts?)

Produces a display list for a vertical bar chart. Each bar is colored from the palette. Y-axis tick labels are measured before layout so the chart area is correctly offset. Horizontal grid lines are drawn at each tick.

OptionDefaultDescription
tickCount5Number of Y-axis ticks
barGap0.25Fraction of slot width used as gap between bars (0–1)
axisColor'#cccccc'Color of the baseline
gridColor'#eeeeee'Color of horizontal grid lines
Returns arrayDrawing instructions to create the chart

chart.lineChart(ctx, data, opts?)

Produces a display list for a line chart with dot markers at each data point. The first and last points sit exactly on the left and right edges of the chart area. Dots are drawn as a filled ring — outer color matches the line, inner fill matches background. Hit areas are twice the dot radius for easier interaction.

OptionDefaultDescription
lineColorFirst palette colorLine and dot color
lineWidth2Line stroke width in pixels
pointRadius4Dot radius in pixels
smoothfalseIf true, uses cubic bezier curves between points instead of straight lines
tickCount5Number of Y-axis ticks
axisColor'#cccccc'Color of the baseline
gridColor'#eeeeee'Color of horizontal grid lines
Returns arrayDrawing instructions to create the chart

chart.sparkline(ctx, data, opts?)

Produces a display list for a minimal inline line chart with no axes, labels, or legend. Y-axis scales from the data minimum to maximum, making variation more visible at any value range. No hit areas are emitted.

OptionDefaultDescription
lineColorFirst palette colorLine color
lineWidth2Line stroke width in pixels
smoothfalseIf true, uses cubic Bézier curves between points
showEndPointtrueShow a dot at the last data point
pointRadius3End point dot radius in pixels
Returns arrayDrawing instructions to create the chart

Global Options

These options can be set at registration time via wakaPAC.use(WakaChart, options) and apply to all chart calls. Per-component config and per-call options take precedence:

OptionDefaultDescription
colorsBuilt-in paletteArray of color strings cycled across data points
font'12px sans-serif'Font for axis labels, value labels, and legend text
padding16Inner margin from canvas edge in pixels
background'#ffffff'Background color — used for gap strokes between pie slices and dot centres on line charts
legendColor'#333333'Color for legend labels and axis text
legendFont'12px sans-serif'Font for legend labels

Notes

Chart functions receive the canvas context for text measurement and canvas dimension reading only — no drawing occurs during the function call. The context must be a valid CanvasRenderingContext2D with its canvas property set correctly.

Display lists must be retained between MSG_PAINT and MSG_LCLICK for hit testing to work. Store the last display list on this after calling playMetaFile().

Sparklines emit no hit areas. metaFileHitTest() always returns null for a sparkline display list.

The background option must match the actual canvas background color for pie slice gaps and line chart dot centres to render correctly. The default is #ffffff — update it if your canvas has a different background.

Best Practices

  • Register before creating components — call wakaPAC.use(WakaChart) before any wakaPAC() calls so the _chart handle is injected correctly during component creation.
  • Produce display lists outside MSG_PAINT when possible — build the list in init(), data callbacks, or event handlers and store it on this. The MSG_PAINT handler should only call playMetaFile() on pre-built lists.
  • Always retain the display list after paintingmetaFileHitTest() operates on the last rendered list. Assign it to this._lastDL after every playMetaFile() call.
  • Match background to canvas color — set the background option to match your canvas background so pie gaps and dot centres render correctly.
  • Use data-pac-marker with multiple foreach bindings — add data-pac-marker to each foreach element and check event.detail.marker in MSG_FOREACH_REBUILT to route to the correct drawing logic.