Deep Chart.js integration knowledge for the Plot panel: Worker-based rendering, dataset management, downsampling strategies, scale handling, and interaction patterns.
65%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
Fix and improve this skill with Tessl
tessl review fix ./.github/skills/plot-internals/SKILL.md// Worker-side: owns the Chart.js instance
Chart.register(LineElement, PointElement, LineController, ...);
Comlink.expose({
async init(args: InitArgs): Promise<ChartRenderer> {
await fontLoaded; // Wait for fonts before first render
return new ChartRenderer(args);
}
});Chart instance (Chart.js)UpdateAction messages from main threadBounds (data range) and HoverElement[] (interaction)class TimestampDatasetsBuilderImpl {
#seriesMap = new Map<string, SeriesData>(); // path → accumulated points
// Called for each batch of messages
handleMessages(messages: MessageEvent[]): void {
// Extract numeric values using message-path logic
// Append to per-series arrays
// Enforce the 50,000-datum accumulation cap (MAX_CURRENT_DATUMS_PER_SERIES)
}
// Returns current datasets for rendering
buildActions(): UpdateAction[] {
return this.#seriesMap.values().map(seriesData => ({
datasets: [{ data: seriesData.points }],
}));
}
}There are two separate constants — don't conflate them:
| Constant | Value | File | Purpose |
|---|---|---|---|
MAX_CURRENT_DATUMS_PER_SERIES | 50_000 | panels/Plot/builders/TimestampDatasetsBuilderImpl.ts (also CustomDatasetsBuilderImpl.ts) | Accumulation cap for live/current data per series |
MAX_POINTS | 5_000 | components/TimeBasedChart/downsample.ts | Target rendered points across all series combined |
MAX_CURRENT_DATUMS_PER_SERIES, the builder culls the oldest data via
splice, dropping the overflow plus an extra MAX_CURRENT_DATUMS_PER_SERIES * 0.25 so culling
isn't triggered every single append.MAX_POINTS / numSeries — the 5,000 is shared across all signals.Downsampling (components/TimeBasedChart/downsample.ts) is a custom stateful pixel-interval
min/max bucketing, not LTTB:
MINIMUM_PIXEL_DISTANCE = 3 pixelsPOINTS_PER_INTERVAL = 4 points: first, last, min, and max
(intFirst, intLast, intMin, intMax)DownsampleState) is carried across batches via a cursor so streaming data downsamples
incrementally without reprocessing consumed points⚠️ Do not describe this as LTTB / "Largest-Triangle-Three-Buckets". It is interval min/max decimation keyed on pixel distance.
#hasRangeSource: boolean;
// true = preloaded data available (full time range in blocks)
// false = only current-frame data (live or non-preloading mode)When #hasRangeSource is true:
TimeScale from Chart.js chartjs-adapter-luxon// Main thread asks Worker for elements at mouse position
const elements = await renderer.getElementsAtPixel({ x, y });
// Returns: series index, data index, value — used for tooltip rendering// Worker must load fonts before rendering (no DOM access)
const fontLoaded = loadDefaultFont(); // Fetches font file via fetch()
// Every new ChartRenderer awaits this before creating Chart instancechart.update()lichtblick-suite/lichtblick
9589f38
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.