Support multiple layers per <Canvas> - #424
Draft
mhkeller wants to merge 4 commits into
Draft
Conversation
The <canvas> element now covers the whole chart container and the layout scales, clears and translates before every repaint. Components hand getCanvasContext().draw() a function instead of managing the canvas themselves, so several can share one <Canvas>, repaint together and draw into the margins like Svg and Html children can. - getCanvasContext() with draw(fn), redraw() and a read-only ctx - overflow prop on <Canvas>, matching the other layouts - paint pass in helpers/paintLayers.js with unit tests - main's idempotent scaleCanvas and its test brought forward - canvas docs components, guide and changelog rewritten for the pattern - MapLayered draws two components on one canvas - e2e: full-bleed geometry, translate vs the Svg layer, layer order
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Layer exceptions can leak canvas save-stack state, and direct buffer resizes bypass the new warning.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Makes <Canvas> own rendering and support multiple ordered drawing components.
Changes:
- Adds
getCanvasContext(), coordinated repainting, clipping, and pointer coordinates. - Migrates canvas components and expands the layered-map example.
- Adds unit, layout, and end-to-end coverage plus documentation.
File summaries
| File | Description |
|---|---|
src/lib/layouts/Canvas.svelte |
Manages canvas layers and repainting. |
src/lib/helpers/paintLayers.js |
Paints ordered, isolated layers. |
src/lib/lib/scaleCanvas.js |
Makes scaling idempotent. |
src/lib/context.js |
Adds canvas and pointer APIs. |
src/lib/LayerCake.svelte |
Implements pointer coordinates. |
src/lib/index.js |
Exports canvas APIs and types. |
src/lib/layouts/Webgl.svelte |
Corrects padded sizing. |
src/_components/Scatter.canvas.svelte |
Migrates scatter drawing. |
src/_components/Map.canvas.svelte |
Migrates map drawing. |
src/_components/MapPoints.canvas.svelte |
Migrates point drawing. |
src/routes/_examples/MapLayered.svelte |
Demonstrates shared canvas layers. |
src/routes/_examples_ssr/MapLayered.svelte |
Updates the SSR example. |
test/lib/scaleCanvas.test.js |
Tests scaling behavior. |
test/lib/paintLayers.test.js |
Tests paint ordering and clipping. |
test/e2e/canvas-layout.spec.js |
Verifies browser layout and pixels. |
test/e2e/copy-clipboard.spec.js |
Serializes clipboard tests. |
src/content/guide/99-helper-functions.md |
Updates scaling guidance. |
src/content/guide/06-layout-component-props.md |
Documents canvas overflow. |
src/content/guide/05-layout-components.md |
Documents the drawing API. |
src/content/guide/04-computed-context-values.md |
Documents k.pointer. |
src/content/guide/03-layercake-props.md |
Lists canvas types. |
src/content/examples/MapLayered.md |
Describes layered canvas usage. |
CHANGELOG.md |
Records API and migration changes. |
Review details
- Files reviewed: 23/27 changed files
- Comments generated: 3
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // A layer gets destroyed when its component is destroyed. Svelte only | ||
| // lets us hook into a component's destruction while it is setting up. | ||
| // So in order to ride that lifecycle, we have to require canvas.draw() | ||
| // to be called from inside an effect. |
| // stay blank. | ||
| console.error('[LayerCake] A canvas draw function threw an error:', err); | ||
| } finally { | ||
| ctx.restore(); |
Comment on lines
+115
to
+131
| const sizeNow = canvas.style.width + ' ' + canvas.style.height; | ||
| if (sizeWeSet && sizeNow !== sizeWeSet && !warnedAboutResize) { | ||
| warnedAboutResize = true; | ||
| console.warn( | ||
| '[LayerCake] Something resized this <Canvas> between repaints, most likely a component calling scaleCanvas() itself. <Canvas> sizes and clears the canvas for you: draw through getCanvasContext().draw() and remove your scaleCanvas and clearRect calls. https://layercake.graphics/guide#canvas' | ||
| ); | ||
| } | ||
|
|
||
| paintLayers(context, layers, { | ||
| containerWidth: k.containerWidth, | ||
| containerHeight: k.containerHeight, | ||
| width: k.width, | ||
| height: k.height, | ||
| padding: k.padding, | ||
| overflow | ||
| }); | ||
| sizeWeSet = canvas.style.width + ' ' + canvas.style.height; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
<Canvas>now owns its canvas. The element covers the whole chart container and the layout scales, clears and moves the origin before every repaint. Much inspired by @techniq's work in LayerChart and this ancient conversation. Components stop managing the canvas and hand over a draw function instead:Whatever the function reads (props,
$state,k.*) triggers a repaint. Several components can share one<Canvas>and paint in order, so the old one-<Canvas>-per-component workaround (#50) is gone. Since the origin moves by the padding, canvas drawings can reach into the margins the way Svg and Html children always could.API
getCanvasContext()returns a typed context:draw(fn),redraw()for things Svelte can't track and a read-onlyctxk.pointer(event)gives chart-area[x, y]on any layer. Layers cover different boxes now, sooffsetXshifts meaning depending on where you listen.k.pointerdoesn'toverflowon<Canvas>, the same prop as the other layouts."hidden"clips at the chart areadraw()must be called during component setup and throws a clear error anywhere elsescaleCanvascall, usually)Also in here
scaleCanvasand its test, brought forward so the merge is cleanhelpers/paintLayers.jswith unit tests for order, clipping and error isolationWorth knowing
{#if}goes to the top of the stack. Anorderoption can come later but could be clunky.translatein GL) and stays on the roadmap