layerchart 2.0.0-next.55 → 2.0.0-next.57
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/bench/ComposableLineChart.svelte +1 -1
- package/dist/bench/GeoBench.svelte +1 -8
- package/dist/components/AnnotationRange.svelte +21 -8
- package/dist/components/Arc.svelte +1 -3
- package/dist/components/ArcLabel.svelte.test.js +7 -7
- package/dist/components/Bar.svelte +4 -2
- package/dist/components/BoxPlot.svelte +4 -12
- package/dist/components/Cell.svelte +13 -8
- package/dist/components/Chart.svelte +69 -26
- package/dist/components/ChartChildren.svelte +22 -4
- package/dist/components/Circle.svelte +34 -12
- package/dist/components/ClipPath.svelte +3 -9
- package/dist/components/Ellipse.svelte +27 -6
- package/dist/components/GeoLegend.svelte +1 -3
- package/dist/components/GeoPoint.svelte +25 -3
- package/dist/components/GeoSpline.svelte +1 -4
- package/dist/components/GeoTile.svelte +8 -4
- package/dist/components/Group.svelte +11 -5
- package/dist/components/Highlight.svelte +3 -3
- package/dist/components/Image.svelte +42 -30
- package/dist/components/Labels.svelte +2 -4
- package/dist/components/Line.svelte +7 -6
- package/dist/components/LinearGradient.svelte +8 -4
- package/dist/components/Link.svelte +0 -1
- package/dist/components/Marker.svelte +9 -1
- package/dist/components/Path.svelte +43 -23
- package/dist/components/Pattern.svelte +101 -5
- package/dist/components/Pattern.svelte.d.ts +3 -1
- package/dist/components/Pie.svelte +2 -6
- package/dist/components/RadialGradient.svelte +8 -4
- package/dist/components/Rect.svelte +29 -12
- package/dist/components/Spline.svelte +24 -6
- package/dist/components/Text.svelte +9 -5
- package/dist/components/Trail.svelte +19 -7
- package/dist/components/Vector.svelte +37 -14
- package/dist/components/Violin.svelte +1 -2
- package/dist/components/charts/ArcChart.svelte +8 -5
- package/dist/components/charts/AreaChart.svelte +6 -1
- package/dist/components/charts/BarChart.svelte +3 -1
- package/dist/components/charts/LineChart.svelte +6 -1
- package/dist/components/charts/PieChart.svelte +10 -3
- package/dist/components/tooltip/Tooltip.svelte +2 -8
- package/dist/contexts/chart.d.ts +1 -1
- package/dist/contexts/chart.js +3 -1
- package/dist/server/TestBarChart.svelte +28 -28
- package/dist/server/TestLineChart.svelte +28 -28
- package/dist/server/index.js +1 -1
- package/dist/states/brush.svelte.js +16 -13
- package/dist/states/chart.svelte.js +14 -4
- package/dist/states/chart.svelte.test.js +70 -19
- package/dist/states/geo.svelte.js +1 -4
- package/dist/states/series.svelte.js +1 -1
- package/dist/utils/canvas.js +7 -4
- package/dist/utils/trail.js +3 -4
- package/package.json +1 -1
|
@@ -6,9 +6,7 @@ import { add } from '../utils/math.js';
|
|
|
6
6
|
* For continuous scales, returns the domain unchanged.
|
|
7
7
|
*/
|
|
8
8
|
export function expandBandBrushDomain(brushDomain, baseDomain) {
|
|
9
|
-
if (brushDomain[0] == null ||
|
|
10
|
-
brushDomain[1] == null ||
|
|
11
|
-
typeof brushDomain[0] !== 'string') {
|
|
9
|
+
if (brushDomain[0] == null || brushDomain[1] == null || typeof brushDomain[0] !== 'string') {
|
|
12
10
|
return brushDomain;
|
|
13
11
|
}
|
|
14
12
|
const startIdx = baseDomain.indexOf(brushDomain[0]);
|
|
@@ -109,8 +107,7 @@ export class BrushState {
|
|
|
109
107
|
// Determine active state from current values
|
|
110
108
|
const hasX = this.x[0] != null && this.x[1] != null;
|
|
111
109
|
const hasY = this.y[0] != null && this.y[1] != null;
|
|
112
|
-
this.active =
|
|
113
|
-
this.axis === 'x' ? hasX : this.axis === 'y' ? hasY : hasX || hasY;
|
|
110
|
+
this.active = this.axis === 'x' ? hasX : this.axis === 'y' ? hasY : hasX || hasY;
|
|
114
111
|
}
|
|
115
112
|
/** Set brush to a new range, clamped to domain bounds */
|
|
116
113
|
setRange(startValue, currentValue) {
|
|
@@ -177,12 +174,16 @@ export class BrushState {
|
|
|
177
174
|
const yDomain = this.ctx?.baseYScale.domain() ?? [];
|
|
178
175
|
const xCat = isCategoricalDomain(xDomain);
|
|
179
176
|
const yCat = isCategoricalDomain(yDomain);
|
|
180
|
-
const clampX = (v) => xCat
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
177
|
+
const clampX = (v) => xCat
|
|
178
|
+
? clampByIndex(v, this.xDomainMin, this.xDomainMax, xDomain)
|
|
179
|
+
: clamp(v, this.xDomainMin, this.xDomainMax);
|
|
180
|
+
const clampY = (v) => yCat
|
|
181
|
+
? clampByIndex(v, this.yDomainMin, this.yDomainMax, yDomain)
|
|
182
|
+
: clamp(v, this.yDomainMin, this.yDomainMax);
|
|
183
|
+
const ltX = (a, b) => (xCat ? xDomain.indexOf(a) < xDomain.indexOf(b) : a < +b);
|
|
184
|
+
const gtX = (a, b) => (xCat ? xDomain.indexOf(a) > xDomain.indexOf(b) : a > +b);
|
|
185
|
+
const ltY = (a, b) => (yCat ? yDomain.indexOf(a) < yDomain.indexOf(b) : a < +b);
|
|
186
|
+
const gtY = (a, b) => (yCat ? yDomain.indexOf(a) > yDomain.indexOf(b) : a > +b);
|
|
186
187
|
switch (edge) {
|
|
187
188
|
case 'top':
|
|
188
189
|
this.y = [
|
|
@@ -218,10 +219,12 @@ export class BrushState {
|
|
|
218
219
|
const newX = externalX ?? [null, null];
|
|
219
220
|
const newY = externalY ?? [null, null];
|
|
220
221
|
// Only write when values actually differ to avoid reactive loops
|
|
221
|
-
if (this.x[0]?.valueOf() !== newX[0]?.valueOf() ||
|
|
222
|
+
if (this.x[0]?.valueOf() !== newX[0]?.valueOf() ||
|
|
223
|
+
this.x[1]?.valueOf() !== newX[1]?.valueOf()) {
|
|
222
224
|
this.x = newX;
|
|
223
225
|
}
|
|
224
|
-
if (this.y[0]?.valueOf() !== newY[0]?.valueOf() ||
|
|
226
|
+
if (this.y[0]?.valueOf() !== newY[0]?.valueOf() ||
|
|
227
|
+
this.y[1]?.valueOf() !== newY[1]?.valueOf()) {
|
|
225
228
|
this.y = newY;
|
|
226
229
|
}
|
|
227
230
|
const isXAxisActive = externalX != null &&
|
|
@@ -678,8 +678,13 @@ export class ChartState {
|
|
|
678
678
|
rDomain = $derived(calcDomain('r', this.extents, this.props.rDomain));
|
|
679
679
|
x1Domain = $derived.by(() => {
|
|
680
680
|
if (this.props.x1Domain) {
|
|
681
|
-
|
|
682
|
-
|
|
681
|
+
// Only filter by visible series when series are configured — otherwise the
|
|
682
|
+
// full x1Domain is used as-is (composable charts without series).
|
|
683
|
+
if (this.seriesState.series.length > 0) {
|
|
684
|
+
const visibleKeys = new Set(this.seriesState.visibleSeries.map((s) => s.key));
|
|
685
|
+
return this.props.x1Domain.filter((key) => visibleKeys.has(key));
|
|
686
|
+
}
|
|
687
|
+
return this.props.x1Domain;
|
|
683
688
|
}
|
|
684
689
|
// Auto-derive for grouped series when x is the category axis
|
|
685
690
|
if (this.props.seriesLayout === 'group' && this.valueAxis === 'y') {
|
|
@@ -692,8 +697,13 @@ export class ChartState {
|
|
|
692
697
|
});
|
|
693
698
|
y1Domain = $derived.by(() => {
|
|
694
699
|
if (this.props.y1Domain) {
|
|
695
|
-
|
|
696
|
-
|
|
700
|
+
// Only filter by visible series when series are configured — otherwise the
|
|
701
|
+
// full y1Domain is used as-is (composable charts without series).
|
|
702
|
+
if (this.seriesState.series.length > 0) {
|
|
703
|
+
const visibleKeys = new Set(this.seriesState.visibleSeries.map((s) => s.key));
|
|
704
|
+
return this.props.y1Domain.filter((key) => visibleKeys.has(key));
|
|
705
|
+
}
|
|
706
|
+
return this.props.y1Domain;
|
|
697
707
|
}
|
|
698
708
|
// Auto-derive for grouped series when y is the category axis
|
|
699
709
|
if (this.props.seriesLayout === 'group' && this.valueAxis === 'x') {
|
|
@@ -287,17 +287,23 @@ describe('ChartState mark registration', () => {
|
|
|
287
287
|
flushSync();
|
|
288
288
|
expect(state.seriesState.isDefaultSeries).toBe(false);
|
|
289
289
|
expect(state.seriesState.series).toHaveLength(2);
|
|
290
|
-
expect(state.seriesState.series[0]).toMatchObject({
|
|
291
|
-
|
|
290
|
+
expect(state.seriesState.series[0]).toMatchObject({
|
|
291
|
+
key: 'apples',
|
|
292
|
+
color: 'red',
|
|
293
|
+
value: 'apples',
|
|
294
|
+
});
|
|
295
|
+
expect(state.seriesState.series[1]).toMatchObject({
|
|
296
|
+
key: 'bananas',
|
|
297
|
+
color: 'yellow',
|
|
298
|
+
value: 'bananas',
|
|
299
|
+
});
|
|
292
300
|
}
|
|
293
301
|
finally {
|
|
294
302
|
cleanup();
|
|
295
303
|
}
|
|
296
304
|
});
|
|
297
305
|
it('should not generate implicit series when explicit series are provided', () => {
|
|
298
|
-
const data = [
|
|
299
|
-
{ date: '2024-01', apples: 10, bananas: 15 },
|
|
300
|
-
];
|
|
306
|
+
const data = [{ date: '2024-01', apples: 10, bananas: 15 }];
|
|
301
307
|
const { state, cleanup } = createChartState({
|
|
302
308
|
data,
|
|
303
309
|
x: 'date',
|
|
@@ -611,8 +617,16 @@ describe('ChartState geo projection skips markInfo', () => {
|
|
|
611
617
|
// seriesKey/color/label should still create implicit series for legends
|
|
612
618
|
expect(state.seriesState.isDefaultSeries).toBe(false);
|
|
613
619
|
expect(state.seriesState.series).toHaveLength(2);
|
|
614
|
-
expect(state.seriesState.series[0]).toMatchObject({
|
|
615
|
-
|
|
620
|
+
expect(state.seriesState.series[0]).toMatchObject({
|
|
621
|
+
key: 'earthquakes',
|
|
622
|
+
color: 'red',
|
|
623
|
+
label: 'Earthquakes',
|
|
624
|
+
});
|
|
625
|
+
expect(state.seriesState.series[1]).toMatchObject({
|
|
626
|
+
key: 'volcanos',
|
|
627
|
+
color: 'orange',
|
|
628
|
+
label: 'Volcanos',
|
|
629
|
+
});
|
|
616
630
|
// But flatData should not include extra mark data
|
|
617
631
|
expect(state.flatData).toHaveLength(3);
|
|
618
632
|
}
|
|
@@ -861,9 +875,7 @@ describe('ChartState implicit x/y from marks (no x/y on Chart)', () => {
|
|
|
861
875
|
}
|
|
862
876
|
});
|
|
863
877
|
it('should deduplicate repeated mark x keys into a single accessor', () => {
|
|
864
|
-
const data = [
|
|
865
|
-
{ date: new Date(2024, 0, 1), value: 10 },
|
|
866
|
-
];
|
|
878
|
+
const data = [{ date: new Date(2024, 0, 1), value: 10 }];
|
|
867
879
|
const { state, cleanup } = createChartState({});
|
|
868
880
|
try {
|
|
869
881
|
// Two marks, same x='date' — should not create duplicate keys
|
|
@@ -878,9 +890,7 @@ describe('ChartState implicit x/y from marks (no x/y on Chart)', () => {
|
|
|
878
890
|
}
|
|
879
891
|
});
|
|
880
892
|
it('should use explicit x/y from Chart props over mark-derived values', () => {
|
|
881
|
-
const data = [
|
|
882
|
-
{ date: new Date(2024, 0, 1), value: 10 },
|
|
883
|
-
];
|
|
893
|
+
const data = [{ date: new Date(2024, 0, 1), value: 10 }];
|
|
884
894
|
const { state, cleanup } = createChartState({
|
|
885
895
|
x: 'value', // explicit — should override 'date' from marks
|
|
886
896
|
y: 'value',
|
|
@@ -1250,12 +1260,7 @@ describe('ChartState group layout auto-derives x1/y1', () => {
|
|
|
1250
1260
|
{ year: '2016', apples: 480, bananas: 240, cherries: 120, grapes: 50 },
|
|
1251
1261
|
{ year: '2017', apples: 960, bananas: 480, cherries: 240, grapes: 100 },
|
|
1252
1262
|
];
|
|
1253
|
-
const series = [
|
|
1254
|
-
{ key: 'apples' },
|
|
1255
|
-
{ key: 'bananas' },
|
|
1256
|
-
{ key: 'cherries' },
|
|
1257
|
-
{ key: 'grapes' },
|
|
1258
|
-
];
|
|
1263
|
+
const series = [{ key: 'apples' }, { key: 'bananas' }, { key: 'cherries' }, { key: 'grapes' }];
|
|
1259
1264
|
it('should auto-derive x1Domain from series keys when seriesLayout=group and valueAxis=y', () => {
|
|
1260
1265
|
const { state, cleanup } = createChartState({
|
|
1261
1266
|
data: wideData,
|
|
@@ -1401,3 +1406,49 @@ describe('ChartState group layout auto-derives x1/y1', () => {
|
|
|
1401
1406
|
}
|
|
1402
1407
|
});
|
|
1403
1408
|
});
|
|
1409
|
+
describe('ChartState x1Domain/y1Domain without series', () => {
|
|
1410
|
+
const longData = [
|
|
1411
|
+
{ year: 2019, fruit: 'apples', value: 3840 },
|
|
1412
|
+
{ year: 2019, fruit: 'bananas', value: 1920 },
|
|
1413
|
+
{ year: 2018, fruit: 'apples', value: 1600 },
|
|
1414
|
+
{ year: 2018, fruit: 'bananas', value: 1440 },
|
|
1415
|
+
];
|
|
1416
|
+
it('should pass through explicit x1Domain when no series are configured', () => {
|
|
1417
|
+
const { state, cleanup } = createChartState({
|
|
1418
|
+
data: longData,
|
|
1419
|
+
x: 'year',
|
|
1420
|
+
xScale: scaleBand(),
|
|
1421
|
+
y: 'value',
|
|
1422
|
+
x1: 'fruit',
|
|
1423
|
+
x1Domain: ['apples', 'bananas'],
|
|
1424
|
+
x1Range: ({ xScale }) => [0, xScale.bandwidth()],
|
|
1425
|
+
});
|
|
1426
|
+
try {
|
|
1427
|
+
expect(state.seriesState.series).toHaveLength(0);
|
|
1428
|
+
expect(state.x1Domain).toEqual(['apples', 'bananas']);
|
|
1429
|
+
expect(state.x1Scale.domain()).toEqual(['apples', 'bananas']);
|
|
1430
|
+
}
|
|
1431
|
+
finally {
|
|
1432
|
+
cleanup();
|
|
1433
|
+
}
|
|
1434
|
+
});
|
|
1435
|
+
it('should pass through explicit y1Domain when no series are configured', () => {
|
|
1436
|
+
const { state, cleanup } = createChartState({
|
|
1437
|
+
data: longData,
|
|
1438
|
+
y: 'year',
|
|
1439
|
+
yScale: scaleBand(),
|
|
1440
|
+
x: 'value',
|
|
1441
|
+
y1: 'fruit',
|
|
1442
|
+
y1Domain: ['apples', 'bananas'],
|
|
1443
|
+
y1Range: ({ yScale }) => [0, yScale.bandwidth()],
|
|
1444
|
+
});
|
|
1445
|
+
try {
|
|
1446
|
+
expect(state.seriesState.series).toHaveLength(0);
|
|
1447
|
+
expect(state.y1Domain).toEqual(['apples', 'bananas']);
|
|
1448
|
+
expect(state.y1Scale.domain()).toEqual(['apples', 'bananas']);
|
|
1449
|
+
}
|
|
1450
|
+
finally {
|
|
1451
|
+
cleanup();
|
|
1452
|
+
}
|
|
1453
|
+
});
|
|
1454
|
+
});
|
|
@@ -57,10 +57,7 @@ export class GeoState {
|
|
|
57
57
|
]);
|
|
58
58
|
}
|
|
59
59
|
if (this.transformState?.mode === 'projection' && this.transformApply.translate) {
|
|
60
|
-
_projection.translate([
|
|
61
|
-
this.transformState.translate.x,
|
|
62
|
-
this.transformState.translate.y,
|
|
63
|
-
]);
|
|
60
|
+
_projection.translate([this.transformState.translate.x, this.transformState.translate.y]);
|
|
64
61
|
}
|
|
65
62
|
}
|
|
66
63
|
// Apply center
|
|
@@ -214,7 +214,7 @@ export class SeriesState {
|
|
|
214
214
|
* Check if the series is the default
|
|
215
215
|
*/
|
|
216
216
|
get isDefaultSeries() {
|
|
217
|
-
return this.#series.length === 0 || (this.#series.length === 1 && this.#series[0].key === 'default');
|
|
217
|
+
return (this.#series.length === 0 || (this.#series.length === 1 && this.#series[0].key === 'default'));
|
|
218
218
|
}
|
|
219
219
|
/**
|
|
220
220
|
* Check if series is highlighted
|
package/dist/utils/canvas.js
CHANGED
|
@@ -196,8 +196,10 @@ function render(ctx, render, styleOptions = {}, { applyText, } = {}) {
|
|
|
196
196
|
for (const attr of paintOrder) {
|
|
197
197
|
if (attr === 'fill') {
|
|
198
198
|
const fill = styleOptions.styles?.fill &&
|
|
199
|
-
((typeof CanvasGradient !== 'undefined' &&
|
|
200
|
-
|
|
199
|
+
((typeof CanvasGradient !== 'undefined' &&
|
|
200
|
+
styleOptions.styles?.fill instanceof CanvasGradient) ||
|
|
201
|
+
(typeof CanvasPattern !== 'undefined' &&
|
|
202
|
+
styleOptions.styles?.fill instanceof CanvasPattern) ||
|
|
201
203
|
!styleOptions.styles?.fill?.includes('var'))
|
|
202
204
|
? styleOptions.styles.fill
|
|
203
205
|
: resolvedStyles?.fill;
|
|
@@ -213,7 +215,8 @@ function render(ctx, render, styleOptions = {}, { applyText, } = {}) {
|
|
|
213
215
|
}
|
|
214
216
|
else if (attr === 'stroke') {
|
|
215
217
|
const stroke = styleOptions.styles?.stroke &&
|
|
216
|
-
((typeof CanvasGradient !== 'undefined' &&
|
|
218
|
+
((typeof CanvasGradient !== 'undefined' &&
|
|
219
|
+
styleOptions.styles?.stroke instanceof CanvasGradient) ||
|
|
217
220
|
!styleOptions.styles?.stroke?.includes('var'))
|
|
218
221
|
? styleOptions.styles?.stroke
|
|
219
222
|
: resolvedStyles?.stroke;
|
|
@@ -337,7 +340,7 @@ export function clearCanvasContext(ctx, options) {
|
|
|
337
340
|
@see: https://web.dev/articles/canvas-hidipi
|
|
338
341
|
*/
|
|
339
342
|
export function scaleCanvas(ctx, width, height) {
|
|
340
|
-
const devicePixelRatio = typeof window !== 'undefined' ?
|
|
343
|
+
const devicePixelRatio = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;
|
|
341
344
|
ctx.canvas.width = width * devicePixelRatio;
|
|
342
345
|
ctx.canvas.height = height * devicePixelRatio;
|
|
343
346
|
ctx.canvas.style.width = `${width}px`;
|
package/dist/utils/trail.js
CHANGED
|
@@ -88,9 +88,7 @@ function trailPathButt(points) {
|
|
|
88
88
|
const dirPrev = hasPrev
|
|
89
89
|
? normalize(curr.x - prev.x, curr.y - prev.y)
|
|
90
90
|
: normalize(next.x - curr.x, next.y - curr.y);
|
|
91
|
-
const dirNext = hasNext
|
|
92
|
-
? normalize(next.x - curr.x, next.y - curr.y)
|
|
93
|
-
: dirPrev;
|
|
91
|
+
const dirNext = hasNext ? normalize(next.x - curr.x, next.y - curr.y) : dirPrev;
|
|
94
92
|
// Perpendicular normals (rotate 90° CCW)
|
|
95
93
|
const normPrev = [-dirPrev[1], dirPrev[0]];
|
|
96
94
|
const normNext = [-dirNext[1], dirNext[0]];
|
|
@@ -296,7 +294,8 @@ function interpolateRadii(original, dense) {
|
|
|
296
294
|
// Cumulative arc-length of original points
|
|
297
295
|
const origCum = [0];
|
|
298
296
|
for (let i = 1; i < original.length; i++) {
|
|
299
|
-
origCum.push(origCum[i - 1] +
|
|
297
|
+
origCum.push(origCum[i - 1] +
|
|
298
|
+
Math.hypot(original[i].x - original[i - 1].x, original[i].y - original[i - 1].y));
|
|
300
299
|
}
|
|
301
300
|
const origTotal = origCum[origCum.length - 1] || 1;
|
|
302
301
|
// Cumulative arc-length of dense points
|
package/package.json
CHANGED