layerchart 2.0.0-next.3 → 2.0.0-next.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/dist/components/AnnotationPoint.svelte +16 -9
  2. package/dist/components/AnnotationRange.svelte +3 -3
  3. package/dist/components/Arc.svelte +2 -2
  4. package/dist/components/Axis.svelte +63 -14
  5. package/dist/components/Axis.svelte.d.ts +12 -2
  6. package/dist/components/Blur.svelte +5 -3
  7. package/dist/components/Blur.svelte.d.ts +2 -5
  8. package/dist/components/BrushContext.svelte +1 -1
  9. package/dist/components/Calendar.svelte +10 -6
  10. package/dist/components/Calendar.svelte.d.ts +2 -1
  11. package/dist/components/Chart.svelte +2 -2
  12. package/dist/components/Connector.svelte +2 -2
  13. package/dist/components/Connector.svelte.d.ts +1 -1
  14. package/dist/components/Ellipse.svelte +187 -0
  15. package/dist/components/Ellipse.svelte.d.ts +64 -0
  16. package/dist/components/ForceSimulation.svelte +168 -50
  17. package/dist/components/ForceSimulation.svelte.d.ts +80 -21
  18. package/dist/components/GeoPath.svelte +12 -5
  19. package/dist/components/GeoPoint.svelte +1 -2
  20. package/dist/components/GeoSpline.svelte +4 -4
  21. package/dist/components/GeoSpline.svelte.d.ts +1 -1
  22. package/dist/components/Group.svelte +2 -2
  23. package/dist/components/Highlight.svelte +2 -2
  24. package/dist/components/Hull.svelte +1 -1
  25. package/dist/components/Labels.svelte +3 -2
  26. package/dist/components/Labels.svelte.d.ts +2 -2
  27. package/dist/components/Legend.svelte +19 -12
  28. package/dist/components/Legend.svelte.d.ts +5 -5
  29. package/dist/components/MonthPath.svelte +14 -11
  30. package/dist/components/MonthPath.svelte.d.ts +4 -3
  31. package/dist/components/Polygon.svelte +285 -0
  32. package/dist/components/Polygon.svelte.d.ts +115 -0
  33. package/dist/components/RadialGradient.svelte +1 -3
  34. package/dist/components/Spline.svelte +30 -18
  35. package/dist/components/Spline.svelte.d.ts +12 -4
  36. package/dist/components/Text.svelte +62 -60
  37. package/dist/components/Text.svelte.d.ts +6 -0
  38. package/dist/components/TransformControls.svelte +16 -20
  39. package/dist/components/Treemap.svelte +63 -26
  40. package/dist/components/Treemap.svelte.d.ts +11 -11
  41. package/dist/components/Voronoi.svelte +51 -33
  42. package/dist/components/Voronoi.svelte.d.ts +3 -1
  43. package/dist/components/charts/ArcChart.svelte +5 -3
  44. package/dist/components/charts/AreaChart.svelte +11 -11
  45. package/dist/components/charts/BarChart.svelte +64 -53
  46. package/dist/components/charts/DefaultTooltip.svelte +1 -1
  47. package/dist/components/charts/LineChart.svelte +10 -6
  48. package/dist/components/charts/PieChart.svelte +5 -3
  49. package/dist/components/charts/ScatterChart.svelte +2 -3
  50. package/dist/components/charts/utils.svelte.d.ts +2 -2
  51. package/dist/components/charts/utils.svelte.js +5 -1
  52. package/dist/components/index.d.ts +4 -0
  53. package/dist/components/index.js +5 -1
  54. package/dist/components/layout/Canvas.svelte +67 -49
  55. package/dist/components/layout/Canvas.svelte.d.ts +6 -0
  56. package/dist/components/layout/Layer.svelte +6 -4
  57. package/dist/components/layout/Layer.svelte.d.ts +6 -4
  58. package/dist/components/tooltip/Tooltip.svelte +14 -7
  59. package/dist/components/tooltip/TooltipContext.svelte +78 -34
  60. package/dist/components/tooltip/TooltipContext.svelte.d.ts +3 -3
  61. package/dist/components/tooltip/TooltipHeader.svelte +5 -4
  62. package/dist/components/tooltip/TooltipHeader.svelte.d.ts +3 -3
  63. package/dist/components/tooltip/TooltipItem.svelte +5 -4
  64. package/dist/components/tooltip/TooltipItem.svelte.d.ts +3 -3
  65. package/dist/components/tooltip/TooltipList.svelte +1 -1
  66. package/dist/components/tooltip/tooltipMetaContext.d.ts +2 -2
  67. package/dist/docs/Blockquote.svelte +6 -4
  68. package/dist/docs/Blockquote.svelte.d.ts +4 -19
  69. package/dist/docs/Code.svelte +20 -12
  70. package/dist/docs/Code.svelte.d.ts +9 -23
  71. package/dist/docs/Header1.svelte +4 -2
  72. package/dist/docs/Header1.svelte.d.ts +4 -28
  73. package/dist/docs/Json.svelte +11 -3
  74. package/dist/docs/Json.svelte.d.ts +9 -21
  75. package/dist/docs/Layout.svelte +10 -7
  76. package/dist/docs/Layout.svelte.d.ts +4 -19
  77. package/dist/docs/Link.svelte +7 -3
  78. package/dist/docs/Link.svelte.d.ts +4 -38
  79. package/dist/docs/Preview.svelte +6 -3
  80. package/dist/docs/TilesetField.svelte +20 -19
  81. package/dist/docs/TilesetField.svelte.d.ts +5 -22
  82. package/dist/docs/ViewSourceButton.svelte +9 -6
  83. package/dist/docs/ViewSourceButton.svelte.d.ts +7 -21
  84. package/dist/utils/arcText.svelte.js +4 -4
  85. package/dist/utils/array.d.ts +11 -0
  86. package/dist/utils/array.js +23 -0
  87. package/dist/utils/array.test.d.ts +1 -0
  88. package/dist/utils/array.test.js +200 -0
  89. package/dist/utils/canvas.d.ts +77 -0
  90. package/dist/utils/canvas.js +105 -41
  91. package/dist/utils/genData.d.ts +14 -0
  92. package/dist/utils/genData.js +24 -6
  93. package/dist/utils/index.d.ts +1 -0
  94. package/dist/utils/index.js +1 -0
  95. package/dist/utils/path.d.ts +10 -0
  96. package/dist/utils/path.js +30 -0
  97. package/dist/utils/scales.svelte.d.ts +3 -2
  98. package/dist/utils/scales.svelte.js +7 -3
  99. package/dist/utils/shape.d.ts +43 -0
  100. package/dist/utils/shape.js +59 -0
  101. package/dist/utils/string.d.ts +49 -0
  102. package/dist/utils/string.js +4 -2
  103. package/dist/utils/ticks.d.ts +15 -4
  104. package/dist/utils/ticks.js +140 -159
  105. package/dist/utils/ticks.test.js +6 -16
  106. package/dist/utils/treemap.d.ts +1 -1
  107. package/package.json +27 -25
  108. package/dist/utils/object.js +0 -2
@@ -102,6 +102,7 @@
102
102
  fx = cx,
103
103
  fy = cy,
104
104
  r = '50%',
105
+ // fr = '0%'; // TODO: Svelte / Typescript does not know `<radialRadiant fr="...">`
105
106
  spreadMethod = 'pad',
106
107
  transform = undefined,
107
108
  units = 'objectBoundingBox',
@@ -111,9 +112,6 @@
111
112
  ...restProps
112
113
  }: RadialGradientProps = $props();
113
114
 
114
- // TODO: Svelte / Typescript does not know `<radialRadiant fr="...">`
115
- // export let fr = '0%';
116
-
117
115
  const ctx = getChartContext();
118
116
 
119
117
  const renderCtx = getRenderContext();
@@ -87,23 +87,23 @@
87
87
  /**
88
88
  * Add additional content at the start of the line.
89
89
  *
90
- * Receives `{ point: DOMPoint }` as a snippet prop.
90
+ * Receives `{ point: DOMPoint; value: { x: number; y: number } }` as a snippet prop.
91
91
  */
92
- startContent?: Snippet<[{ point: DOMPoint }]>;
92
+ startContent?: Snippet<[{ point: DOMPoint; value: { x: number; y: number } }]>;
93
93
 
94
94
  /**
95
95
  * Add additional content at the end of the line.
96
96
  *
97
- * Receives `{ point: DOMPoint }` as a snippet prop.
97
+ * Receives `{ point: DOMPoint; value: { x: number; y: number } }` as a snippet prop.
98
98
  */
99
- endContent?: Snippet<[{ point: DOMPoint }]>;
99
+ endContent?: Snippet<[{ point: DOMPoint; value: { x: number; y: number } }]>;
100
100
 
101
101
  /**
102
102
  * A reference to the `<path>` element.
103
103
  *
104
104
  * @bindable
105
105
  */
106
- splineRef?: SVGPathElement;
106
+ pathRef?: SVGPathElement;
107
107
 
108
108
  motion?: MotionProp;
109
109
  } & CommonStyleProps;
@@ -159,14 +159,14 @@
159
159
  startContent,
160
160
  endContent,
161
161
  opacity,
162
- splineRef: splineRefProp = $bindable(),
162
+ pathRef: pathRefProp = $bindable(),
163
163
  ...restProps
164
164
  }: SplineProps = $props();
165
165
 
166
- let splineRef = $state<SVGPathElement>();
166
+ let pathRef = $state<SVGPathElement>();
167
167
 
168
168
  $effect.pre(() => {
169
- splineRefProp = splineRef;
169
+ pathRefProp = pathRef;
170
170
  });
171
171
 
172
172
  const markerStart = $derived(markerStartProp ?? marker);
@@ -331,8 +331,8 @@
331
331
  easing: typeof draw === 'object' && draw.easing ? draw.easing : cubicInOut,
332
332
  interpolate() {
333
333
  return (t: number) => {
334
- const totalLength = splineRef?.getTotalLength() ?? 0;
335
- const point = splineRef?.getPointAtLength(totalLength * t);
334
+ const totalLength = pathRef?.getTotalLength() ?? 0;
335
+ const point = pathRef?.getPointAtLength(totalLength * t);
336
336
  return point;
337
337
  };
338
338
  },
@@ -343,16 +343,16 @@
343
343
  $effect(() => {
344
344
  if (!startContent && !endContent) return;
345
345
  d;
346
- if (!splineRef || !splineRef.getTotalLength()) return;
347
- startPoint = splineRef.getPointAtLength(0);
348
- const totalLength = splineRef.getTotalLength();
349
- endPoint.target = splineRef.getPointAtLength(totalLength);
346
+ if (!pathRef || !pathRef.getTotalLength()) return;
347
+ startPoint = pathRef.getPointAtLength(0);
348
+ const totalLength = pathRef.getTotalLength();
349
+ endPoint.target = pathRef.getPointAtLength(totalLength);
350
350
  });
351
351
 
352
352
  $effect(() => {
353
353
  if (!draw) return;
354
- [tweenedState.current];
355
354
  // Anytime the path data changes, redraw
355
+ [pathData, data, ctx.data];
356
356
  key = Symbol();
357
357
  });
358
358
  </script>
@@ -377,7 +377,7 @@
377
377
  marker-mid={markerMidId ? `url(#${markerMidId})` : undefined}
378
378
  marker-end={markerEndId ? `url(#${markerEndId})` : undefined}
379
379
  in:drawTransition|global={typeof draw === 'object' ? draw : undefined}
380
- bind:this={splineRef}
380
+ bind:this={pathRef}
381
381
  />
382
382
  <MarkerWrapper id={markerStartId} marker={markerStart} />
383
383
  <MarkerWrapper id={markerMidId} marker={markerMid} />
@@ -385,13 +385,25 @@
385
385
 
386
386
  {#if startContent && startPoint}
387
387
  <Group x={startPoint.x} y={startPoint.y} class={layerClass('spline-g-start')}>
388
- {@render startContent({ point: startPoint })}
388
+ {@render startContent({
389
+ point: startPoint,
390
+ value: {
391
+ x: ctx.xScale?.invert?.(startPoint.x),
392
+ y: ctx.yScale?.invert?.(startPoint.y),
393
+ },
394
+ })}
389
395
  </Group>
390
396
  {/if}
391
397
 
392
398
  {#if endContent && endPoint.current}
393
399
  <Group x={endPoint.current.x} y={endPoint.current.y} class={layerClass('spline-g-end')}>
394
- {@render endContent({ point: endPoint.current })}
400
+ {@render endContent({
401
+ point: endPoint.current,
402
+ value: {
403
+ x: ctx.xScale?.invert?.(endPoint.current.x),
404
+ y: ctx.yScale?.invert?.(endPoint.current.y),
405
+ },
406
+ })}
395
407
  </Group>
396
408
  {/if}
397
409
  {/key}
@@ -67,29 +67,37 @@ export type SplinePropsWithoutHTML = {
67
67
  /**
68
68
  * Add additional content at the start of the line.
69
69
  *
70
- * Receives `{ point: DOMPoint }` as a snippet prop.
70
+ * Receives `{ point: DOMPoint; value: { x: number; y: number } }` as a snippet prop.
71
71
  */
72
72
  startContent?: Snippet<[{
73
73
  point: DOMPoint;
74
+ value: {
75
+ x: number;
76
+ y: number;
77
+ };
74
78
  }]>;
75
79
  /**
76
80
  * Add additional content at the end of the line.
77
81
  *
78
- * Receives `{ point: DOMPoint }` as a snippet prop.
82
+ * Receives `{ point: DOMPoint; value: { x: number; y: number } }` as a snippet prop.
79
83
  */
80
84
  endContent?: Snippet<[{
81
85
  point: DOMPoint;
86
+ value: {
87
+ x: number;
88
+ y: number;
89
+ };
82
90
  }]>;
83
91
  /**
84
92
  * A reference to the `<path>` element.
85
93
  *
86
94
  * @bindable
87
95
  */
88
- splineRef?: SVGPathElement;
96
+ pathRef?: SVGPathElement;
89
97
  motion?: MotionProp;
90
98
  } & CommonStyleProps;
91
99
  export type SplineProps = SplinePropsWithoutHTML & Without<SVGAttributes<SVGPathElement>, SplinePropsWithoutHTML>;
92
100
  import { draw as _drawTransition } from 'svelte/transition';
93
- declare const Spline: import("svelte").Component<SplineProps, {}, "splineRef">;
101
+ declare const Spline: import("svelte").Component<SplineProps, {}, "pathRef">;
94
102
  type Spline = ReturnType<typeof Spline>;
95
103
  export default Spline;
@@ -97,6 +97,20 @@
97
97
  */
98
98
  verticalAnchor?: 'start' | 'middle' | 'end' | 'inherit';
99
99
 
100
+ /**
101
+ * The dominant baseline of the text. Useful for aligning text to the baseline of the axis.
102
+ *
103
+ * @default 'auto'
104
+ */
105
+ dominantBaseline?:
106
+ | 'auto'
107
+ | 'text-before-edge'
108
+ | 'text-after-edge'
109
+ | 'middle'
110
+ | 'hanging'
111
+ | 'ideographic'
112
+ | 'mathematical';
113
+
100
114
  /**
101
115
  * Rotational angle of the text
102
116
  */
@@ -178,20 +192,6 @@
178
192
  import { degreesToRadians } from '../utils/math.js';
179
193
  import { createId } from '../utils/createId.js';
180
194
 
181
- /*
182
- TODO:
183
- - [ ] Handle styled text (use <slot /> to measure?)
184
- - [ ] Simplify by using `alignment-baseline` / `dominant-baseline`, rework multiline or drop support, etc
185
- - https://svelte.dev/repl/f12d3003313a43ba8a0be53e5786f1c7?version=3.44.3
186
- - https://observablehq.com/@neocartocnrs/cheat-sheet-on-texts-in-svg
187
-
188
- Reference:
189
- - https://bl.ocks.org/mbostock/7555321
190
- - https://github.com/airbnb/visx/blob/master/packages/visx-text/src/Text.tsx
191
- - https://airbnb.io/visx/text
192
- - https://github.com/airbnb/visx/blob/master/packages/visx-demo/src/pages/text.tsx
193
- */
194
-
195
195
  const uid = $props.id();
196
196
 
197
197
  let {
@@ -208,6 +208,7 @@
208
208
  scaleToFit = false,
209
209
  textAnchor = 'start',
210
210
  verticalAnchor = 'end',
211
+ dominantBaseline = 'auto',
211
212
  rotate,
212
213
  opacity = 1,
213
214
  strokeWidth = 0,
@@ -227,6 +228,8 @@
227
228
  ...restProps
228
229
  }: TextProps = $props();
229
230
 
231
+ const renderCtx = getRenderContext();
232
+
230
233
  let ref = $state<SVGTextElement>();
231
234
  let svgRef = $state<SVGElement>();
232
235
  let pathRef = $state<SVGPathElement>();
@@ -260,49 +263,56 @@
260
263
  };
261
264
  });
262
265
 
263
- const rawText = $derived(value != null ? value.toString() : '');
266
+ // Handle null and convert `\n` strings back to newline characters
267
+ const rawText = $derived(value != null ? value.toString().replace(/\\n/g, '\n') : '');
264
268
 
265
269
  const textValue = $derived.by(() => {
266
270
  if (!truncateConfig) return rawText;
267
271
  return truncateText(rawText, truncateConfig);
268
272
  });
269
273
 
270
- const renderCtx = getRenderContext();
274
+ const spaceWidth = $derived(getStringWidth('\u00A0', style) || 0);
271
275
 
272
- const words = $derived(textValue ? textValue.split(/(?:(?!\u00A0+)\s+)/) : []);
276
+ const wordsByLines = $derived.by(() => {
277
+ // Split by newlines to preserve explicit line breaks
278
+ const lines = textValue.split('\n');
273
279
 
274
- const wordsWithWidth = $derived(
275
- words.map((word) => ({
276
- word,
277
- width: getStringWidth(word, style) || 0,
278
- }))
279
- );
280
+ return lines.flatMap((line) => {
281
+ // Split each line into words
282
+ const words = line.split(/(?:(?!\u00A0+)\s+)/);
280
283
 
281
- const spaceWidth = $derived(getStringWidth('\u00A0', style) || 0);
282
-
283
- const wordsByLines = $derived(
284
- wordsWithWidth.reduce((result: { words: string[]; width?: number }[], item) => {
285
- const currentLine = result[result.length - 1];
286
-
287
- if (
288
- currentLine &&
289
- (width == null || scaleToFit || (currentLine.width || 0) + item.width + spaceWidth < width)
290
- ) {
291
- // Word can be added to an existing line
292
- currentLine.words.push(item.word);
293
- currentLine.width = currentLine.width || 0;
294
- currentLine.width += item.width + spaceWidth;
284
+ if (width == null) {
285
+ // No width specified, only use explicit line breaks (if used)
286
+ return [{ words }];
295
287
  } else {
296
- // Add first word to line or word is too long to scaleToFit on existing line
297
- const newLine = { words: [item.word], width: item.width };
298
- result.push(newLine);
288
+ // Handle word wrapping within each line
289
+ return words.reduce((result: { words: string[]; width?: number }[], item) => {
290
+ const currentLine = result[result.length - 1];
291
+ const itemWidth = getStringWidth(item, style) || 0;
292
+
293
+ if (
294
+ currentLine &&
295
+ (width == null ||
296
+ scaleToFit ||
297
+ (currentLine.width || 0) + itemWidth + spaceWidth < width)
298
+ ) {
299
+ // Word can be added to an existing line
300
+ currentLine.words.push(item);
301
+ currentLine.width = currentLine.width || 0;
302
+ currentLine.width += itemWidth + spaceWidth;
303
+ } else {
304
+ // Add first word to line or word is too long to scaleToFit on existing line
305
+ const newLine = { words: [item], width: itemWidth };
306
+ result.push(newLine);
307
+ }
308
+
309
+ return result;
310
+ }, []);
299
311
  }
312
+ });
313
+ });
300
314
 
301
- return result;
302
- }, [])
303
- );
304
-
305
- const lines = $derived(wordsByLines.length);
315
+ const lineCount = $derived(wordsByLines.length);
306
316
 
307
317
  /**
308
318
  * Convert css value to pixel value (ex. 0.71em => 11.36)
@@ -327,28 +337,18 @@
327
337
 
328
338
  const startDy = $derived.by(() => {
329
339
  if (verticalAnchor === 'start') {
330
- return getPixelValue(capHeight);
331
- } else if (verticalAnchor === 'middle') {
332
- return ((lines - 1) / 2) * -getPixelValue(lineHeight) + getPixelValue(capHeight) / 2;
333
- } else {
334
- return (lines - 1) * -getPixelValue(lineHeight);
335
- }
336
- });
337
-
338
- const pathStartDy = $derived.by(() => {
339
- if (verticalAnchor === 'start') {
340
- return getPixelValue(capHeight);
340
+ return getPixelValue(lineHeight);
341
341
  } else if (verticalAnchor === 'middle') {
342
- return (0 / 2) * -getPixelValue(lineHeight) + getPixelValue(capHeight) / 2;
342
+ return ((lineCount - 1) / 2) * -getPixelValue(lineHeight) + getPixelValue(capHeight) / 2;
343
343
  } else {
344
- return 0 * -getPixelValue(lineHeight);
344
+ return (lineCount - 1) * -getPixelValue(lineHeight) - getPixelValue(capHeight) / 2;
345
345
  }
346
346
  });
347
347
 
348
348
  const scaleTransform = $derived.by(() => {
349
349
  if (
350
350
  scaleToFit &&
351
- lines > 0 &&
351
+ lineCount > 0 &&
352
352
  typeof x == 'number' &&
353
353
  typeof y == 'number' &&
354
354
  typeof width == 'number'
@@ -500,6 +500,7 @@
500
500
  >
501
501
  <textPath
502
502
  style="text-anchor: {textAnchor};"
503
+ dominant-baseline={dominantBaseline}
503
504
  href="#{pathId}"
504
505
  {startOffset}
505
506
  class={cls(layerClass('text-path'))}
@@ -514,6 +515,7 @@
514
515
  y={motionY.current}
515
516
  {transform}
516
517
  text-anchor={textAnchor}
518
+ dominant-baseline={dominantBaseline}
517
519
  {...restProps}
518
520
  {fill}
519
521
  fill-opacity={fillOpacity}
@@ -525,7 +527,7 @@
525
527
  {#each wordsByLines as line, index}
526
528
  <tspan
527
529
  x={motionX.current}
528
- dy={index === 0 ? startDy : lineHeight}
530
+ dy={index === 0 ? startDy : getPixelValue(lineHeight)}
529
531
  class={layerClass('text-tspan')}
530
532
  >
531
533
  {line.words.join(' ')}
@@ -80,6 +80,12 @@ export type TextPropsWithoutHTML = {
80
80
  * @default 'end'
81
81
  */
82
82
  verticalAnchor?: 'start' | 'middle' | 'end' | 'inherit';
83
+ /**
84
+ * The dominant baseline of the text. Useful for aligning text to the baseline of the axis.
85
+ *
86
+ * @default 'auto'
87
+ */
88
+ dominantBaseline?: 'auto' | 'text-before-edge' | 'text-after-edge' | 'middle' | 'hanging' | 'ideographic' | 'mathematical';
83
89
  /**
84
90
  * Rotational angle of the text
85
91
  */
@@ -34,18 +34,14 @@
34
34
  import { Button, Icon, MenuButton, Tooltip } from 'svelte-ux';
35
35
  import { cls } from '@layerstack/tailwind';
36
36
 
37
- // TODO: maybe we include the icons as I think importing them like this
38
- // will bog down the dev server
39
- import {
40
- mdiArrowULeftTop,
41
- mdiMagnifyPlusOutline,
42
- mdiMagnifyMinusOutline,
43
- mdiImageFilterCenterFocus,
44
- mdiChevronDown,
45
- mdiResize,
46
- mdiArrowExpandAll,
47
- mdiCancel,
48
- } from '@mdi/js';
37
+ import LucideFocus from '~icons/lucide/focus';
38
+ import LucideChevronDown from '~icons/lucide/chevron-down';
39
+ import LucideCircleOff from '~icons/lucide/circle-off';
40
+ import LucideImageUpscale from '~icons/lucide/image-upscale';
41
+ import LucideMove from '~icons/lucide/move';
42
+ import LucideUndo2 from '~icons/lucide/undo-2';
43
+ import LucideZoomIn from '~icons/lucide/zoom-in';
44
+ import LucideZoomOut from '~icons/lucide/zoom-out';
49
45
 
50
46
  import { getTransformContext } from './TransformContext.svelte';
51
47
  import type { Without } from '../utils/types.js';
@@ -113,7 +109,7 @@
113
109
  {#if show.includes('zoomIn')}
114
110
  <Tooltip title="Zoom in">
115
111
  <Button
116
- icon={mdiMagnifyPlusOutline}
112
+ icon={LucideZoomIn}
117
113
  on:click={() => transform.zoomIn()}
118
114
  {size}
119
115
  class="text-surface-content p-2"
@@ -124,7 +120,7 @@
124
120
  {#if show.includes('zoomOut')}
125
121
  <Tooltip title="Zoom out">
126
122
  <Button
127
- icon={mdiMagnifyMinusOutline}
123
+ icon={LucideZoomOut}
128
124
  on:click={() => transform.zoomOut()}
129
125
  {size}
130
126
  class="text-surface-content p-2"
@@ -135,7 +131,7 @@
135
131
  {#if show.includes('center')}
136
132
  <Tooltip title="Center">
137
133
  <Button
138
- icon={mdiImageFilterCenterFocus}
134
+ icon={LucideFocus}
139
135
  on:click={() => transform.translateCenter()}
140
136
  {size}
141
137
  class="text-surface-content p-2"
@@ -146,7 +142,7 @@
146
142
  {#if show.includes('reset')}
147
143
  <Tooltip title="Reset">
148
144
  <Button
149
- icon={mdiArrowULeftTop}
145
+ icon={LucideUndo2}
150
146
  on:click={() => transform.reset()}
151
147
  {size}
152
148
  class="text-surface-content p-2"
@@ -159,9 +155,9 @@
159
155
  <MenuButton
160
156
  iconOnly
161
157
  options={[
162
- { label: 'None', value: 'none', icon: mdiCancel },
163
- { label: 'Zoom', value: 'scale', icon: mdiResize },
164
- { label: 'Move', value: 'translate', icon: mdiArrowExpandAll },
158
+ { label: 'None', value: 'none', icon: LucideCircleOff },
159
+ { label: 'Zoom', value: 'scale', icon: LucideImageUpscale },
160
+ { label: 'Move', value: 'translate', icon: LucideMove },
165
161
  ]}
166
162
  menuProps={{ placement: menuPlacementByOrientationAndPlacement[orientation][placement] }}
167
163
  menuIcon={null}
@@ -171,7 +167,7 @@
171
167
  class="text-surface-content"
172
168
  >
173
169
  <svelte:fragment slot="selection" let:value>
174
- <Icon data={value?.icon ?? mdiChevronDown} />
170
+ <Icon data={value?.icon ?? LucideChevronDown} />
175
171
  </svelte:fragment>
176
172
  </MenuButton>
177
173
  </Tooltip>
@@ -18,53 +18,53 @@
18
18
  *
19
19
  * @default 0
20
20
  */
21
- padding?: number;
21
+ padding?: number | ((node: HierarchyRectangularNode<T>) => number);
22
22
 
23
23
  /**
24
24
  * The inner padding between nodes.
25
25
  *
26
26
  * @default 0
27
27
  */
28
- paddingInner?: number;
28
+ paddingInner?: number | ((node: HierarchyRectangularNode<T>) => number);
29
29
 
30
30
  /**
31
31
  * The outer padding between nodes.
32
32
  *
33
33
  * @default 0
34
34
  */
35
- paddingOuter?: number;
35
+ paddingOuter?: number | ((node: HierarchyRectangularNode<T>) => number);
36
36
 
37
37
  /**
38
38
  * The top padding between nodes.
39
39
  *
40
40
  * @default 0
41
41
  */
42
- paddingTop?: number;
42
+ paddingTop?: number | ((node: HierarchyRectangularNode<T>) => number);
43
43
 
44
44
  /**
45
45
  * The bottom padding between nodes.
46
46
  *
47
47
  * @default 0
48
48
  */
49
- paddingBottom?: number;
49
+ paddingBottom?: number | ((node: HierarchyRectangularNode<T>) => number);
50
50
  /**
51
51
  * The left padding between nodes.
52
52
  *
53
53
  */
54
- paddingLeft?: number;
54
+ paddingLeft?: number | ((node: HierarchyRectangularNode<T>) => number);
55
55
 
56
56
  /**
57
57
  * The right padding between nodes.
58
58
  *
59
59
  */
60
- paddingRight?: number;
60
+ paddingRight?: number | ((node: HierarchyRectangularNode<T>) => number);
61
61
 
62
62
  /**
63
- * The selected node.
63
+ * Modify tiling function for approapriate aspect ratio when treemap is zoomed in
64
64
  *
65
- * @default null
65
+ * @default false
66
66
  */
67
- selected?: HierarchyRectangularNode<T> | null;
67
+ maintainAspectRatio?: boolean;
68
68
 
69
69
  hierarchy?: HierarchyNode<T>;
70
70
 
@@ -99,7 +99,7 @@
99
99
  paddingBottom = 0,
100
100
  paddingLeft,
101
101
  paddingRight,
102
- selected = $bindable(null),
102
+ maintainAspectRatio = false,
103
103
  children,
104
104
  }: TreemapProps<T> = $props();
105
105
 
@@ -121,45 +121,82 @@
121
121
  : tile
122
122
  );
123
123
 
124
- const treemap = $derived.by(() => {
124
+ const treemapData = $derived.by(() => {
125
125
  const _treemap = d3treemap<T>()
126
126
  .size([ctx.width, ctx.height])
127
- .tile(aspectTile(tileFunc, ctx.width, ctx.height));
127
+ .tile(maintainAspectRatio ? aspectTile(tileFunc, ctx.width, ctx.height) : tileFunc);
128
128
 
129
129
  if (padding) {
130
- _treemap.padding(padding);
130
+ // Make Typescript happy to pick the correct overload
131
+ // TODO: Better way to do this?
132
+ if (typeof padding === 'number') {
133
+ _treemap.padding(padding);
134
+ } else {
135
+ _treemap.padding(padding);
136
+ }
131
137
  }
132
138
 
133
139
  if (paddingInner) {
134
- _treemap.paddingInner(paddingInner);
140
+ if (typeof paddingInner === 'number') {
141
+ _treemap.paddingInner(typeof paddingInner === 'number' ? paddingInner : paddingInner);
142
+ } else {
143
+ _treemap.paddingInner(paddingInner);
144
+ }
135
145
  }
136
146
 
137
147
  if (paddingOuter) {
138
- _treemap.paddingOuter(paddingOuter);
148
+ if (typeof paddingOuter === 'number') {
149
+ _treemap.paddingOuter(paddingOuter);
150
+ } else {
151
+ _treemap.paddingOuter(paddingOuter);
152
+ }
139
153
  }
140
154
 
141
155
  if (paddingTop) {
142
- _treemap.paddingTop(paddingTop);
156
+ if (typeof paddingTop === 'number') {
157
+ _treemap.paddingTop(paddingTop);
158
+ } else {
159
+ _treemap.paddingTop(paddingTop);
160
+ }
143
161
  }
144
162
 
145
163
  if (paddingBottom) {
146
- _treemap.paddingBottom(paddingBottom);
164
+ if (typeof paddingBottom === 'number') {
165
+ _treemap.paddingBottom(paddingBottom);
166
+ } else {
167
+ _treemap.paddingBottom(paddingBottom);
168
+ }
147
169
  }
148
170
 
149
171
  if (paddingLeft) {
150
- _treemap.paddingLeft(paddingLeft);
172
+ if (typeof paddingLeft === 'number') {
173
+ _treemap.paddingLeft(paddingLeft);
174
+ } else {
175
+ _treemap.paddingLeft(paddingLeft);
176
+ }
151
177
  }
152
178
  if (paddingRight) {
153
- _treemap.paddingRight(paddingRight);
179
+ if (typeof paddingRight === 'number') {
180
+ _treemap.paddingRight(paddingRight);
181
+ } else {
182
+ _treemap.paddingRight(paddingRight);
183
+ }
154
184
  }
155
- return _treemap;
156
- });
157
185
 
158
- const treemapData = $derived(hierarchy ? treemap(hierarchy) : null);
186
+ if (hierarchy) {
187
+ const h = hierarchy.copy();
188
+ const treemapData = _treemap(h);
189
+ return {
190
+ links: treemapData.links(),
191
+ nodes: treemapData.descendants(),
192
+ };
193
+ }
159
194
 
160
- $effect.pre(() => {
161
- selected = treemapData;
195
+ return {
196
+ links: [],
197
+ nodes: [],
198
+ };
162
199
  });
163
200
  </script>
164
201
 
165
- {@render children?.({ nodes: treemapData ? treemapData.descendants() : [] })}
202
+ {@render children?.({ nodes: treemapData.nodes })}