layerchart 2.0.0-next.50 → 2.0.0-next.52
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/Arc.svelte +12 -4
- package/dist/components/Arc.svelte.d.ts +4 -0
- package/dist/components/ArcLabel.svelte +259 -0
- package/dist/components/ArcLabel.svelte.d.ts +73 -0
- package/dist/components/ArcLabel.svelte.test.d.ts +1 -0
- package/dist/components/ArcLabel.svelte.test.js +235 -0
- 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/CircleLegend.svelte +389 -0
- package/dist/components/CircleLegend.svelte.d.ts +114 -0
- package/dist/components/Ellipse.svelte +83 -64
- package/dist/components/GeoLegend.svelte +404 -0
- package/dist/components/GeoLegend.svelte.d.ts +106 -0
- 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/Labels.svelte +46 -11
- package/dist/components/Labels.svelte.d.ts +7 -3
- package/dist/components/Legend.svelte +58 -3
- package/dist/components/Legend.svelte.d.ts +7 -0
- 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/__screenshots__/ArcLabel.svelte.test.ts/ArcLabel-defaults-placement-to-centroid--x-y-at-the-centroid--middle-anchors--1.png +0 -0
- package/dist/components/__screenshots__/ArcLabel.svelte.test.ts/ArcLabel-defaults-placement-to-centroid--x-y-at-the-centroid--middle-anchors--2.png +0 -0
- package/dist/components/charts/ArcChart.svelte +39 -2
- package/dist/components/charts/ArcChart.svelte.d.ts +12 -1
- package/dist/components/charts/PieChart.svelte +40 -2
- package/dist/components/charts/PieChart.svelte.d.ts +10 -0
- package/dist/components/index.d.ts +8 -0
- package/dist/components/index.js +8 -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/arcText.svelte.d.ts +7 -1
- package/dist/utils/arcText.svelte.js +8 -4
- 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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
2
|
import { flushSync } from 'svelte';
|
|
3
3
|
import { scaleBand } from 'd3-scale';
|
|
4
|
+
import { geoAlbersUsa } from 'd3-geo';
|
|
4
5
|
import { timeDay } from 'd3-time';
|
|
5
6
|
import { ChartState } from './chart.svelte.js';
|
|
6
7
|
import { isScaleBand, isScaleTime } from '../utils/scales.svelte.js';
|
|
@@ -527,6 +528,115 @@ describe('ChartState mark registration', () => {
|
|
|
527
528
|
}
|
|
528
529
|
});
|
|
529
530
|
});
|
|
531
|
+
describe('ChartState geo projection skips markInfo', () => {
|
|
532
|
+
const geoData = [
|
|
533
|
+
{ name: 'New York', longitude: -74.006, latitude: 40.7128 },
|
|
534
|
+
{ name: 'Los Angeles', longitude: -118.2437, latitude: 34.0522 },
|
|
535
|
+
{ name: 'Chicago', longitude: -87.6298, latitude: 41.8781 },
|
|
536
|
+
];
|
|
537
|
+
it('should not create implicit series from marks when geo projection is active', () => {
|
|
538
|
+
const { state, cleanup } = createChartState({
|
|
539
|
+
data: geoData,
|
|
540
|
+
x: 'longitude',
|
|
541
|
+
y: 'latitude',
|
|
542
|
+
geo: { projection: geoAlbersUsa },
|
|
543
|
+
});
|
|
544
|
+
try {
|
|
545
|
+
// Register a mark with its own data (like a tooltip highlight Circle)
|
|
546
|
+
state.registerMark({ data: [geoData[0]], x: 'longitude', y: 'latitude' });
|
|
547
|
+
flushSync();
|
|
548
|
+
// Should remain default series — mark should not create implicit "latitude" series
|
|
549
|
+
expect(state.seriesState.isDefaultSeries).toBe(true);
|
|
550
|
+
}
|
|
551
|
+
finally {
|
|
552
|
+
cleanup();
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
it('should not add mark data to flatData when geo projection is active', () => {
|
|
556
|
+
const { state, cleanup } = createChartState({
|
|
557
|
+
data: geoData,
|
|
558
|
+
x: 'longitude',
|
|
559
|
+
y: 'latitude',
|
|
560
|
+
geo: { projection: geoAlbersUsa },
|
|
561
|
+
});
|
|
562
|
+
try {
|
|
563
|
+
state.registerMark({ data: [geoData[0]], x: 'longitude', y: 'latitude' });
|
|
564
|
+
flushSync();
|
|
565
|
+
// flatData should only contain chart data, not the mark's extra data
|
|
566
|
+
expect(state.flatData).toHaveLength(3);
|
|
567
|
+
expect(state.flatData).toBe(geoData);
|
|
568
|
+
}
|
|
569
|
+
finally {
|
|
570
|
+
cleanup();
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
it('should not derive x/y accessors from marks when geo projection is active', () => {
|
|
574
|
+
// Chart with geo but no explicit x/y — marks should not fill in the accessors
|
|
575
|
+
const { state: stateWithGeo, cleanup: cleanupGeo } = createChartState({
|
|
576
|
+
data: geoData,
|
|
577
|
+
geo: { projection: geoAlbersUsa },
|
|
578
|
+
});
|
|
579
|
+
const { state: stateWithoutGeo, cleanup: cleanupNoGeo } = createChartState({
|
|
580
|
+
data: geoData,
|
|
581
|
+
});
|
|
582
|
+
try {
|
|
583
|
+
// Both start with null x accessor (no x prop set)
|
|
584
|
+
expect(stateWithGeo.x).toBeNull();
|
|
585
|
+
expect(stateWithoutGeo.x).toBeNull();
|
|
586
|
+
stateWithGeo.registerMark({ x: 'longitude', y: 'latitude' });
|
|
587
|
+
stateWithoutGeo.registerMark({ x: 'longitude', y: 'latitude' });
|
|
588
|
+
flushSync();
|
|
589
|
+
// Without geo: mark should derive x accessor
|
|
590
|
+
expect(stateWithoutGeo.x).not.toBeNull();
|
|
591
|
+
expect(stateWithoutGeo.x(geoData[0])).toBe(geoData[0].longitude);
|
|
592
|
+
// With geo: mark should NOT derive x accessor
|
|
593
|
+
expect(stateWithGeo.x).toBeNull();
|
|
594
|
+
}
|
|
595
|
+
finally {
|
|
596
|
+
cleanupGeo();
|
|
597
|
+
cleanupNoGeo();
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
it('should preserve seriesKey/color/label from marks in geo mode for legends', () => {
|
|
601
|
+
const { state, cleanup } = createChartState({
|
|
602
|
+
data: geoData,
|
|
603
|
+
x: 'longitude',
|
|
604
|
+
y: 'latitude',
|
|
605
|
+
geo: { projection: geoAlbersUsa },
|
|
606
|
+
});
|
|
607
|
+
try {
|
|
608
|
+
state.registerMark({ seriesKey: 'earthquakes', color: 'red', label: 'Earthquakes' });
|
|
609
|
+
state.registerMark({ seriesKey: 'volcanos', color: 'orange', label: 'Volcanos' });
|
|
610
|
+
flushSync();
|
|
611
|
+
// seriesKey/color/label should still create implicit series for legends
|
|
612
|
+
expect(state.seriesState.isDefaultSeries).toBe(false);
|
|
613
|
+
expect(state.seriesState.series).toHaveLength(2);
|
|
614
|
+
expect(state.seriesState.series[0]).toMatchObject({ key: 'earthquakes', color: 'red', label: 'Earthquakes' });
|
|
615
|
+
expect(state.seriesState.series[1]).toMatchObject({ key: 'volcanos', color: 'orange', label: 'Volcanos' });
|
|
616
|
+
// But flatData should not include extra mark data
|
|
617
|
+
expect(state.flatData).toHaveLength(3);
|
|
618
|
+
}
|
|
619
|
+
finally {
|
|
620
|
+
cleanup();
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
it('should still process marks normally without geo projection', () => {
|
|
624
|
+
const { state, cleanup } = createChartState({
|
|
625
|
+
data: geoData,
|
|
626
|
+
x: 'name',
|
|
627
|
+
});
|
|
628
|
+
try {
|
|
629
|
+
state.registerMark({ y: 'latitude', color: 'blue' });
|
|
630
|
+
flushSync();
|
|
631
|
+
// Without geo, marks should create implicit series as normal
|
|
632
|
+
expect(state.seriesState.isDefaultSeries).toBe(false);
|
|
633
|
+
expect(state.seriesState.series[0].key).toBe('latitude');
|
|
634
|
+
}
|
|
635
|
+
finally {
|
|
636
|
+
cleanup();
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
});
|
|
530
640
|
describe('ChartState implicit series domain update on visibility toggle', () => {
|
|
531
641
|
it('should update y domain when hiding an implicit series', () => {
|
|
532
642
|
const data = [
|
|
@@ -30,6 +30,7 @@ export type GeoStateProps = {
|
|
|
30
30
|
};
|
|
31
31
|
export declare class GeoState {
|
|
32
32
|
private _propsGetter;
|
|
33
|
+
private _dimensionsGetter?;
|
|
33
34
|
props: GeoStateProps;
|
|
34
35
|
chartWidth: number;
|
|
35
36
|
chartHeight: number;
|
|
@@ -40,6 +41,9 @@ export declare class GeoState {
|
|
|
40
41
|
translate: boolean;
|
|
41
42
|
};
|
|
42
43
|
projection: GeoProjection | undefined;
|
|
43
|
-
constructor(propsGetter: () => GeoStateProps)
|
|
44
|
+
constructor(propsGetter: () => GeoStateProps, dimensionsGetter?: () => {
|
|
45
|
+
width: number;
|
|
46
|
+
height: number;
|
|
47
|
+
});
|
|
44
48
|
fitSizeRange: [number, number];
|
|
45
49
|
}
|
|
@@ -1,88 +1,100 @@
|
|
|
1
1
|
export class GeoState {
|
|
2
2
|
// Props getter function - set in constructor
|
|
3
3
|
_propsGetter;
|
|
4
|
+
_dimensionsGetter;
|
|
4
5
|
// Props - accessed via getter function for fine-grained reactivity
|
|
5
6
|
props = $derived(this._propsGetter());
|
|
6
|
-
// Context references
|
|
7
|
+
// Context references — used by GeoProjection.svelte (client-side only)
|
|
7
8
|
chartWidth = $state(100);
|
|
8
9
|
chartHeight = $state(100);
|
|
9
10
|
transformState = $state(null);
|
|
10
11
|
transformApply = $state({ rotation: false, scale: true, translate: true });
|
|
11
|
-
// The actual projection instance
|
|
12
|
-
projection = $
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// Apply scale
|
|
25
|
-
if ('scale' in _projection) {
|
|
26
|
-
if (this.props.scale) {
|
|
27
|
-
_projection.scale(this.props.scale);
|
|
28
|
-
}
|
|
29
|
-
if (this.transformState?.mode === 'projection' && this.transformApply.scale) {
|
|
30
|
-
_projection.scale(this.transformState.scale);
|
|
31
|
-
}
|
|
12
|
+
// The actual projection instance — derived so it works during SSR
|
|
13
|
+
projection = $derived.by(() => {
|
|
14
|
+
if (!this.props.projection)
|
|
15
|
+
return undefined;
|
|
16
|
+
const _projection = this.props.projection();
|
|
17
|
+
// Apply fitSize if fitGeojson is provided
|
|
18
|
+
if (this.props.fitGeojson && 'fitSize' in _projection) {
|
|
19
|
+
_projection.fitSize(this.fitSizeRange, this.props.fitGeojson);
|
|
20
|
+
}
|
|
21
|
+
// Apply scale
|
|
22
|
+
if ('scale' in _projection) {
|
|
23
|
+
if (this.props.scale) {
|
|
24
|
+
_projection.scale(this.props.scale);
|
|
32
25
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (this.props.rotate) {
|
|
36
|
-
_projection.rotate([
|
|
37
|
-
this.props.rotate.yaw,
|
|
38
|
-
this.props.rotate.pitch,
|
|
39
|
-
this.props.rotate.roll,
|
|
40
|
-
]);
|
|
41
|
-
}
|
|
42
|
-
if (this.transformState?.mode === 'projection' && this.transformApply.rotation) {
|
|
43
|
-
_projection.rotate([
|
|
44
|
-
this.transformState.translate.x, // yaw
|
|
45
|
-
this.transformState.translate.y, // pitch
|
|
46
|
-
]);
|
|
47
|
-
}
|
|
26
|
+
if (this.transformState?.mode === 'projection' && this.transformApply.scale) {
|
|
27
|
+
_projection.scale(this.transformState.scale);
|
|
48
28
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
]);
|
|
59
|
-
}
|
|
29
|
+
}
|
|
30
|
+
// Apply rotate
|
|
31
|
+
if ('rotate' in _projection) {
|
|
32
|
+
if (this.props.rotate) {
|
|
33
|
+
_projection.rotate([
|
|
34
|
+
this.props.rotate.yaw,
|
|
35
|
+
this.props.rotate.pitch,
|
|
36
|
+
this.props.rotate.roll,
|
|
37
|
+
]);
|
|
60
38
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
39
|
+
if (this.transformState?.mode === 'projection' && this.transformApply.rotation) {
|
|
40
|
+
_projection.rotate([
|
|
41
|
+
this.transformState.translate.x, // yaw
|
|
42
|
+
this.transformState.translate.y, // pitch
|
|
43
|
+
]);
|
|
64
44
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
45
|
+
}
|
|
46
|
+
// Apply translate
|
|
47
|
+
if ('translate' in _projection) {
|
|
48
|
+
if (this.props.translate) {
|
|
49
|
+
_projection.translate(this.props.translate);
|
|
68
50
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
51
|
+
else if (!this.props.fitGeojson) {
|
|
52
|
+
// Default translate to container center when not explicitly set
|
|
53
|
+
// and not already positioned via fitSize/fitGeojson
|
|
54
|
+
_projection.translate([
|
|
55
|
+
(this._dimensionsGetter?.().width ?? this.chartWidth) / 2,
|
|
56
|
+
(this._dimensionsGetter?.().height ?? this.chartHeight) / 2,
|
|
57
|
+
]);
|
|
72
58
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
59
|
+
if (this.transformState?.mode === 'projection' && this.transformApply.translate) {
|
|
60
|
+
_projection.translate([
|
|
61
|
+
this.transformState.translate.x,
|
|
62
|
+
this.transformState.translate.y,
|
|
63
|
+
]);
|
|
76
64
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
65
|
+
}
|
|
66
|
+
// Apply center
|
|
67
|
+
if (this.props.center && 'center' in _projection) {
|
|
68
|
+
_projection.center(this.props.center);
|
|
69
|
+
}
|
|
70
|
+
// Apply reflectX
|
|
71
|
+
if (this.props.reflectX) {
|
|
72
|
+
_projection.reflectX(this.props.reflectX);
|
|
73
|
+
}
|
|
74
|
+
// Apply reflectY
|
|
75
|
+
if (this.props.reflectY) {
|
|
76
|
+
_projection.reflectY(this.props.reflectY);
|
|
77
|
+
}
|
|
78
|
+
// Apply clipAngle
|
|
79
|
+
if (this.props.clipAngle && 'clipAngle' in _projection) {
|
|
80
|
+
_projection.clipAngle(this.props.clipAngle);
|
|
81
|
+
}
|
|
82
|
+
// Apply clipExtent
|
|
83
|
+
if (this.props.clipExtent && 'clipExtent' in _projection) {
|
|
84
|
+
_projection.clipExtent(this.props.clipExtent);
|
|
85
|
+
}
|
|
86
|
+
return _projection;
|
|
87
|
+
});
|
|
88
|
+
constructor(propsGetter, dimensionsGetter) {
|
|
89
|
+
this._propsGetter = propsGetter;
|
|
90
|
+
this._dimensionsGetter = dimensionsGetter;
|
|
83
91
|
}
|
|
84
|
-
// Derived properties
|
|
92
|
+
// Derived properties — use dimensions getter (from ChartState) when available,
|
|
93
|
+
// falling back to $state values (set by GeoProjection.svelte via $effect)
|
|
85
94
|
fitSizeRange = $derived(this.props.fixedAspectRatio
|
|
86
95
|
? [100, 100 / this.props.fixedAspectRatio]
|
|
87
|
-
: [
|
|
96
|
+
: [
|
|
97
|
+
this._dimensionsGetter?.().width ?? this.chartWidth,
|
|
98
|
+
this._dimensionsGetter?.().height ?? this.chartHeight,
|
|
99
|
+
]);
|
|
88
100
|
}
|
|
@@ -39,9 +39,15 @@ export type ArcTextOptions = {
|
|
|
39
39
|
startOffset?: string;
|
|
40
40
|
/**
|
|
41
41
|
* An amount of padding to add to the outer radius of the path to add space
|
|
42
|
-
* between the text and the arc.
|
|
42
|
+
* between the text and the arc. Applies to `'outer'` and `'middle'` positions.
|
|
43
43
|
*/
|
|
44
44
|
outerPadding?: number;
|
|
45
|
+
/**
|
|
46
|
+
* An amount of padding to subtract from the inner radius of the path to add
|
|
47
|
+
* space between the text and the arc. Applies to `'inner'` and `'middle'`
|
|
48
|
+
* positions. Positive values move the text inward (toward the chart center).
|
|
49
|
+
*/
|
|
50
|
+
innerPadding?: number;
|
|
45
51
|
/**
|
|
46
52
|
* Optional offset specifically for 'outer-radial' position from the outer arc edge.
|
|
47
53
|
* If not provided, 'outerPadding' will be used.
|
|
@@ -172,14 +172,18 @@ export function createArcTextProps(props, opts = {}, position) {
|
|
|
172
172
|
return undefined;
|
|
173
173
|
});
|
|
174
174
|
const sharedProps = $derived.by(() => {
|
|
175
|
-
|
|
175
|
+
// Center text along the arc path by default (50% startOffset, middle anchor).
|
|
176
|
+
// When the caller provides an explicit `startOffset`, honor it with a `start`
|
|
177
|
+
// anchor so the text begins at that position instead of centering around it.
|
|
178
|
+
if (opts.startOffset != null) {
|
|
176
179
|
return {
|
|
177
|
-
startOffset: opts.startOffset
|
|
178
|
-
textAnchor: '
|
|
180
|
+
startOffset: opts.startOffset,
|
|
181
|
+
textAnchor: 'start',
|
|
179
182
|
};
|
|
180
183
|
}
|
|
181
184
|
return {
|
|
182
|
-
startOffset:
|
|
185
|
+
startOffset: '50%',
|
|
186
|
+
textAnchor: 'middle',
|
|
183
187
|
};
|
|
184
188
|
});
|
|
185
189
|
const radialPositionProps = $derived.by(() => {
|
package/dist/utils/canvas.js
CHANGED
|
@@ -56,6 +56,15 @@ const supportedStyles = [
|
|
|
56
56
|
* Appends or reuses `<svg>` element below `<canvas>` to resolve CSS variables and classes (ex. `stroke: var(--color-primary)` => `stroke: rgb(...)` )
|
|
57
57
|
*/
|
|
58
58
|
export function _getComputedStyles(canvas, { styles, classes } = {}) {
|
|
59
|
+
// Server-side: no DOM available, return styles with sensible defaults
|
|
60
|
+
if (typeof document === 'undefined') {
|
|
61
|
+
const merged = { ...styles };
|
|
62
|
+
if (!merged.fontSize)
|
|
63
|
+
merged.fontSize = '10px';
|
|
64
|
+
if (!merged.fontFamily)
|
|
65
|
+
merged.fontFamily = 'sans-serif';
|
|
66
|
+
return merged;
|
|
67
|
+
}
|
|
59
68
|
// console.count(`getComputedStyles: ${getComputedStylesKey(canvas, { styles, classes })}`);
|
|
60
69
|
try {
|
|
61
70
|
// Get or create `<svg>` below `<canvas>`
|
|
@@ -111,10 +120,17 @@ function render(ctx, render, styleOptions = {}, { applyText, } = {}) {
|
|
|
111
120
|
const mergedStyles = { ...styleOptions.styles, ...parsedInlineStyles };
|
|
112
121
|
// TODO: Consider memoizing? How about reactiving to CSS variable changes (light/dark mode toggle)
|
|
113
122
|
let resolvedStyles;
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
|
|
123
|
+
if (typeof document === 'undefined' ||
|
|
124
|
+
(styleOptions.classes == null &&
|
|
125
|
+
!Object.values(mergedStyles).some((v) => typeof v === 'string' && v.includes('var(')))) {
|
|
126
|
+
// Skip resolving styles if running on server (no DOM), or no classes are provided and no styles are using CSS variables
|
|
117
127
|
resolvedStyles = mergedStyles;
|
|
128
|
+
// On server, provide sensible defaults for styles that would normally come from CSS
|
|
129
|
+
if (typeof document === 'undefined') {
|
|
130
|
+
if (!resolvedStyles.stroke && !resolvedStyles.fill) {
|
|
131
|
+
resolvedStyles = { ...resolvedStyles, stroke: 'black' };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
118
134
|
}
|
|
119
135
|
else {
|
|
120
136
|
// Remove constant non-css variable properties (ex. `strokeWidth: 0.5`, `fill: #123456`) as not needed and improves memoization cache hit
|
|
@@ -140,8 +156,11 @@ function render(ctx, render, styleOptions = {}, { applyText, } = {}) {
|
|
|
140
156
|
}
|
|
141
157
|
// font/text properties can be expensive to set (not sure why), so only apply if needed (renderText())
|
|
142
158
|
if (applyText) {
|
|
143
|
-
// Text properties
|
|
144
|
-
|
|
159
|
+
// Text properties — use defaults for server-side rendering where computed styles aren't available
|
|
160
|
+
const fontSize = resolvedStyles.fontSize || '10px';
|
|
161
|
+
const fontFamily = resolvedStyles.fontFamily || 'sans-serif';
|
|
162
|
+
const fontWeight = resolvedStyles.fontWeight || '';
|
|
163
|
+
ctx.font = `${fontWeight} ${fontSize} ${fontFamily}`.trim(); // build string instead of using `computedStyles.font` to fix/workaround `tabular-nums` returning `null`
|
|
145
164
|
if (resolvedStyles.textAnchor === 'middle') {
|
|
146
165
|
ctx.textAlign = 'center';
|
|
147
166
|
}
|
|
@@ -176,8 +195,8 @@ function render(ctx, render, styleOptions = {}, { applyText, } = {}) {
|
|
|
176
195
|
for (const attr of paintOrder) {
|
|
177
196
|
if (attr === 'fill') {
|
|
178
197
|
const fill = styleOptions.styles?.fill &&
|
|
179
|
-
(styleOptions.styles?.fill instanceof CanvasGradient ||
|
|
180
|
-
styleOptions.styles?.fill instanceof CanvasPattern ||
|
|
198
|
+
((typeof CanvasGradient !== 'undefined' && styleOptions.styles?.fill instanceof CanvasGradient) ||
|
|
199
|
+
(typeof CanvasPattern !== 'undefined' && styleOptions.styles?.fill instanceof CanvasPattern) ||
|
|
181
200
|
!styleOptions.styles?.fill?.includes('var'))
|
|
182
201
|
? styleOptions.styles.fill
|
|
183
202
|
: resolvedStyles?.fill;
|
|
@@ -194,7 +213,7 @@ function render(ctx, render, styleOptions = {}, { applyText, } = {}) {
|
|
|
194
213
|
}
|
|
195
214
|
else if (attr === 'stroke') {
|
|
196
215
|
const stroke = styleOptions.styles?.stroke &&
|
|
197
|
-
(styleOptions.styles?.stroke instanceof CanvasGradient ||
|
|
216
|
+
((typeof CanvasGradient !== 'undefined' && styleOptions.styles?.stroke instanceof CanvasGradient) ||
|
|
198
217
|
!styleOptions.styles?.stroke?.includes('var'))
|
|
199
218
|
? styleOptions.styles?.stroke
|
|
200
219
|
: resolvedStyles?.stroke;
|
|
@@ -312,7 +331,7 @@ export function clearCanvasContext(ctx, options) {
|
|
|
312
331
|
@see: https://web.dev/articles/canvas-hidipi
|
|
313
332
|
*/
|
|
314
333
|
export function scaleCanvas(ctx, width, height) {
|
|
315
|
-
const devicePixelRatio = window.devicePixelRatio || 1;
|
|
334
|
+
const devicePixelRatio = typeof window !== 'undefined' ? (window.devicePixelRatio || 1) : 1;
|
|
316
335
|
ctx.canvas.width = width * devicePixelRatio;
|
|
317
336
|
ctx.canvas.height = height * devicePixelRatio;
|
|
318
337
|
ctx.canvas.style.width = `${width}px`;
|
|
@@ -322,7 +341,7 @@ export function scaleCanvas(ctx, width, height) {
|
|
|
322
341
|
}
|
|
323
342
|
/** Get pixel color (r,g,b,a) at canvas coordinates */
|
|
324
343
|
export function getPixelColor(ctx, x, y) {
|
|
325
|
-
const dpr = window.devicePixelRatio ?? 1;
|
|
344
|
+
const dpr = (typeof window !== 'undefined' ? window.devicePixelRatio : null) ?? 1;
|
|
326
345
|
const imageData = ctx.getImageData(x * dpr, y * dpr, 1, 1);
|
|
327
346
|
const [r, g, b, a] = imageData.data;
|
|
328
347
|
return { r, g, b, a };
|
|
@@ -270,9 +270,9 @@ describe('renderPathData', () => {
|
|
|
270
270
|
it('applies strokeOpacity less than 1', () => {
|
|
271
271
|
const globalAlphaValues = [];
|
|
272
272
|
const originalStroke = ctx.stroke.bind(ctx);
|
|
273
|
-
vi.spyOn(ctx, 'stroke').mockImplementation((
|
|
273
|
+
vi.spyOn(ctx, 'stroke').mockImplementation(function () {
|
|
274
274
|
globalAlphaValues.push(ctx.globalAlpha);
|
|
275
|
-
originalStroke(
|
|
275
|
+
originalStroke();
|
|
276
276
|
});
|
|
277
277
|
renderPathData(ctx, 'M0,0 L100,0', {
|
|
278
278
|
styles: {
|
|
@@ -62,6 +62,20 @@ class MotionNone {
|
|
|
62
62
|
function setupTracking(motion, getValue, options) {
|
|
63
63
|
if (options.controlled)
|
|
64
64
|
return;
|
|
65
|
+
// On the server (SSR), $effect won't run, so eagerly set the target value
|
|
66
|
+
// to ensure render closures capture the actual computed state (not the initial/baseline value).
|
|
67
|
+
if (typeof window === 'undefined') {
|
|
68
|
+
try {
|
|
69
|
+
const value = getValue();
|
|
70
|
+
if (value != null) {
|
|
71
|
+
motion.set(value, { instant: true });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// getValue() may fail if reactive dependencies aren't ready yet; ignore
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
65
79
|
$effect(() => {
|
|
66
80
|
const value = getValue();
|
|
67
81
|
if (value == null)
|
package/package.json
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "techniq/layerchart",
|
|
7
7
|
"homepage": "https://layerchart.com",
|
|
8
|
-
"version": "2.0.0-next.
|
|
8
|
+
"version": "2.0.0-next.52",
|
|
9
9
|
"devDependencies": {
|
|
10
10
|
"@changesets/cli": "^2.30.0",
|
|
11
|
+
"@napi-rs/canvas": "^0.1.97",
|
|
11
12
|
"@sveltejs/adapter-auto": "^7.0.1",
|
|
12
13
|
"@sveltejs/kit": "^2.55.0",
|
|
13
14
|
"@sveltejs/package": "^2.5.7",
|
|
@@ -89,6 +90,11 @@
|
|
|
89
90
|
"svelte": "./dist/index.js",
|
|
90
91
|
"default": "./dist/index.js"
|
|
91
92
|
},
|
|
93
|
+
"./server": {
|
|
94
|
+
"types": "./dist/server/index.d.ts",
|
|
95
|
+
"svelte": "./dist/server/index.js",
|
|
96
|
+
"default": "./dist/server/index.js"
|
|
97
|
+
},
|
|
92
98
|
"./utils/*": {
|
|
93
99
|
"types": "./dist/utils/*.d.ts",
|
|
94
100
|
"svelte": "./dist/utils/*.js",
|