Skip to content

Commit 58acceb

Browse files
authored
Merge pull request #561 from techniq/canvas-perf
Canvas perf improvements
2 parents 89a8bff + 1784188 commit 58acceb

16 files changed

Lines changed: 224 additions & 149 deletions

File tree

.changeset/brave-spies-give.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'layerchart': patch
3+
---
4+
5+
fix(GeoPath): Fix reactivity with `curve` when using Canvas context

.changeset/curly-lies-write.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'layerchart': patch
3+
---
4+
5+
fix(Canvas): Only apply text/font properties to canvas to improve performance

.changeset/loud-paws-allow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'layerchart': patch
3+
---
4+
5+
fix(GeoPath): Improve performance by only using custom geoCurvePath when `curve` overridden

.changeset/tricky-nights-mix.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'layerchart': patch
3+
---
4+
5+
fix(Canvas): Improve performace by reducing computed style lookups and memoizing responses

.changeset/violet-gifts-fail.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'layerchart': patch
3+
---
4+
5+
fix(Canvas): Improve performance by skipping unnecessary work when hit canvas is unneeded

packages/layerchart/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
"d3-tile": "^1.0.0",
111111
"d3-time": "^3.1.0",
112112
"lodash-es": "^4.17.21",
113+
"memoize": "^10.1.0",
113114
"runed": "^0.28.0"
114115
},
115116
"peerDependencies": {

packages/layerchart/src/lib/components/GeoPath.svelte

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import type { TooltipContextValue } from './tooltip/TooltipContext.svelte';
66
import { curveLinearClosed, type CurveFactory, type CurveFactoryLineOnly } from 'd3-shape';
77
import {
8+
geoPath as d3GeoPath,
89
geoTransform as d3geoTransform,
910
type GeoIdentityTransform,
1011
type GeoPermissibleObjects,
@@ -105,6 +106,10 @@
105106
const geoPath = $derived.by(() => {
106107
geojson;
107108
if (!projection) return;
109+
// Only use geoCurvePath for custom curves (performance impact)
110+
if (curve === curveLinearClosed) {
111+
return d3GeoPath(projection);
112+
}
108113
return geoCurvePath(projection, curve);
109114
});
110115
@@ -166,8 +171,9 @@
166171
touchmove: restProps.ontouchmove,
167172
},
168173
deps: () => [
169-
geojson,
170174
projection,
175+
geojson,
176+
curve,
171177
fillKey.current,
172178
strokeKey.current,
173179
strokeWidth,

packages/layerchart/src/lib/components/MonthPath.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131

3232
<script lang="ts">
3333
import { timeWeek, timeMonth, timeYear } from 'd3-time';
34-
import { endOfInterval } from '$lib/utils/date.js';
3534
import { cls } from '@layerstack/tailwind';
35+
import { endOfInterval } from '@layerstack/utils';
3636
import { layerClass } from '$lib/utils/attributes.js';
3737
import Spline, { type SplinePropsWithoutHTML } from './Spline.svelte';
3838
@@ -58,7 +58,7 @@
5858
const startWeek = $derived(timeWeek.count(timeYear(date), date));
5959
6060
// end of month
61-
const monthEnd = $derived(endOfInterval(date, timeMonth));
61+
const monthEnd = $derived(endOfInterval('month', date));
6262
const endDayOfWeek = $derived(monthEnd.getDay());
6363
const endWeek = $derived(timeWeek.count(timeYear(monthEnd), monthEnd));
6464

packages/layerchart/src/lib/components/layout/Canvas.svelte

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -346,45 +346,53 @@
346346
context.restore();
347347
}
348348
349-
// sync hit canvas with main canvas
349+
/*
350+
* Sync hit canvas with main canvas
351+
*/
350352
if (hitCanvasContext) {
351-
// scale hit canvas to match main canvas
352-
scaleCanvas(hitCanvasContext, ctx.containerWidth, ctx.containerHeight);
353-
hitCanvasContext.clearRect(0, 0, ctx.containerWidth, ctx.containerHeight);
354-
355-
// reset and sync transform to the state after retainState components
356-
hitCanvasContext.resetTransform();
357-
hitCanvasContext.setTransform(mainTransformAfterRetain);
358-
359-
// reset color generator
360-
colorGenerator = rgbColorGenerator();
361-
362353
const inactiveMoving = !activeCanvas && transformCtx.moving;
363-
364-
// render retainState components on hit canvas (e.g., Group)
365-
for (const c of retainStateComponents) {
366-
const componentHasEvents = c.events && Object.values(c.events).filter((d) => d).length > 0;
367-
368-
if (componentHasEvents && !inactiveMoving && !transformCtx.dragging) {
369-
// since the transform was already applied via setTransform, skip rendering
370-
// the retainState component's transform again; proceed to its children
371-
continue;
354+
if (disableHitCanvas || transformCtx.dragging || inactiveMoving) {
355+
// Skip rendering hit canvas
356+
hitCanvasContext.clearRect(0, 0, ctx.containerWidth, ctx.containerHeight);
357+
} else {
358+
// scale hit canvas to match main canvas
359+
scaleCanvas(hitCanvasContext, ctx.containerWidth, ctx.containerHeight);
360+
hitCanvasContext.clearRect(0, 0, ctx.containerWidth, ctx.containerHeight);
361+
362+
// reset and sync transform to the state after retainState components
363+
hitCanvasContext.resetTransform();
364+
hitCanvasContext.setTransform(mainTransformAfterRetain);
365+
366+
// reset color generator
367+
colorGenerator = rgbColorGenerator();
368+
369+
// render retainState components on hit canvas (e.g., Group)
370+
for (const c of retainStateComponents) {
371+
const componentHasEvents =
372+
c.events && Object.values(c.events).filter((d) => d).length > 0;
373+
374+
if (componentHasEvents) {
375+
// since the transform was already applied via setTransform, skip rendering
376+
// the retainState component's transform again; proceed to its children
377+
continue;
378+
}
372379
}
373-
}
374380
375-
// render non-retainState components on hit canvas
376-
for (const c of nonRetainStateComponents) {
377-
const componentHasEvents = c.events && Object.values(c.events).filter((d) => d).length > 0;
381+
// render non-retainState components on hit canvas
382+
for (const c of nonRetainStateComponents) {
383+
const componentHasEvents =
384+
c.events && Object.values(c.events).filter((d) => d).length > 0;
378385
379-
if (componentHasEvents && !inactiveMoving && !transformCtx.dragging && !disableHitCanvas) {
380-
const color = getColorStr(colorGenerator.next().value);
381-
const styleOverrides = { styles: { fill: color, stroke: color, _fillOpacity: 0.1 } };
386+
if (componentHasEvents) {
387+
const color = getColorStr(colorGenerator.next().value);
388+
const styleOverrides = { styles: { fill: color, stroke: color, _fillOpacity: 0.1 } };
382389
383-
hitCanvasContext.save();
384-
c.render(hitCanvasContext, styleOverrides);
385-
hitCanvasContext.restore();
390+
hitCanvasContext.save();
391+
c.render(hitCanvasContext, styleOverrides);
392+
hitCanvasContext.restore();
386393
387-
componentByColor.set(color, c);
394+
componentByColor.set(color, c);
395+
}
388396
}
389397
}
390398
}

0 commit comments

Comments
 (0)