layerchart 2.0.0-next.50 → 2.0.0-next.51
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/Axis.svelte +25 -0
- package/dist/components/Axis.svelte.d.ts +10 -0
- package/dist/components/Circle.svelte +82 -59
- package/dist/components/Ellipse.svelte +83 -64
- package/dist/components/GeoRaster.svelte +311 -0
- package/dist/components/GeoRaster.svelte.d.ts +61 -0
- package/dist/components/Grid.svelte +15 -0
- package/dist/components/Grid.svelte.d.ts +5 -0
- package/dist/components/Image.svelte +2 -2
- package/dist/components/Line.svelte +82 -62
- package/dist/components/Points.svelte +2 -2
- package/dist/components/Polygon.svelte +92 -56
- package/dist/components/Rect.svelte +113 -64
- package/dist/components/Rule.svelte +2 -0
- package/dist/components/Sankey.svelte +0 -2
- package/dist/components/Text.svelte +83 -52
- package/dist/components/charts/PieChart.svelte +2 -2
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +2 -0
- package/dist/components/layers/Canvas.svelte +65 -48
- package/dist/components/layers/Canvas.svelte.d.ts +10 -0
- package/dist/contexts/canvas.d.ts +3 -0
- package/dist/server/ContextCapture.svelte +30 -0
- package/dist/server/ContextCapture.svelte.d.ts +8 -0
- package/dist/server/ServerChart.svelte +26 -0
- package/dist/server/ServerChart.svelte.d.ts +11 -0
- package/dist/server/TestBarChart.svelte +35 -0
- package/dist/server/TestBarChart.svelte.d.ts +14 -0
- package/dist/server/TestLineChart.svelte +35 -0
- package/dist/server/TestLineChart.svelte.d.ts +14 -0
- package/dist/server/captureStore.d.ts +8 -0
- package/dist/server/captureStore.js +18 -0
- package/dist/server/index.d.ts +137 -0
- package/dist/server/index.js +141 -0
- package/dist/server/renderChart.ssr.test.d.ts +1 -0
- package/dist/server/renderChart.ssr.test.js +205 -0
- package/dist/server/renderTree.d.ts +8 -0
- package/dist/server/renderTree.js +29 -0
- package/dist/states/__screenshots__/chart.svelte.test.ts/ChartState-geo-projection-skips-markInfo-should-not-derive-x-y-accessors-from-marks-when-geo-projection-is-active-1.png +0 -0
- package/dist/states/__screenshots__/chart.svelte.test.ts/ChartState-geo-projection-skips-markInfo-should-not-derive-x-y-accessors-from-marks-when-geo-projection-is-active-2.png +0 -0
- package/dist/states/chart.svelte.d.ts +5 -1
- package/dist/states/chart.svelte.js +18 -3
- package/dist/states/chart.svelte.test.js +110 -0
- package/dist/states/geo.svelte.d.ts +5 -1
- package/dist/states/geo.svelte.js +80 -68
- package/dist/utils/canvas.js +29 -10
- package/dist/utils/canvas.svelte.test.js +2 -2
- package/dist/utils/motion.svelte.js +14 -0
- package/package.json +7 -1
|
@@ -238,7 +238,12 @@
|
|
|
238
238
|
import { createDataMotionMap } from '../utils/motion.svelte.js';
|
|
239
239
|
import { getStringWidth, truncateText, type TruncateTextOptions } from '../utils/string.js';
|
|
240
240
|
import { getComputedStyles, renderText, type ComputedStylesOptions } from '../utils/canvas.js';
|
|
241
|
-
import {
|
|
241
|
+
import {
|
|
242
|
+
resolveDataProp,
|
|
243
|
+
resolveColorProp,
|
|
244
|
+
resolveGeoDataPair,
|
|
245
|
+
resolveStyleProp,
|
|
246
|
+
} from '../utils/dataProp.js';
|
|
242
247
|
import { getGeoContext } from '../contexts/geo.js';
|
|
243
248
|
import { get } from '@layerstack/utils';
|
|
244
249
|
import { chartDataArray } from '../utils/common.js';
|
|
@@ -294,9 +299,7 @@
|
|
|
294
299
|
const geo = getGeoContext();
|
|
295
300
|
|
|
296
301
|
// Data to iterate over in data mode
|
|
297
|
-
const resolvedData: any[] = $derived(
|
|
298
|
-
dataMode ? (dataProp ?? chartDataArray(chartCtx.data)) : []
|
|
299
|
-
);
|
|
302
|
+
const resolvedData: any[] = $derived(dataMode ? (dataProp ?? chartDataArray(chartCtx.data)) : []);
|
|
300
303
|
|
|
301
304
|
// Resolve position for a data item
|
|
302
305
|
function resolveTextPosition(d: any) {
|
|
@@ -401,7 +404,11 @@
|
|
|
401
404
|
const motionValue = createMotion(
|
|
402
405
|
typeof value === 'number' ? value : 0,
|
|
403
406
|
() => (typeof value === 'number' ? value : 0),
|
|
404
|
-
typeof value === 'number' && motion
|
|
407
|
+
typeof value === 'number' && motion
|
|
408
|
+
? typeof motion === 'object' && 'type' in motion
|
|
409
|
+
? motion
|
|
410
|
+
: undefined
|
|
411
|
+
: undefined
|
|
405
412
|
);
|
|
406
413
|
|
|
407
414
|
// Handle null and convert `\n` strings back to newline characters
|
|
@@ -549,6 +556,13 @@
|
|
|
549
556
|
motion
|
|
550
557
|
);
|
|
551
558
|
|
|
559
|
+
const staticFill = $derived(typeof fill === 'string' ? fill : undefined);
|
|
560
|
+
const staticFillOpacity = $derived(typeof fillOpacity === 'number' ? fillOpacity : undefined);
|
|
561
|
+
const staticStroke = $derived(typeof stroke === 'string' ? stroke : undefined);
|
|
562
|
+
const staticStrokeWidth = $derived(typeof strokeWidth === 'number' ? strokeWidth : undefined);
|
|
563
|
+
const staticOpacity = $derived(typeof opacity === 'number' ? opacity : undefined);
|
|
564
|
+
const staticClassName = $derived(typeof className === 'string' ? className : undefined);
|
|
565
|
+
|
|
552
566
|
function render(
|
|
553
567
|
ctx: CanvasRenderingContext2D,
|
|
554
568
|
styleOverrides: ComputedStylesOptions | undefined
|
|
@@ -562,20 +576,33 @@
|
|
|
562
576
|
itemClass?: string | undefined
|
|
563
577
|
) {
|
|
564
578
|
return styleOverrides
|
|
565
|
-
? merge(
|
|
579
|
+
? merge(
|
|
580
|
+
{
|
|
581
|
+
styles: {
|
|
582
|
+
strokeWidth:
|
|
583
|
+
itemStrokeWidth ?? (typeof strokeWidth === 'number' ? strokeWidth : undefined),
|
|
584
|
+
},
|
|
585
|
+
},
|
|
586
|
+
styleOverrides
|
|
587
|
+
)
|
|
566
588
|
: {
|
|
567
589
|
styles: {
|
|
568
590
|
fill: itemFill ?? fill,
|
|
569
|
-
fillOpacity:
|
|
591
|
+
fillOpacity:
|
|
592
|
+
itemFillOpacity ?? (typeof fillOpacity === 'number' ? fillOpacity : undefined),
|
|
570
593
|
stroke: itemStroke ?? stroke,
|
|
571
|
-
strokeWidth:
|
|
594
|
+
strokeWidth:
|
|
595
|
+
itemStrokeWidth ?? (typeof strokeWidth === 'number' ? strokeWidth : undefined),
|
|
572
596
|
opacity: itemOpacity ?? (typeof opacity === 'number' ? opacity : undefined),
|
|
573
597
|
paintOrder: 'stroke',
|
|
574
598
|
// Only include textAnchor in constantStyles when explicitly non-default,
|
|
575
599
|
// so that CSS class-based text-anchor (e.g. [text-anchor:middle]) can take effect
|
|
576
600
|
...(textAnchor !== 'start' ? { textAnchor } : {}),
|
|
577
601
|
},
|
|
578
|
-
classes: cls(
|
|
602
|
+
classes: cls(
|
|
603
|
+
'lc-text',
|
|
604
|
+
itemClass ?? (typeof className === 'string' ? className : undefined)
|
|
605
|
+
),
|
|
579
606
|
style: restProps.style as string | undefined,
|
|
580
607
|
};
|
|
581
608
|
}
|
|
@@ -584,8 +611,7 @@
|
|
|
584
611
|
const baseStyles = getTextStyles();
|
|
585
612
|
const computedStyles = getComputedStyles(ctx.canvas, baseStyles);
|
|
586
613
|
ctx.font = `${computedStyles.fontSize} ${computedStyles.fontFamily}`;
|
|
587
|
-
const textAlign =
|
|
588
|
-
textAnchor === 'middle' ? 'center' : textAnchor === 'end' ? 'end' : 'start';
|
|
614
|
+
const textAlign = textAnchor === 'middle' ? 'center' : textAnchor === 'end' ? 'end' : 'start';
|
|
589
615
|
ctx.textAlign = textAlign;
|
|
590
616
|
|
|
591
617
|
for (const item of resolvedItems) {
|
|
@@ -596,7 +622,14 @@
|
|
|
596
622
|
const resolvedStrokeWidth = resolveStyleProp(strokeWidth, item.d);
|
|
597
623
|
const resolvedOpacity = resolveStyleProp(opacity, item.d);
|
|
598
624
|
const resolvedClass = resolveStyleProp(className, item.d);
|
|
599
|
-
const itemStyles = getTextStyles(
|
|
625
|
+
const itemStyles = getTextStyles(
|
|
626
|
+
resolvedFill,
|
|
627
|
+
resolvedStroke,
|
|
628
|
+
resolvedFillOpacity,
|
|
629
|
+
resolvedStrokeWidth,
|
|
630
|
+
resolvedOpacity,
|
|
631
|
+
resolvedClass
|
|
632
|
+
);
|
|
600
633
|
ctx.save();
|
|
601
634
|
if (rotate !== undefined) {
|
|
602
635
|
const radians = degreesToRadians(rotate);
|
|
@@ -634,8 +667,7 @@
|
|
|
634
667
|
|
|
635
668
|
ctx.font = `${computedStyles.fontSize} ${computedStyles.fontFamily}`;
|
|
636
669
|
|
|
637
|
-
const textAlign =
|
|
638
|
-
textAnchor === 'middle' ? 'center' : textAnchor === 'end' ? 'end' : 'start';
|
|
670
|
+
const textAlign = textAnchor === 'middle' ? 'center' : textAnchor === 'end' ? 'end' : 'start';
|
|
639
671
|
ctx.textAlign = textAlign;
|
|
640
672
|
|
|
641
673
|
for (let index = 0; index < wordsByLines.length; index++) {
|
|
@@ -668,26 +700,29 @@
|
|
|
668
700
|
color: typeof fill === 'string' ? fill : undefined,
|
|
669
701
|
};
|
|
670
702
|
},
|
|
671
|
-
canvasRender:
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
703
|
+
canvasRender:
|
|
704
|
+
layerCtx === 'canvas'
|
|
705
|
+
? {
|
|
706
|
+
render,
|
|
707
|
+
deps: () => [
|
|
708
|
+
dataMode,
|
|
709
|
+
dataMode ? resolvedItems : null,
|
|
710
|
+
value,
|
|
711
|
+
motionX.current,
|
|
712
|
+
motionY.current,
|
|
713
|
+
fillKey!.current,
|
|
714
|
+
strokeKey!.current,
|
|
715
|
+
strokeWidth,
|
|
716
|
+
opacity,
|
|
717
|
+
className,
|
|
718
|
+
truncateConfig,
|
|
719
|
+
rotate,
|
|
720
|
+
lineHeight,
|
|
721
|
+
textAnchor,
|
|
722
|
+
verticalAnchor,
|
|
723
|
+
],
|
|
724
|
+
}
|
|
725
|
+
: undefined,
|
|
691
726
|
});
|
|
692
727
|
</script>
|
|
693
728
|
|
|
@@ -717,11 +752,7 @@
|
|
|
717
752
|
opacity={resolvedOpacity}
|
|
718
753
|
class={['lc-text', resolvedClass]}
|
|
719
754
|
>
|
|
720
|
-
<tspan
|
|
721
|
-
x={item.x}
|
|
722
|
-
dy={dataModeStartDy}
|
|
723
|
-
class="lc-text-tspan"
|
|
724
|
-
>
|
|
755
|
+
<tspan x={item.x} dy={dataModeStartDy} class="lc-text-tspan">
|
|
725
756
|
{text}
|
|
726
757
|
</tspan>
|
|
727
758
|
</text>
|
|
@@ -741,13 +772,13 @@
|
|
|
741
772
|
bind:this={ref}
|
|
742
773
|
{dy}
|
|
743
774
|
{...restProps}
|
|
744
|
-
fill={
|
|
745
|
-
fill-opacity={
|
|
746
|
-
stroke={
|
|
747
|
-
stroke-width={
|
|
748
|
-
opacity={
|
|
775
|
+
fill={staticFill}
|
|
776
|
+
fill-opacity={staticFillOpacity}
|
|
777
|
+
stroke={staticStroke}
|
|
778
|
+
stroke-width={staticStrokeWidth}
|
|
779
|
+
opacity={staticOpacity}
|
|
749
780
|
transform={transformProp}
|
|
750
|
-
class={['lc-text',
|
|
781
|
+
class={['lc-text', staticClassName]}
|
|
751
782
|
>
|
|
752
783
|
<textPath
|
|
753
784
|
style="text-anchor: {textAnchor};"
|
|
@@ -768,12 +799,12 @@
|
|
|
768
799
|
text-anchor={textAnchor}
|
|
769
800
|
dominant-baseline={dominantBaseline}
|
|
770
801
|
{...restProps}
|
|
771
|
-
fill={
|
|
772
|
-
fill-opacity={
|
|
773
|
-
stroke={
|
|
774
|
-
stroke-width={
|
|
775
|
-
opacity={
|
|
776
|
-
class={['lc-text',
|
|
802
|
+
fill={staticFill}
|
|
803
|
+
fill-opacity={staticFillOpacity}
|
|
804
|
+
stroke={staticStroke}
|
|
805
|
+
stroke-width={staticStrokeWidth}
|
|
806
|
+
opacity={staticOpacity}
|
|
807
|
+
class={['lc-text', staticClassName]}
|
|
777
808
|
>
|
|
778
809
|
{#each wordsByLines as line, index}
|
|
779
810
|
<tspan
|
|
@@ -831,7 +862,7 @@
|
|
|
831
862
|
{textAnchor === 'middle' ? 'center' : textAnchor === 'end' ? 'right' : 'left'}"
|
|
832
863
|
style:white-space="pre-wrap"
|
|
833
864
|
style:line-height={lineHeight}
|
|
834
|
-
class={['lc-text',
|
|
865
|
+
class={['lc-text', staticClassName]}
|
|
835
866
|
>
|
|
836
867
|
{textValue}
|
|
837
868
|
</div>
|
|
@@ -231,7 +231,7 @@
|
|
|
231
231
|
// Reading context.series.allSeriesData here would create a derived_references_self cycle:
|
|
232
232
|
// SeriesState.#series → ChartState.props → data={visibleData} → chartData → context.series.allSeriesData → #series
|
|
233
233
|
const chartData = $derived.by(() => {
|
|
234
|
-
const seriesData = series.flatMap((s) => s.data ?? []);
|
|
234
|
+
const seriesData = series.flatMap((s) => ('data' in s ? s.data : undefined) ?? []);
|
|
235
235
|
return (seriesData.length > 0 ? seriesData : chartDataArray(data)) as Array<TData>;
|
|
236
236
|
});
|
|
237
237
|
|
|
@@ -245,7 +245,7 @@
|
|
|
245
245
|
|
|
246
246
|
// Compute series colors locally to avoid derived_references_self cycle through context.series.allSeriesColors
|
|
247
247
|
const allSeriesColors = $derived(
|
|
248
|
-
series.map((s) => s.color).filter((c) => c != null) as string[]
|
|
248
|
+
series.map((s) => ('color' in s ? s.color : undefined)).filter((c) => c != null) as string[]
|
|
249
249
|
);
|
|
250
250
|
|
|
251
251
|
// Custom tickFormat for PieChart legends - uses data labels instead of series labels
|
|
@@ -68,6 +68,8 @@ export { default as GeoPath } from './GeoPath.svelte';
|
|
|
68
68
|
export * from './GeoPath.svelte';
|
|
69
69
|
export { default as GeoPoint } from './GeoPoint.svelte';
|
|
70
70
|
export * from './GeoPoint.svelte';
|
|
71
|
+
export { default as GeoRaster } from './GeoRaster.svelte';
|
|
72
|
+
export * from './GeoRaster.svelte';
|
|
71
73
|
export { default as GeoSpline } from './GeoSpline.svelte';
|
|
72
74
|
export * from './GeoSpline.svelte';
|
|
73
75
|
export { default as GeoTile } from './GeoTile.svelte';
|
package/dist/components/index.js
CHANGED
|
@@ -68,6 +68,8 @@ export { default as GeoPath } from './GeoPath.svelte';
|
|
|
68
68
|
export * from './GeoPath.svelte';
|
|
69
69
|
export { default as GeoPoint } from './GeoPoint.svelte';
|
|
70
70
|
export * from './GeoPoint.svelte';
|
|
71
|
+
export { default as GeoRaster } from './GeoRaster.svelte';
|
|
72
|
+
export * from './GeoRaster.svelte';
|
|
71
73
|
export { default as GeoSpline } from './GeoSpline.svelte';
|
|
72
74
|
export * from './GeoSpline.svelte';
|
|
73
75
|
export { default as GeoTile } from './GeoTile.svelte';
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
2
|
import type { ComponentNode } from '../../contexts/chart.js';
|
|
3
|
+
import type { ChartState } from '../../states/chart.svelte.js';
|
|
4
|
+
|
|
5
|
+
type SSRCaptureTarget = {
|
|
6
|
+
chartState?: ChartState<any, any, any>;
|
|
7
|
+
rootNode?: ComponentNode;
|
|
8
|
+
};
|
|
3
9
|
|
|
4
10
|
export type CanvasPropsWithoutHTML = {
|
|
5
11
|
/**
|
|
@@ -80,6 +86,12 @@
|
|
|
80
86
|
*/
|
|
81
87
|
debug?: boolean;
|
|
82
88
|
|
|
89
|
+
/** @internal Server-side capture target used by layerchart/server. */
|
|
90
|
+
ssrCapture?: SSRCaptureTarget;
|
|
91
|
+
|
|
92
|
+
/** @internal Server-side capture callback used by layerchart/server. */
|
|
93
|
+
ssrCaptureCallback?: (data: SSRCaptureTarget) => void;
|
|
94
|
+
|
|
83
95
|
children?: Snippet<
|
|
84
96
|
[{ ref: HTMLCanvasElement; canvasContext: CanvasRenderingContext2D | undefined }]
|
|
85
97
|
>;
|
|
@@ -87,7 +99,6 @@
|
|
|
87
99
|
|
|
88
100
|
export type CanvasProps = CanvasPropsWithoutHTML &
|
|
89
101
|
Without<HTMLCanvasAttributes, CanvasPropsWithoutHTML>;
|
|
90
|
-
|
|
91
102
|
</script>
|
|
92
103
|
|
|
93
104
|
<script lang="ts">
|
|
@@ -107,10 +118,13 @@
|
|
|
107
118
|
type CanvasContextValue,
|
|
108
119
|
type ComponentRender,
|
|
109
120
|
} from '../../contexts/canvas.js';
|
|
121
|
+
import { renderTree } from '../../server/renderTree.js';
|
|
110
122
|
|
|
111
123
|
let {
|
|
112
124
|
ref: refProp = $bindable(),
|
|
113
125
|
canvasContext: canvasContextProp = $bindable(),
|
|
126
|
+
ssrCapture,
|
|
127
|
+
ssrCaptureCallback,
|
|
114
128
|
willReadFrequently = false,
|
|
115
129
|
debug = false,
|
|
116
130
|
zIndex = 0,
|
|
@@ -183,9 +197,10 @@
|
|
|
183
197
|
// Bubble up to ancestor groups
|
|
184
198
|
let ancestor = node?.parent;
|
|
185
199
|
while (ancestor) {
|
|
186
|
-
const handler =
|
|
187
|
-
|
|
188
|
-
|
|
200
|
+
const handler =
|
|
201
|
+
ancestor.kind === 'group'
|
|
202
|
+
? (ancestor.canvasRender?.events?.[eventName] as ((e: Event) => void) | null | undefined)
|
|
203
|
+
: undefined;
|
|
189
204
|
handler?.(e);
|
|
190
205
|
ancestor = ancestor.parent;
|
|
191
206
|
}
|
|
@@ -225,21 +240,23 @@
|
|
|
225
240
|
*/
|
|
226
241
|
|
|
227
242
|
// Invalidate/redraw if color scheme changes, either via browser `prefers-color-scheme` (including emulation) or by changing `<html class="dark">` or `<html data-theme="...">`
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
(
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
+
if (typeof window !== 'undefined') {
|
|
244
|
+
const { dark } = new MediaQueryPresets();
|
|
245
|
+
watch(
|
|
246
|
+
() => dark.current,
|
|
247
|
+
() => {
|
|
248
|
+
canvasContext.invalidate();
|
|
249
|
+
}
|
|
250
|
+
);
|
|
251
|
+
useMutationObserver(
|
|
252
|
+
() => document.documentElement,
|
|
253
|
+
() => canvasContext.invalidate(),
|
|
254
|
+
{
|
|
255
|
+
attributes: true,
|
|
256
|
+
attributeFilter: ['class', 'data-theme'],
|
|
257
|
+
}
|
|
258
|
+
);
|
|
259
|
+
}
|
|
243
260
|
|
|
244
261
|
onMount(() => {
|
|
245
262
|
context = ref?.getContext('2d', { willReadFrequently }) as CanvasRenderingContext2D;
|
|
@@ -310,33 +327,6 @@
|
|
|
310
327
|
pendingInvalidation = false;
|
|
311
328
|
}
|
|
312
329
|
|
|
313
|
-
/**
|
|
314
|
-
* Recursively render the component tree.
|
|
315
|
-
* Group nodes: save → render (translate/opacity) → recurse children → restore
|
|
316
|
-
* Leaf nodes: save → render → restore
|
|
317
|
-
*/
|
|
318
|
-
function renderTree(canvasCtx: CanvasRenderingContext2D, node: ComponentNode) {
|
|
319
|
-
if (node.kind === 'group' && node.canvasRender) {
|
|
320
|
-
// Group: save state, apply transform, render children, restore
|
|
321
|
-
canvasCtx.save();
|
|
322
|
-
node.canvasRender.render(canvasCtx);
|
|
323
|
-
for (const child of node.children) {
|
|
324
|
-
renderTree(canvasCtx, child);
|
|
325
|
-
}
|
|
326
|
-
canvasCtx.restore();
|
|
327
|
-
} else if (node.canvasRender) {
|
|
328
|
-
// Leaf mark: save, render, restore
|
|
329
|
-
canvasCtx.save();
|
|
330
|
-
node.canvasRender.render(canvasCtx);
|
|
331
|
-
canvasCtx.restore();
|
|
332
|
-
} else {
|
|
333
|
-
// Non-rendering node (e.g. root, composite-mark): just recurse children
|
|
334
|
-
for (const child of node.children) {
|
|
335
|
-
renderTree(canvasCtx, child);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
330
|
function nodeHasEvents(node: ComponentNode) {
|
|
341
331
|
return node.canvasRender?.events && Object.values(node.canvasRender.events).some((d) => d);
|
|
342
332
|
}
|
|
@@ -345,7 +335,11 @@
|
|
|
345
335
|
* Recursively render the hit canvas tree for pointer event detection.
|
|
346
336
|
* Renders components that have event handlers (or whose ancestor group does), using unique colors.
|
|
347
337
|
*/
|
|
348
|
-
function renderHitTree(
|
|
338
|
+
function renderHitTree(
|
|
339
|
+
hitCtx: CanvasRenderingContext2D,
|
|
340
|
+
node: ComponentNode,
|
|
341
|
+
ancestorHasEvents = false
|
|
342
|
+
) {
|
|
349
343
|
if (node.kind === 'group' && node.canvasRender) {
|
|
350
344
|
const groupHasEvents = ancestorHasEvents || nodeHasEvents(node);
|
|
351
345
|
// Group: apply transform, recurse children (scoped by save/restore)
|
|
@@ -383,11 +377,33 @@
|
|
|
383
377
|
|
|
384
378
|
function invalidate() {
|
|
385
379
|
if (pendingInvalidation) return;
|
|
380
|
+
if (typeof requestAnimationFrame === 'undefined') return;
|
|
386
381
|
pendingInvalidation = true;
|
|
387
382
|
frameId = requestAnimationFrame(update);
|
|
388
383
|
}
|
|
389
384
|
|
|
390
|
-
|
|
385
|
+
function getRootNode() {
|
|
386
|
+
return rootNode;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return { register, invalidate, getRootNode };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function captureSSR() {
|
|
393
|
+
if (typeof window !== 'undefined') return '';
|
|
394
|
+
if (!ssrCapture && !ssrCaptureCallback) return '';
|
|
395
|
+
|
|
396
|
+
const captured: SSRCaptureTarget = {
|
|
397
|
+
chartState: ctx,
|
|
398
|
+
rootNode,
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
if (ssrCapture) {
|
|
402
|
+
Object.assign(ssrCapture, captured);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
ssrCaptureCallback?.(captured);
|
|
406
|
+
return '';
|
|
391
407
|
}
|
|
392
408
|
|
|
393
409
|
const canvasContext = createCanvasContext();
|
|
@@ -457,6 +473,7 @@
|
|
|
457
473
|
<canvas bind:this={hitCanvasElement} class="lc-hit-canvas" class:debug></canvas>
|
|
458
474
|
|
|
459
475
|
{@render children?.({ ref, canvasContext: context })}
|
|
476
|
+
{captureSSR()}
|
|
460
477
|
|
|
461
478
|
<style>
|
|
462
479
|
@layer base {
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import type { ComponentNode } from '../../contexts/chart.js';
|
|
2
|
+
import type { ChartState } from '../../states/chart.svelte.js';
|
|
3
|
+
type SSRCaptureTarget = {
|
|
4
|
+
chartState?: ChartState<any, any, any>;
|
|
5
|
+
rootNode?: ComponentNode;
|
|
6
|
+
};
|
|
1
7
|
export type CanvasPropsWithoutHTML = {
|
|
2
8
|
/**
|
|
3
9
|
* The `<canvas>` tag. Useful for bindings.
|
|
@@ -67,6 +73,10 @@ export type CanvasPropsWithoutHTML = {
|
|
|
67
73
|
* @default false
|
|
68
74
|
*/
|
|
69
75
|
debug?: boolean;
|
|
76
|
+
/** @internal Server-side capture target used by layerchart/server. */
|
|
77
|
+
ssrCapture?: SSRCaptureTarget;
|
|
78
|
+
/** @internal Server-side capture callback used by layerchart/server. */
|
|
79
|
+
ssrCaptureCallback?: (data: SSRCaptureTarget) => void;
|
|
70
80
|
children?: Snippet<[
|
|
71
81
|
{
|
|
72
82
|
ref: HTMLCanvasElement;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { MouseEventHandler, PointerEventHandler, TouchEventHandler } from 'svelte/elements';
|
|
2
2
|
import type { ComputedStylesOptions } from '../utils/canvas.js';
|
|
3
|
+
import type { ComponentNode } from '../states/chart.svelte.js';
|
|
3
4
|
export type ComponentRender<T extends Element = Element> = {
|
|
4
5
|
render: (ctx: CanvasRenderingContext2D, styleOverrides?: ComputedStylesOptions) => any;
|
|
5
6
|
events?: {
|
|
@@ -26,6 +27,8 @@ export type CanvasContextValue = {
|
|
|
26
27
|
*/
|
|
27
28
|
register<T extends Element>(component: ComponentRender<T>): () => void;
|
|
28
29
|
invalidate(): void;
|
|
30
|
+
/** Get the root ComponentNode of the canvas render tree. Used for server-side rendering. */
|
|
31
|
+
getRootNode?: () => ComponentNode;
|
|
29
32
|
};
|
|
30
33
|
export declare function getCanvasContext(): CanvasContextValue;
|
|
31
34
|
export declare function setCanvasContext(context: CanvasContextValue): CanvasContextValue;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getChartContext } from '../contexts/chart.js';
|
|
3
|
+
import { getCanvasContext } from '../contexts/canvas.js';
|
|
4
|
+
import { setSSRCapture, type CaptureTarget } from './captureStore.js';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
capture,
|
|
8
|
+
onCapture,
|
|
9
|
+
}: {
|
|
10
|
+
capture?: CaptureTarget;
|
|
11
|
+
onCapture?: (data: CaptureTarget) => void;
|
|
12
|
+
} = $props();
|
|
13
|
+
|
|
14
|
+
const chartState = getChartContext();
|
|
15
|
+
const canvasCtx = getCanvasContext();
|
|
16
|
+
|
|
17
|
+
const captured = {
|
|
18
|
+
chartState,
|
|
19
|
+
rootNode: canvasCtx.getRootNode?.(),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
if (typeof window === 'undefined') {
|
|
23
|
+
if (capture) {
|
|
24
|
+
Object.assign(capture, captured);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
setSSRCapture(captured);
|
|
28
|
+
onCapture?.(captured);
|
|
29
|
+
}
|
|
30
|
+
</script>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type CaptureTarget } from './captureStore.js';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
capture?: CaptureTarget;
|
|
4
|
+
onCapture?: (data: CaptureTarget) => void;
|
|
5
|
+
};
|
|
6
|
+
declare const ContextCapture: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type ContextCapture = ReturnType<typeof ContextCapture>;
|
|
8
|
+
export default ContextCapture;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import Chart from '../components/Chart.svelte';
|
|
4
|
+
import type { ChartProps } from '../components/Chart.svelte';
|
|
5
|
+
import Canvas from '../components/layers/Canvas.svelte';
|
|
6
|
+
import type { CaptureTarget } from './captureStore.js';
|
|
7
|
+
|
|
8
|
+
let {
|
|
9
|
+
children,
|
|
10
|
+
capture,
|
|
11
|
+
onCapture,
|
|
12
|
+
...chartProps
|
|
13
|
+
}: {
|
|
14
|
+
children?: Snippet;
|
|
15
|
+
capture?: CaptureTarget;
|
|
16
|
+
onCapture?: (data: CaptureTarget) => void;
|
|
17
|
+
} & Omit<ChartProps<any>, 'children'> = $props();
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<Chart ssr={true} {...chartProps}>
|
|
21
|
+
<Canvas ssrCapture={capture} ssrCaptureCallback={onCapture}>
|
|
22
|
+
{#if children}
|
|
23
|
+
{@render children()}
|
|
24
|
+
{/if}
|
|
25
|
+
</Canvas>
|
|
26
|
+
</Chart>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { ChartProps } from '../components/Chart.svelte';
|
|
3
|
+
import type { CaptureTarget } from './captureStore.js';
|
|
4
|
+
type $$ComponentProps = {
|
|
5
|
+
children?: Snippet;
|
|
6
|
+
capture?: CaptureTarget;
|
|
7
|
+
onCapture?: (data: CaptureTarget) => void;
|
|
8
|
+
} & Omit<ChartProps<any>, 'children'>;
|
|
9
|
+
declare const ServerChart: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
10
|
+
type ServerChart = ReturnType<typeof ServerChart>;
|
|
11
|
+
export default ServerChart;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { scaleBand } from 'd3-scale';
|
|
3
|
+
import ServerChart from './ServerChart.svelte';
|
|
4
|
+
import type { CaptureTarget } from './captureStore.js';
|
|
5
|
+
import Bars from '../components/Bars.svelte';
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
data,
|
|
9
|
+
width,
|
|
10
|
+
height,
|
|
11
|
+
capture,
|
|
12
|
+
onCapture
|
|
13
|
+
}: {
|
|
14
|
+
data: { category: string; value: number }[];
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
capture?: CaptureTarget;
|
|
18
|
+
onCapture?: (data: CaptureTarget) => void;
|
|
19
|
+
} = $props();
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<ServerChart
|
|
23
|
+
{capture}
|
|
24
|
+
{onCapture}
|
|
25
|
+
{width}
|
|
26
|
+
{height}
|
|
27
|
+
{data}
|
|
28
|
+
x="category"
|
|
29
|
+
xScale={scaleBand().paddingInner(0.2).paddingOuter(0.1)}
|
|
30
|
+
y="value"
|
|
31
|
+
yDomain={[0, null]}
|
|
32
|
+
padding={{ top: 20, right: 20, bottom: 30, left: 40 }}
|
|
33
|
+
>
|
|
34
|
+
<Bars fill="rgb(59, 130, 246)" radius={4} />
|
|
35
|
+
</ServerChart>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CaptureTarget } from './captureStore.js';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
data: {
|
|
4
|
+
category: string;
|
|
5
|
+
value: number;
|
|
6
|
+
}[];
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
capture?: CaptureTarget;
|
|
10
|
+
onCapture?: (data: CaptureTarget) => void;
|
|
11
|
+
};
|
|
12
|
+
declare const TestBarChart: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
13
|
+
type TestBarChart = ReturnType<typeof TestBarChart>;
|
|
14
|
+
export default TestBarChart;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import ServerChart from './ServerChart.svelte';
|
|
3
|
+
import type { CaptureTarget } from './captureStore.js';
|
|
4
|
+
import Area from '../components/Area.svelte';
|
|
5
|
+
import Spline from '../components/Spline.svelte';
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
data,
|
|
9
|
+
width,
|
|
10
|
+
height,
|
|
11
|
+
capture,
|
|
12
|
+
onCapture
|
|
13
|
+
}: {
|
|
14
|
+
data: { date: number; value: number }[];
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
capture?: CaptureTarget;
|
|
18
|
+
onCapture?: (data: CaptureTarget) => void;
|
|
19
|
+
} = $props();
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<ServerChart
|
|
23
|
+
{capture}
|
|
24
|
+
{onCapture}
|
|
25
|
+
{width}
|
|
26
|
+
{height}
|
|
27
|
+
{data}
|
|
28
|
+
x="date"
|
|
29
|
+
y="value"
|
|
30
|
+
yDomain={[0, null]}
|
|
31
|
+
padding={{ top: 20, right: 20, bottom: 20, left: 20 }}
|
|
32
|
+
>
|
|
33
|
+
<Area fill="rgba(59, 130, 246, 0.15)" stroke="none" />
|
|
34
|
+
<Spline stroke="rgb(59, 130, 246)" strokeWidth={2} />
|
|
35
|
+
</ServerChart>
|