From 852081deb2fe6d788668776f7b976457c6c18931 Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Fri, 23 May 2025 14:14:09 -0400 Subject: [PATCH 1/7] fix(Treemap): Fix reactivity of props (tile, padding, etc) and remove `selected` prop --- .changeset/cruel-cameras-begin.md | 5 ++++ .changeset/modern-nails-kiss.md | 5 ++++ .../src/lib/components/Treemap.svelte | 28 +++++++++---------- .../routes/docs/examples/Treemap/+page.svelte | 13 +++------ 4 files changed, 28 insertions(+), 23 deletions(-) create mode 100644 .changeset/cruel-cameras-begin.md create mode 100644 .changeset/modern-nails-kiss.md diff --git a/.changeset/cruel-cameras-begin.md b/.changeset/cruel-cameras-begin.md new file mode 100644 index 000000000..5b14ebd06 --- /dev/null +++ b/.changeset/cruel-cameras-begin.md @@ -0,0 +1,5 @@ +--- +'layerchart': patch +--- + +breaking(Treemap): Remove `selected` prop diff --git a/.changeset/modern-nails-kiss.md b/.changeset/modern-nails-kiss.md new file mode 100644 index 000000000..e218d5071 --- /dev/null +++ b/.changeset/modern-nails-kiss.md @@ -0,0 +1,5 @@ +--- +'layerchart': patch +--- + +fix(Treemap): Fix reactivity of props (tile, padding, etc) diff --git a/packages/layerchart/src/lib/components/Treemap.svelte b/packages/layerchart/src/lib/components/Treemap.svelte index ee5d7e841..923ac16be 100644 --- a/packages/layerchart/src/lib/components/Treemap.svelte +++ b/packages/layerchart/src/lib/components/Treemap.svelte @@ -59,13 +59,6 @@ */ paddingRight?: number; - /** - * The selected node. - * - * @default null - */ - selected?: HierarchyRectangularNode | null; - hierarchy?: HierarchyNode; children?: Snippet<[{ nodes: HierarchyRectangularNode[] }]>; @@ -121,7 +114,7 @@ : tile ); - const treemap = $derived.by(() => { + const treemapData = $derived.by(() => { const _treemap = d3treemap() .size([ctx.width, ctx.height]) .tile(aspectTile(tileFunc, ctx.width, ctx.height)); @@ -152,14 +145,21 @@ if (paddingRight) { _treemap.paddingRight(paddingRight); } - return _treemap; - }); - const treemapData = $derived(hierarchy ? treemap(hierarchy) : null); + if (hierarchy) { + const h = hierarchy.copy(); + const treemapData = _treemap(h); + return { + links: treemapData.links(), + nodes: treemapData.descendants(), + }; + } - $effect.pre(() => { - selected = treemapData; + return { + links: [], + nodes: [], + }; }); -{@render children?.({ nodes: treemapData ? treemapData.descendants() : [] })} +{@render children?.({ nodes: treemapData.nodes })} diff --git a/packages/layerchart/src/routes/docs/examples/Treemap/+page.svelte b/packages/layerchart/src/routes/docs/examples/Treemap/+page.svelte index 27872946b..0b62b21b6 100644 --- a/packages/layerchart/src/routes/docs/examples/Treemap/+page.svelte +++ b/packages/layerchart/src/routes/docs/examples/Treemap/+page.svelte @@ -83,8 +83,8 @@ let tile: ComponentProps['tile'] = $state('squarify'); let colorBy = $state('children'); - let selectedNested: HierarchyRectangularNode | null = $state(null); - let selectedZoomable: HierarchyRectangularNode | null = $state(null); + let selectedNested: HierarchyNode = $state(complexDataHierarchy.copy()); + let selectedZoomable: HierarchyNode = $state(complexDataHierarchy.copy()); let paddingOuter = $state(4); let paddingInner = $state(4); let paddingTop = $state(20); @@ -180,9 +180,8 @@ {#snippet children({ xScale, yScale })} {#snippet children({ xScale, yScale })} - + {#snippet children({ nodes })} {#each nodes as node} Date: Wed, 4 Jun 2025 11:39:43 -0400 Subject: [PATCH 2/7] fix(Treemap): Add `maintainAspectRatio` (default: false) to opt into tiling function adjustment (primarily for zoom) --- .changeset/early-peaches-accept.md | 5 + .../src/lib/components/Treemap.svelte | 10 +- .../docs/components/Treemap/+page.svelte | 153 +++++++++++++++++- .../routes/docs/components/Treemap/+page.ts | 50 ++++++ .../routes/docs/examples/Treemap/+page.svelte | 47 +++++- 5 files changed, 259 insertions(+), 6 deletions(-) create mode 100644 .changeset/early-peaches-accept.md diff --git a/.changeset/early-peaches-accept.md b/.changeset/early-peaches-accept.md new file mode 100644 index 000000000..08fd61a41 --- /dev/null +++ b/.changeset/early-peaches-accept.md @@ -0,0 +1,5 @@ +--- +'layerchart': patch +--- + +fix(Treemap): Add `maintainAspectRatio` (default: false) to opt into tiling function adjustment (primarily for zoom) diff --git a/packages/layerchart/src/lib/components/Treemap.svelte b/packages/layerchart/src/lib/components/Treemap.svelte index 923ac16be..b6e381324 100644 --- a/packages/layerchart/src/lib/components/Treemap.svelte +++ b/packages/layerchart/src/lib/components/Treemap.svelte @@ -59,6 +59,13 @@ */ paddingRight?: number; + /** + * Modify tiling function for approapriate aspect ratio when treemap is zoomed in + * + * @default false + */ + maintainAspectRatio?: boolean; + hierarchy?: HierarchyNode; children?: Snippet<[{ nodes: HierarchyRectangularNode[] }]>; @@ -92,6 +99,7 @@ paddingBottom = 0, paddingLeft, paddingRight, + maintainAspectRatio = false, selected = $bindable(null), children, }: TreemapProps = $props(); @@ -117,7 +125,7 @@ const treemapData = $derived.by(() => { const _treemap = d3treemap() .size([ctx.width, ctx.height]) - .tile(aspectTile(tileFunc, ctx.width, ctx.height)); + .tile(maintainAspectRatio ? aspectTile(tileFunc, ctx.width, ctx.height) : tileFunc); if (padding) { _treemap.padding(padding); diff --git a/packages/layerchart/src/routes/docs/components/Treemap/+page.svelte b/packages/layerchart/src/routes/docs/components/Treemap/+page.svelte index 3abe40881..48c806201 100644 --- a/packages/layerchart/src/routes/docs/components/Treemap/+page.svelte +++ b/packages/layerchart/src/routes/docs/components/Treemap/+page.svelte @@ -29,8 +29,13 @@ .sum((d) => d.value) .sort(sortFunc('value', 'desc')); + const rootSimple = hierarchy(data.population) + .sum((d) => d.value) + .sort(sortFunc('value', 'desc')); + let tile: ComponentProps['tile'] = $state('squarify'); let colorBy = $state('children'); + let maintainAspectRatio = $state(false); let paddingOuter = $state(4); let paddingInner = $state(4); @@ -64,10 +69,143 @@

Example

+

Simple

+ +
+
+ + + Squarify + Resquarify + Binary + Slice + Dice + Slice / Dice + + + + + No + Yes + + + + + Children + Depth + Parent + + +
+
+ + +
+
+ + + + +
+
+ + +
+ + {#snippet children({ context })} + + + {#snippet children({ nodes })} + {#each nodes as node} + context.tooltip.show(e, node)} + onpointerleave={context.tooltip.hide} + > + {@const nodeWidth = node.x1 - node.x0} + {@const nodeHeight = node.y1 - node.y0} + {@const nodeColor = getNodeColor(node, colorBy)} + + + + {node.data.name} + {#if node.children} + + {format(node.value ?? 0, 'integer')} + + {/if} + + + {#if !node.children} + + {/if} + + + {/each} + {/snippet} + + + + + {#snippet children({ data })} + {data.data.name} + + + + {/snippet} + + {/snippet} + +
+
+

Complex

-
+
Squarify @@ -78,6 +216,18 @@ Slice / Dice + + + No + Yes + + Children @@ -112,6 +262,7 @@ {paddingBottom} {paddingLeft} {paddingRight} + {maintainAspectRatio} > {#snippet children({ nodes })} {#each nodes as node} diff --git a/packages/layerchart/src/routes/docs/components/Treemap/+page.ts b/packages/layerchart/src/routes/docs/components/Treemap/+page.ts index d14b909a4..f35b4bea4 100644 --- a/packages/layerchart/src/routes/docs/components/Treemap/+page.ts +++ b/packages/layerchart/src/routes/docs/components/Treemap/+page.ts @@ -5,6 +5,56 @@ import pageSource from './+page.svelte?raw'; export async function load() { return { flare: await fetch('/data/examples/hierarchy/flare.json').then((r) => r.json()), + population: { + name: 'World', + children: [ + { + name: 'Europe', + children: [ + { name: 'Western Europe', value: 200 }, // ~200M based on UN data + { name: 'Southern Europe', value: 151 }, // ~151M based on UN data + { name: 'Eastern Europe', value: 284 }, // ~284M based on UN data + { name: 'Northern Europe', value: 109 }, // ~109M based on UN data + ], + }, + { + name: 'Asia', + children: [ + { name: 'East Asia', value: 1652 }, // 1,652M based on UN data + { name: 'South Asia', value: 2085 }, // 2,085M based on UN data + { name: 'Southeast Asia', value: 700 }, // 700M based on UN data + { name: 'Western Asia', value: 314 }, // 314M based on UN data + { name: 'Central Asia', value: 84 }, // 84M based on UN data + ], + }, + { + name: 'North America', + children: [ + { name: 'Northern America', value: 388 }, // ~388M based on UN data + { name: 'Central America', value: 184 }, // ~184M (estimated from total minus Northern America) + ], + }, + { + name: 'South America', + children: [{ name: 'South America', value: 434 }], // ~434M based on UN data + }, + { + name: 'Africa', + children: [ + { name: 'Western Africa', value: 467 }, // 467M based on UN data + { name: 'Southern Africa', value: 74 }, // 74M based on UN data + { name: 'Northern Africa', value: 276 }, // 276M based on UN data + { name: 'Eastern Africa', value: 513 }, // 513M based on UN data + { name: 'Middle Africa', value: 220 }, // 220M based on UN data + ], + }, + { + name: 'Oceania', + children: [{ name: 'Oceania', value: 47 }], // 47M based on UN data + }, + ], + }, + meta: { api, source, diff --git a/packages/layerchart/src/routes/docs/examples/Treemap/+page.svelte b/packages/layerchart/src/routes/docs/examples/Treemap/+page.svelte index 0b62b21b6..01f8ca79e 100644 --- a/packages/layerchart/src/routes/docs/examples/Treemap/+page.svelte +++ b/packages/layerchart/src/routes/docs/examples/Treemap/+page.svelte @@ -81,6 +81,7 @@ }); let tile: ComponentProps['tile'] = $state('squarify'); + let maintainAspectRatio = $state(false); let colorBy = $state('children'); let selectedNested: HierarchyNode = $state(complexDataHierarchy.copy()); @@ -123,7 +124,7 @@

Zoomable

-
+
Squarify @@ -134,6 +135,18 @@ Slice / Dice + + + No + Yes + + Children @@ -182,6 +195,7 @@ Grouped and Filterable
-
+
Squarify @@ -281,6 +295,18 @@ Slice / Dice + + + No + Yes + + Children @@ -329,6 +355,7 @@ Zoomable
-
+
Squarify @@ -422,6 +449,18 @@ Slice / Dice + + + No + Yes + + Children @@ -456,7 +495,7 @@ > {#snippet children({ xScale, yScale })} - + {#snippet children({ nodes })} {#each nodes as node} Date: Wed, 4 Jun 2025 11:40:44 -0400 Subject: [PATCH 3/7] Fix headings --- .../src/routes/docs/components/Treemap/+page.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/layerchart/src/routes/docs/components/Treemap/+page.svelte b/packages/layerchart/src/routes/docs/components/Treemap/+page.svelte index 48c806201..0a22007bc 100644 --- a/packages/layerchart/src/routes/docs/components/Treemap/+page.svelte +++ b/packages/layerchart/src/routes/docs/components/Treemap/+page.svelte @@ -69,7 +69,7 @@

Example

-

Simple

+

Simple

@@ -202,7 +202,7 @@
-

Complex

+

Complex

From d247362f280c5b9966d4acb23b8253938c9a8adf Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Wed, 4 Jun 2025 11:53:07 -0400 Subject: [PATCH 4/7] Fix CI --- packages/layerchart/src/lib/components/Treemap.svelte | 1 - .../layerchart/src/routes/docs/examples/Treemap/+page.svelte | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/layerchart/src/lib/components/Treemap.svelte b/packages/layerchart/src/lib/components/Treemap.svelte index b6e381324..474b3bb86 100644 --- a/packages/layerchart/src/lib/components/Treemap.svelte +++ b/packages/layerchart/src/lib/components/Treemap.svelte @@ -100,7 +100,6 @@ paddingLeft, paddingRight, maintainAspectRatio = false, - selected = $bindable(null), children, }: TreemapProps = $props(); diff --git a/packages/layerchart/src/routes/docs/examples/Treemap/+page.svelte b/packages/layerchart/src/routes/docs/examples/Treemap/+page.svelte index 01f8ca79e..fa67dc491 100644 --- a/packages/layerchart/src/routes/docs/examples/Treemap/+page.svelte +++ b/packages/layerchart/src/routes/docs/examples/Treemap/+page.svelte @@ -32,6 +32,7 @@ Text, Tooltip, Treemap, + asAny, findAncestor, } from 'layerchart'; import { isNodeVisible } from '$lib/utils/treemap.js'; @@ -187,7 +188,7 @@ {#snippet children({ context })} {#snippet children({ xScale, yScale })} @@ -490,7 +491,7 @@ {#snippet children({ xScale, yScale })} From 6599917c500073d91cc1f28e199e6b242ecaccb2 Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Wed, 4 Jun 2025 12:04:30 -0400 Subject: [PATCH 5/7] fix(Treemap): Fix `padding*` prop types to support function or number constant --- .changeset/spotty-rules-taste.md | 5 ++ .../src/lib/components/Treemap.svelte | 58 ++++++++++++++----- 2 files changed, 49 insertions(+), 14 deletions(-) create mode 100644 .changeset/spotty-rules-taste.md diff --git a/.changeset/spotty-rules-taste.md b/.changeset/spotty-rules-taste.md new file mode 100644 index 000000000..50a84568d --- /dev/null +++ b/.changeset/spotty-rules-taste.md @@ -0,0 +1,5 @@ +--- +'layerchart': patch +--- + +fix(Treemap): Fix `padding*` prop types to support function or number constant diff --git a/packages/layerchart/src/lib/components/Treemap.svelte b/packages/layerchart/src/lib/components/Treemap.svelte index 474b3bb86..3d92d0b54 100644 --- a/packages/layerchart/src/lib/components/Treemap.svelte +++ b/packages/layerchart/src/lib/components/Treemap.svelte @@ -18,46 +18,46 @@ * * @default 0 */ - padding?: number; + padding?: number | ((node: HierarchyRectangularNode) => number); /** * The inner padding between nodes. * * @default 0 */ - paddingInner?: number; + paddingInner?: number | ((node: HierarchyRectangularNode) => number); /** * The outer padding between nodes. * * @default 0 */ - paddingOuter?: number; + paddingOuter?: number | ((node: HierarchyRectangularNode) => number); /** * The top padding between nodes. * * @default 0 */ - paddingTop?: number; + paddingTop?: number | ((node: HierarchyRectangularNode) => number); /** * The bottom padding between nodes. * * @default 0 */ - paddingBottom?: number; + paddingBottom?: number | ((node: HierarchyRectangularNode) => number); /** * The left padding between nodes. * */ - paddingLeft?: number; + paddingLeft?: number | ((node: HierarchyRectangularNode) => number); /** * The right padding between nodes. * */ - paddingRight?: number; + paddingRight?: number | ((node: HierarchyRectangularNode) => number); /** * Modify tiling function for approapriate aspect ratio when treemap is zoomed in @@ -127,30 +127,60 @@ .tile(maintainAspectRatio ? aspectTile(tileFunc, ctx.width, ctx.height) : tileFunc); if (padding) { - _treemap.padding(padding); + // Make Typescript happy to pick the correct overload + // TODO: Better way to do this? + if (typeof padding === 'number') { + _treemap.padding(padding); + } else { + _treemap.padding(padding); + } } if (paddingInner) { - _treemap.paddingInner(paddingInner); + if (typeof paddingInner === 'number') { + _treemap.paddingInner(typeof paddingInner === 'number' ? paddingInner : paddingInner); + } else { + _treemap.paddingInner(paddingInner); + } } if (paddingOuter) { - _treemap.paddingOuter(paddingOuter); + if (typeof paddingOuter === 'number') { + _treemap.paddingOuter(paddingOuter); + } else { + _treemap.paddingOuter(paddingOuter); + } } if (paddingTop) { - _treemap.paddingTop(paddingTop); + if (typeof paddingTop === 'number') { + _treemap.paddingTop(paddingTop); + } else { + _treemap.paddingTop(paddingTop); + } } if (paddingBottom) { - _treemap.paddingBottom(paddingBottom); + if (typeof paddingBottom === 'number') { + _treemap.paddingBottom(paddingBottom); + } else { + _treemap.paddingBottom(paddingBottom); + } } if (paddingLeft) { - _treemap.paddingLeft(paddingLeft); + if (typeof paddingLeft === 'number') { + _treemap.paddingLeft(paddingLeft); + } else { + _treemap.paddingLeft(paddingLeft); + } } if (paddingRight) { - _treemap.paddingRight(paddingRight); + if (typeof paddingRight === 'number') { + _treemap.paddingRight(paddingRight); + } else { + _treemap.paddingRight(paddingRight); + } } if (hierarchy) { From cacfa8fdc49cb37db846c3d0d9c70270142b27c0 Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Fri, 6 Jun 2025 09:29:07 -0400 Subject: [PATCH 6/7] docs(Treemap): Fix display of nodes on treemap example (referential equality) --- .../layerchart/src/routes/docs/examples/Treemap/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/layerchart/src/routes/docs/examples/Treemap/+page.svelte b/packages/layerchart/src/routes/docs/examples/Treemap/+page.svelte index fa67dc491..3577be8b7 100644 --- a/packages/layerchart/src/routes/docs/examples/Treemap/+page.svelte +++ b/packages/layerchart/src/routes/docs/examples/Treemap/+page.svelte @@ -508,7 +508,7 @@ {@const nodeHeight = yScale(node.y1) - yScale(node.y0)} {@const nodeColor = getNodeColor(node, colorBy)} - {#if isNodeVisible(node, selectedZoomable)} + {#if isNodeVisible(node, nodes[0])} Date: Fri, 6 Jun 2025 11:01:32 -0400 Subject: [PATCH 7/7] docs(Treemap): Fix zoomable examples --- packages/layerchart/src/lib/utils/treemap.ts | 2 +- .../src/routes/docs/examples/Treemap/+page.svelte | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/layerchart/src/lib/utils/treemap.ts b/packages/layerchart/src/lib/utils/treemap.ts index 5d91de23a..5bace5889 100644 --- a/packages/layerchart/src/lib/utils/treemap.ts +++ b/packages/layerchart/src/lib/utils/treemap.ts @@ -28,7 +28,7 @@ export function aspectTile(tile: TileFunc, width: number, height: number): TileF /** * Show if the node (a) is a child of the selected (b), or any parent above selected */ -export function isNodeVisible(a: HierarchyNode, b: HierarchyNode | null) { +export function isNodeVisible(a: HierarchyNode, b: HierarchyNode | null | undefined) { while (b) { if (a.parent === b) return true; b = b.parent; diff --git a/packages/layerchart/src/routes/docs/examples/Treemap/+page.svelte b/packages/layerchart/src/routes/docs/examples/Treemap/+page.svelte index 3577be8b7..1ea742b1d 100644 --- a/packages/layerchart/src/routes/docs/examples/Treemap/+page.svelte +++ b/packages/layerchart/src/routes/docs/examples/Treemap/+page.svelte @@ -194,7 +194,7 @@ {#snippet children({ xScale, yScale })} {#snippet children({ xScale, yScale })} - + {#snippet children({ nodes })} {#each nodes as node} {@const nodeColor = getNodeColor(node, colorBy)} - {#if isNodeVisible(node, nodes[0])} + {#if isNodeVisible( node, nodes.find((n) => n.data.name === selectedZoomable.data.name && n.depth === selectedZoomable.depth) )}