layerchart 2.0.0-next.3 → 2.0.0-next.31
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.
- package/dist/components/AnnotationPoint.svelte +16 -9
- package/dist/components/AnnotationRange.svelte +3 -3
- package/dist/components/Arc.svelte +2 -2
- package/dist/components/Axis.svelte +83 -29
- package/dist/components/Axis.svelte.d.ts +13 -3
- package/dist/components/Bar.svelte +12 -8
- package/dist/components/Blur.svelte +5 -3
- package/dist/components/Blur.svelte.d.ts +2 -5
- package/dist/components/BrushContext.svelte +1 -1
- package/dist/components/Calendar.svelte +10 -6
- package/dist/components/Calendar.svelte.d.ts +2 -1
- package/dist/components/Chart.svelte +39 -3
- package/dist/components/Chart.svelte.d.ts +11 -0
- package/dist/components/Connector.svelte +2 -2
- package/dist/components/Connector.svelte.d.ts +1 -1
- package/dist/components/Ellipse.svelte +187 -0
- package/dist/components/Ellipse.svelte.d.ts +64 -0
- package/dist/components/ForceSimulation.svelte +184 -50
- package/dist/components/ForceSimulation.svelte.d.ts +88 -21
- package/dist/components/GeoPath.svelte +12 -5
- package/dist/components/GeoPoint.svelte +1 -2
- package/dist/components/GeoSpline.svelte +4 -4
- package/dist/components/GeoSpline.svelte.d.ts +1 -1
- package/dist/components/Group.svelte +2 -2
- package/dist/components/Highlight.svelte +9 -6
- package/dist/components/Hull.svelte +1 -1
- package/dist/components/Labels.svelte +3 -2
- package/dist/components/Labels.svelte.d.ts +2 -2
- package/dist/components/Legend.svelte +19 -12
- package/dist/components/Legend.svelte.d.ts +5 -5
- package/dist/components/MonthPath.svelte +14 -11
- package/dist/components/MonthPath.svelte.d.ts +4 -3
- package/dist/components/Polygon.svelte +285 -0
- package/dist/components/Polygon.svelte.d.ts +115 -0
- package/dist/components/RadialGradient.svelte +1 -3
- package/dist/components/Spline.svelte +30 -18
- package/dist/components/Spline.svelte.d.ts +12 -4
- package/dist/components/Text.svelte +62 -60
- package/dist/components/Text.svelte.d.ts +6 -0
- package/dist/components/TransformControls.svelte +16 -20
- package/dist/components/Treemap.svelte +63 -26
- package/dist/components/Treemap.svelte.d.ts +11 -11
- package/dist/components/Voronoi.svelte +51 -33
- package/dist/components/Voronoi.svelte.d.ts +3 -1
- package/dist/components/charts/ArcChart.svelte +5 -3
- package/dist/components/charts/AreaChart.svelte +11 -11
- package/dist/components/charts/BarChart.svelte +72 -53
- package/dist/components/charts/DefaultTooltip.svelte +1 -1
- package/dist/components/charts/LineChart.svelte +10 -6
- package/dist/components/charts/PieChart.svelte +5 -3
- package/dist/components/charts/ScatterChart.svelte +2 -3
- package/dist/components/charts/utils.svelte.d.ts +2 -2
- package/dist/components/charts/utils.svelte.js +5 -1
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.js +5 -1
- package/dist/components/layout/Canvas.svelte +67 -49
- package/dist/components/layout/Canvas.svelte.d.ts +6 -0
- package/dist/components/layout/Layer.svelte +6 -4
- package/dist/components/layout/Layer.svelte.d.ts +6 -4
- package/dist/components/tooltip/Tooltip.svelte +14 -7
- package/dist/components/tooltip/TooltipContext.svelte +136 -43
- package/dist/components/tooltip/TooltipContext.svelte.d.ts +3 -3
- package/dist/components/tooltip/TooltipHeader.svelte +5 -4
- package/dist/components/tooltip/TooltipHeader.svelte.d.ts +3 -3
- package/dist/components/tooltip/TooltipItem.svelte +5 -4
- package/dist/components/tooltip/TooltipItem.svelte.d.ts +3 -3
- package/dist/components/tooltip/TooltipList.svelte +1 -1
- package/dist/components/tooltip/tooltipMetaContext.d.ts +2 -2
- package/dist/docs/Blockquote.svelte +6 -4
- package/dist/docs/Blockquote.svelte.d.ts +4 -19
- package/dist/docs/Code.svelte +20 -12
- package/dist/docs/Code.svelte.d.ts +9 -23
- package/dist/docs/Header1.svelte +4 -2
- package/dist/docs/Header1.svelte.d.ts +4 -28
- package/dist/docs/Json.svelte +11 -3
- package/dist/docs/Json.svelte.d.ts +9 -21
- package/dist/docs/Layout.svelte +10 -7
- package/dist/docs/Layout.svelte.d.ts +4 -19
- package/dist/docs/Link.svelte +7 -3
- package/dist/docs/Link.svelte.d.ts +4 -38
- package/dist/docs/Preview.svelte +6 -3
- package/dist/docs/TilesetField.svelte +20 -19
- package/dist/docs/TilesetField.svelte.d.ts +5 -22
- package/dist/docs/ViewSourceButton.svelte +9 -6
- package/dist/docs/ViewSourceButton.svelte.d.ts +7 -21
- package/dist/utils/arcText.svelte.js +4 -4
- package/dist/utils/array.d.ts +11 -0
- package/dist/utils/array.js +23 -0
- package/dist/utils/array.test.d.ts +1 -0
- package/dist/utils/array.test.js +200 -0
- package/dist/utils/canvas.d.ts +77 -0
- package/dist/utils/canvas.js +105 -41
- package/dist/utils/genData.d.ts +14 -0
- package/dist/utils/genData.js +24 -6
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/path.d.ts +10 -0
- package/dist/utils/path.js +30 -0
- package/dist/utils/rect.svelte.d.ts +2 -2
- package/dist/utils/rect.svelte.js +69 -1
- package/dist/utils/scales.svelte.d.ts +3 -2
- package/dist/utils/scales.svelte.js +7 -3
- package/dist/utils/shape.d.ts +43 -0
- package/dist/utils/shape.js +59 -0
- package/dist/utils/string.d.ts +49 -0
- package/dist/utils/string.js +4 -2
- package/dist/utils/ticks.d.ts +15 -4
- package/dist/utils/ticks.js +144 -158
- package/dist/utils/ticks.test.js +11 -16
- package/dist/utils/treemap.d.ts +1 -1
- package/package.json +27 -25
- package/dist/utils/object.js +0 -2
|
@@ -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
|
-
|
|
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
|
|
274
|
+
const spaceWidth = $derived(getStringWidth('\u00A0', style) || 0);
|
|
271
275
|
|
|
272
|
-
const
|
|
276
|
+
const wordsByLines = $derived.by(() => {
|
|
277
|
+
// Split by newlines to preserve explicit line breaks
|
|
278
|
+
const lines = textValue.split('\n');
|
|
273
279
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
//
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
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(
|
|
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 (
|
|
342
|
+
return ((lineCount - 1) / 2) * -getPixelValue(lineHeight) + getPixelValue(capHeight) / 2;
|
|
343
343
|
} else {
|
|
344
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
import
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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={
|
|
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={
|
|
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={
|
|
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={
|
|
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:
|
|
163
|
-
{ label: 'Zoom', value: 'scale', icon:
|
|
164
|
-
{ label: 'Move', value: 'translate', icon:
|
|
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 ??
|
|
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
|
-
*
|
|
63
|
+
* Modify tiling function for approapriate aspect ratio when treemap is zoomed in
|
|
64
64
|
*
|
|
65
|
-
* @default
|
|
65
|
+
* @default false
|
|
66
66
|
*/
|
|
67
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
172
|
+
if (typeof paddingLeft === 'number') {
|
|
173
|
+
_treemap.paddingLeft(paddingLeft);
|
|
174
|
+
} else {
|
|
175
|
+
_treemap.paddingLeft(paddingLeft);
|
|
176
|
+
}
|
|
151
177
|
}
|
|
152
178
|
if (paddingRight) {
|
|
153
|
-
|
|
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
|
-
|
|
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
|
-
|
|
161
|
-
|
|
195
|
+
return {
|
|
196
|
+
links: [],
|
|
197
|
+
nodes: [],
|
|
198
|
+
};
|
|
162
199
|
});
|
|
163
200
|
</script>
|
|
164
201
|
|
|
165
|
-
{@render children?.({ nodes: treemapData
|
|
202
|
+
{@render children?.({ nodes: treemapData.nodes })}
|
|
@@ -10,47 +10,47 @@ export type TreemapProps<T> = {
|
|
|
10
10
|
*
|
|
11
11
|
* @default 0
|
|
12
12
|
*/
|
|
13
|
-
padding?: number;
|
|
13
|
+
padding?: number | ((node: HierarchyRectangularNode<T>) => number);
|
|
14
14
|
/**
|
|
15
15
|
* The inner padding between nodes.
|
|
16
16
|
*
|
|
17
17
|
* @default 0
|
|
18
18
|
*/
|
|
19
|
-
paddingInner?: number;
|
|
19
|
+
paddingInner?: number | ((node: HierarchyRectangularNode<T>) => number);
|
|
20
20
|
/**
|
|
21
21
|
* The outer padding between nodes.
|
|
22
22
|
*
|
|
23
23
|
* @default 0
|
|
24
24
|
*/
|
|
25
|
-
paddingOuter?: number;
|
|
25
|
+
paddingOuter?: number | ((node: HierarchyRectangularNode<T>) => number);
|
|
26
26
|
/**
|
|
27
27
|
* The top padding between nodes.
|
|
28
28
|
*
|
|
29
29
|
* @default 0
|
|
30
30
|
*/
|
|
31
|
-
paddingTop?: number;
|
|
31
|
+
paddingTop?: number | ((node: HierarchyRectangularNode<T>) => number);
|
|
32
32
|
/**
|
|
33
33
|
* The bottom padding between nodes.
|
|
34
34
|
*
|
|
35
35
|
* @default 0
|
|
36
36
|
*/
|
|
37
|
-
paddingBottom?: number;
|
|
37
|
+
paddingBottom?: number | ((node: HierarchyRectangularNode<T>) => number);
|
|
38
38
|
/**
|
|
39
39
|
* The left padding between nodes.
|
|
40
40
|
*
|
|
41
41
|
*/
|
|
42
|
-
paddingLeft?: number;
|
|
42
|
+
paddingLeft?: number | ((node: HierarchyRectangularNode<T>) => number);
|
|
43
43
|
/**
|
|
44
44
|
* The right padding between nodes.
|
|
45
45
|
*
|
|
46
46
|
*/
|
|
47
|
-
paddingRight?: number;
|
|
47
|
+
paddingRight?: number | ((node: HierarchyRectangularNode<T>) => number);
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
49
|
+
* Modify tiling function for approapriate aspect ratio when treemap is zoomed in
|
|
50
50
|
*
|
|
51
|
-
* @default
|
|
51
|
+
* @default false
|
|
52
52
|
*/
|
|
53
|
-
|
|
53
|
+
maintainAspectRatio?: boolean;
|
|
54
54
|
hierarchy?: HierarchyNode<T>;
|
|
55
55
|
children?: Snippet<[{
|
|
56
56
|
nodes: HierarchyRectangularNode<T>[];
|
|
@@ -62,7 +62,7 @@ declare class __sveltets_Render<T> {
|
|
|
62
62
|
props(): TreemapProps<T>;
|
|
63
63
|
events(): {};
|
|
64
64
|
slots(): {};
|
|
65
|
-
bindings(): "
|
|
65
|
+
bindings(): "";
|
|
66
66
|
exports(): {};
|
|
67
67
|
}
|
|
68
68
|
interface $$IsomorphicComponent {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
2
|
import type { Without } from '../utils/types.js';
|
|
3
|
-
import type { SVGAttributes } from 'svelte/elements';
|
|
4
3
|
|
|
5
4
|
export type VoronoiPropsWithoutHTML = {
|
|
6
5
|
/**
|
|
@@ -8,6 +7,9 @@
|
|
|
8
7
|
*/
|
|
9
8
|
data?: any;
|
|
10
9
|
|
|
10
|
+
/** Radius to clip voronoi cells. `0` or `undefined` to disables clipping */
|
|
11
|
+
r?: number;
|
|
12
|
+
|
|
11
13
|
/**
|
|
12
14
|
* Classes to apply to the root and path elements
|
|
13
15
|
*
|
|
@@ -56,7 +58,7 @@
|
|
|
56
58
|
<script lang="ts">
|
|
57
59
|
import { min } from 'd3-array';
|
|
58
60
|
import { Delaunay } from 'd3-delaunay';
|
|
59
|
-
import type
|
|
61
|
+
import { type GeoPermissibleObjects } from 'd3-geo';
|
|
60
62
|
// @ts-expect-error
|
|
61
63
|
import { geoVoronoi } from 'd3-geo-voronoi';
|
|
62
64
|
import { pointRadial } from 'd3-shape';
|
|
@@ -67,10 +69,13 @@
|
|
|
67
69
|
import Spline from './Spline.svelte';
|
|
68
70
|
import { getChartContext } from './Chart.svelte';
|
|
69
71
|
import { getGeoContext } from './GeoContext.svelte';
|
|
72
|
+
import CircleClipPath from './CircleClipPath.svelte';
|
|
73
|
+
|
|
70
74
|
import { layerClass } from '../utils/attributes.js';
|
|
71
75
|
|
|
72
76
|
let {
|
|
73
77
|
data,
|
|
78
|
+
r,
|
|
74
79
|
classes = {},
|
|
75
80
|
onclick,
|
|
76
81
|
onpointerenter,
|
|
@@ -109,53 +114,66 @@
|
|
|
109
114
|
// Width and/or height can sometimes be negative (when loading data remotely and updately)
|
|
110
115
|
const boundWidth = $derived(Math.max(ctx.width, 0));
|
|
111
116
|
const boundHeight = $derived(Math.max(ctx.height, 0));
|
|
117
|
+
|
|
118
|
+
const disableClip = $derived(r === 0 || r == null || r === Infinity);
|
|
112
119
|
</script>
|
|
113
120
|
|
|
114
121
|
<Group {...restProps} class={cls(layerClass('voronoi-g'), classes.root, className)}>
|
|
115
122
|
{#if geo.projection}
|
|
116
123
|
{@const polygons = geoVoronoi().polygons(points)}
|
|
117
124
|
{#each polygons.features as feature}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
onpointermove={(e) => onpointermove?.(e, { data: feature.properties.site.data, feature })}
|
|
128
|
-
onpointerdown={(e) => onpointerdown?.(e, { data: feature.properties.site.data, feature })}
|
|
129
|
-
{onpointerleave}
|
|
130
|
-
ontouchmove={(e) => {
|
|
131
|
-
// Prevent touch to not interfere with pointer
|
|
132
|
-
e.preventDefault();
|
|
133
|
-
}}
|
|
134
|
-
/>
|
|
135
|
-
{/each}
|
|
136
|
-
{:else}
|
|
137
|
-
{@const voronoi = Delaunay.from(points).voronoi([0, 0, boundWidth, boundHeight])}
|
|
138
|
-
{#each points as point, i}
|
|
139
|
-
{@const pathData = voronoi.renderCell(i)}
|
|
140
|
-
<!-- Wait to render Spline until pathData is available to fix path artifacts from injected tweened points in Spline -->
|
|
141
|
-
{#if pathData}
|
|
142
|
-
<Spline
|
|
143
|
-
{pathData}
|
|
125
|
+
{@const point = r ? geo.projection?.(feature.properties.sitecoordinates) : null}
|
|
126
|
+
<CircleClipPath
|
|
127
|
+
cx={point?.[0]}
|
|
128
|
+
cy={point?.[1]}
|
|
129
|
+
r={r ?? 0}
|
|
130
|
+
disabled={point == null || disableClip}
|
|
131
|
+
>
|
|
132
|
+
<GeoPath
|
|
133
|
+
geojson={feature}
|
|
144
134
|
class={cls(
|
|
145
|
-
layerClass('voronoi-path'),
|
|
135
|
+
layerClass('voronoi-geo-path'),
|
|
146
136
|
'fill-transparent stroke-transparent',
|
|
147
137
|
classes.path
|
|
148
138
|
)}
|
|
149
|
-
onclick={(e) => onclick?.(e, { data:
|
|
150
|
-
onpointerenter={(e) =>
|
|
151
|
-
|
|
139
|
+
onclick={(e) => onclick?.(e, { data: feature.properties.site.data, feature })}
|
|
140
|
+
onpointerenter={(e) =>
|
|
141
|
+
onpointerenter?.(e, { data: feature.properties.site.data, feature })}
|
|
142
|
+
onpointermove={(e) => onpointermove?.(e, { data: feature.properties.site.data, feature })}
|
|
143
|
+
onpointerdown={(e) => onpointerdown?.(e, { data: feature.properties.site.data, feature })}
|
|
152
144
|
{onpointerleave}
|
|
153
|
-
onpointerdown={(e) => onpointerdown?.(e, { data: point.data, point })}
|
|
154
145
|
ontouchmove={(e) => {
|
|
155
146
|
// Prevent touch to not interfere with pointer
|
|
156
147
|
e.preventDefault();
|
|
157
148
|
}}
|
|
158
149
|
/>
|
|
150
|
+
</CircleClipPath>
|
|
151
|
+
{/each}
|
|
152
|
+
{:else}
|
|
153
|
+
{@const voronoi = Delaunay.from(points).voronoi([0, 0, boundWidth, boundHeight])}
|
|
154
|
+
{#each points as point, i}
|
|
155
|
+
{@const pathData = voronoi.renderCell(i)}
|
|
156
|
+
<!-- Wait to render Spline until pathData is available to fix path artifacts from injected tweened points in Spline -->
|
|
157
|
+
{#if pathData}
|
|
158
|
+
<CircleClipPath cx={point[0]} cy={point[1]} r={r ?? 0} disabled={disableClip}>
|
|
159
|
+
<Spline
|
|
160
|
+
{pathData}
|
|
161
|
+
class={cls(
|
|
162
|
+
layerClass('voronoi-path'),
|
|
163
|
+
'fill-transparent stroke-transparent',
|
|
164
|
+
classes.path
|
|
165
|
+
)}
|
|
166
|
+
onclick={(e) => onclick?.(e, { data: point.data, point })}
|
|
167
|
+
onpointerenter={(e) => onpointerenter?.(e, { data: point.data, point })}
|
|
168
|
+
onpointermove={(e) => onpointermove?.(e, { data: point.data, point })}
|
|
169
|
+
{onpointerleave}
|
|
170
|
+
onpointerdown={(e) => onpointerdown?.(e, { data: point.data, point })}
|
|
171
|
+
ontouchmove={(e) => {
|
|
172
|
+
// Prevent touch to not interfere with pointer
|
|
173
|
+
e.preventDefault();
|
|
174
|
+
}}
|
|
175
|
+
/>
|
|
176
|
+
</CircleClipPath>
|
|
159
177
|
{/if}
|
|
160
178
|
{/each}
|
|
161
179
|
{/if}
|