Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/brave-spies-give.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'layerchart': patch
---

fix(GeoPath): Fix reactivity with `curve` when using Canvas context
5 changes: 5 additions & 0 deletions .changeset/curly-lies-write.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'layerchart': patch
---

fix(Canvas): Only apply text/font properties to canvas to improve performance
5 changes: 5 additions & 0 deletions .changeset/loud-paws-allow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'layerchart': patch
---

fix(GeoPath): Improve performance by only using custom geoCurvePath when `curve` overridden
5 changes: 5 additions & 0 deletions .changeset/tricky-nights-mix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'layerchart': patch
---

fix(Canvas): Improve performace by reducing computed style lookups and memoizing responses
5 changes: 5 additions & 0 deletions .changeset/violet-gifts-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'layerchart': patch
---

fix(Canvas): Improve performance by skipping unnecessary work when hit canvas is unneeded
1 change: 1 addition & 0 deletions packages/layerchart/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
"d3-tile": "^1.0.0",
"d3-time": "^3.1.0",
"lodash-es": "^4.17.21",
"memoize": "^10.1.0",
"runed": "^0.28.0"
},
"peerDependencies": {
Expand Down
8 changes: 7 additions & 1 deletion packages/layerchart/src/lib/components/GeoPath.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import type { TooltipContextValue } from './tooltip/TooltipContext.svelte';
import { curveLinearClosed, type CurveFactory, type CurveFactoryLineOnly } from 'd3-shape';
import {
geoPath as d3GeoPath,
geoTransform as d3geoTransform,
type GeoIdentityTransform,
type GeoPermissibleObjects,
Expand Down Expand Up @@ -105,6 +106,10 @@
const geoPath = $derived.by(() => {
geojson;
if (!projection) return;
// Only use geoCurvePath for custom curves (performance impact)
if (curve === curveLinearClosed) {
return d3GeoPath(projection);
}
return geoCurvePath(projection, curve);
});

Expand Down Expand Up @@ -166,8 +171,9 @@
touchmove: restProps.ontouchmove,
},
deps: () => [
geojson,
projection,
geojson,
curve,
fillKey.current,
strokeKey.current,
strokeWidth,
Expand Down
4 changes: 2 additions & 2 deletions packages/layerchart/src/lib/components/MonthPath.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@

<script lang="ts">
import { timeWeek, timeMonth, timeYear } from 'd3-time';
import { endOfInterval } from '$lib/utils/date.js';
import { cls } from '@layerstack/tailwind';
import { endOfInterval } from '@layerstack/utils';
import { layerClass } from '$lib/utils/attributes.js';
import Spline, { type SplinePropsWithoutHTML } from './Spline.svelte';

Expand All @@ -58,7 +58,7 @@
const startWeek = $derived(timeWeek.count(timeYear(date), date));

// end of month
const monthEnd = $derived(endOfInterval(date, timeMonth));
const monthEnd = $derived(endOfInterval('month', date));
const endDayOfWeek = $derived(monthEnd.getDay());
const endWeek = $derived(timeWeek.count(timeYear(monthEnd), monthEnd));

Expand Down
72 changes: 40 additions & 32 deletions packages/layerchart/src/lib/components/layout/Canvas.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -346,45 +346,53 @@
context.restore();
}

// sync hit canvas with main canvas
/*
* Sync hit canvas with main canvas
*/
if (hitCanvasContext) {
// scale hit canvas to match main canvas
scaleCanvas(hitCanvasContext, ctx.containerWidth, ctx.containerHeight);
hitCanvasContext.clearRect(0, 0, ctx.containerWidth, ctx.containerHeight);

// reset and sync transform to the state after retainState components
hitCanvasContext.resetTransform();
hitCanvasContext.setTransform(mainTransformAfterRetain);

// reset color generator
colorGenerator = rgbColorGenerator();

const inactiveMoving = !activeCanvas && transformCtx.moving;

// render retainState components on hit canvas (e.g., Group)
for (const c of retainStateComponents) {
const componentHasEvents = c.events && Object.values(c.events).filter((d) => d).length > 0;

if (componentHasEvents && !inactiveMoving && !transformCtx.dragging) {
// since the transform was already applied via setTransform, skip rendering
// the retainState component's transform again; proceed to its children
continue;
if (disableHitCanvas || transformCtx.dragging || inactiveMoving) {
// Skip rendering hit canvas
hitCanvasContext.clearRect(0, 0, ctx.containerWidth, ctx.containerHeight);
} else {
// scale hit canvas to match main canvas
scaleCanvas(hitCanvasContext, ctx.containerWidth, ctx.containerHeight);
hitCanvasContext.clearRect(0, 0, ctx.containerWidth, ctx.containerHeight);

// reset and sync transform to the state after retainState components
hitCanvasContext.resetTransform();
hitCanvasContext.setTransform(mainTransformAfterRetain);

// reset color generator
colorGenerator = rgbColorGenerator();

// render retainState components on hit canvas (e.g., Group)
for (const c of retainStateComponents) {
const componentHasEvents =
c.events && Object.values(c.events).filter((d) => d).length > 0;

if (componentHasEvents) {
// since the transform was already applied via setTransform, skip rendering
// the retainState component's transform again; proceed to its children
continue;
}
}
}

// render non-retainState components on hit canvas
for (const c of nonRetainStateComponents) {
const componentHasEvents = c.events && Object.values(c.events).filter((d) => d).length > 0;
// render non-retainState components on hit canvas
for (const c of nonRetainStateComponents) {
const componentHasEvents =
c.events && Object.values(c.events).filter((d) => d).length > 0;

if (componentHasEvents && !inactiveMoving && !transformCtx.dragging && !disableHitCanvas) {
const color = getColorStr(colorGenerator.next().value);
const styleOverrides = { styles: { fill: color, stroke: color, _fillOpacity: 0.1 } };
if (componentHasEvents) {
const color = getColorStr(colorGenerator.next().value);
const styleOverrides = { styles: { fill: color, stroke: color, _fillOpacity: 0.1 } };

hitCanvasContext.save();
c.render(hitCanvasContext, styleOverrides);
hitCanvasContext.restore();
hitCanvasContext.save();
c.render(hitCanvasContext, styleOverrides);
hitCanvasContext.restore();

componentByColor.set(color, c);
componentByColor.set(color, c);
}
}
}
}
Expand Down
Loading