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.
Files changed (55) hide show
  1. package/dist/bench/ComposableLineChart.svelte +1 -1
  2. package/dist/bench/GeoBench.svelte +1 -8
  3. package/dist/components/AnnotationRange.svelte +21 -8
  4. package/dist/components/Arc.svelte +1 -3
  5. package/dist/components/ArcLabel.svelte.test.js +7 -7
  6. package/dist/components/Bar.svelte +4 -2
  7. package/dist/components/BoxPlot.svelte +4 -12
  8. package/dist/components/Cell.svelte +13 -8
  9. package/dist/components/Chart.svelte +69 -26
  10. package/dist/components/ChartChildren.svelte +22 -4
  11. package/dist/components/Circle.svelte +34 -12
  12. package/dist/components/ClipPath.svelte +3 -9
  13. package/dist/components/Ellipse.svelte +27 -6
  14. package/dist/components/GeoLegend.svelte +1 -3
  15. package/dist/components/GeoPoint.svelte +25 -3
  16. package/dist/components/GeoSpline.svelte +1 -4
  17. package/dist/components/GeoTile.svelte +8 -4
  18. package/dist/components/Group.svelte +11 -5
  19. package/dist/components/Highlight.svelte +3 -3
  20. package/dist/components/Image.svelte +42 -30
  21. package/dist/components/Labels.svelte +2 -4
  22. package/dist/components/Line.svelte +7 -6
  23. package/dist/components/LinearGradient.svelte +8 -4
  24. package/dist/components/Link.svelte +0 -1
  25. package/dist/components/Marker.svelte +9 -1
  26. package/dist/components/Path.svelte +43 -23
  27. package/dist/components/Pattern.svelte +101 -5
  28. package/dist/components/Pattern.svelte.d.ts +3 -1
  29. package/dist/components/Pie.svelte +2 -6
  30. package/dist/components/RadialGradient.svelte +8 -4
  31. package/dist/components/Rect.svelte +29 -12
  32. package/dist/components/Spline.svelte +24 -6
  33. package/dist/components/Text.svelte +9 -5
  34. package/dist/components/Trail.svelte +19 -7
  35. package/dist/components/Vector.svelte +37 -14
  36. package/dist/components/Violin.svelte +1 -2
  37. package/dist/components/charts/ArcChart.svelte +8 -5
  38. package/dist/components/charts/AreaChart.svelte +6 -1
  39. package/dist/components/charts/BarChart.svelte +3 -1
  40. package/dist/components/charts/LineChart.svelte +6 -1
  41. package/dist/components/charts/PieChart.svelte +10 -3
  42. package/dist/components/tooltip/Tooltip.svelte +2 -8
  43. package/dist/contexts/chart.d.ts +1 -1
  44. package/dist/contexts/chart.js +3 -1
  45. package/dist/server/TestBarChart.svelte +28 -28
  46. package/dist/server/TestLineChart.svelte +28 -28
  47. package/dist/server/index.js +1 -1
  48. package/dist/states/brush.svelte.js +16 -13
  49. package/dist/states/chart.svelte.js +14 -4
  50. package/dist/states/chart.svelte.test.js +70 -19
  51. package/dist/states/geo.svelte.js +1 -4
  52. package/dist/states/series.svelte.js +1 -1
  53. package/dist/utils/canvas.js +7 -4
  54. package/dist/utils/trail.js +3 -4
  55. 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 ? clampByIndex(v, this.xDomainMin, this.xDomainMax, xDomain) : clamp(v, this.xDomainMin, this.xDomainMax);
181
- const clampY = (v) => yCat ? clampByIndex(v, this.yDomainMin, this.yDomainMax, yDomain) : clamp(v, this.yDomainMin, this.yDomainMax);
182
- const ltX = (a, b) => xCat ? xDomain.indexOf(a) < xDomain.indexOf(b) : a < +b;
183
- const gtX = (a, b) => xCat ? xDomain.indexOf(a) > xDomain.indexOf(b) : a > +b;
184
- const ltY = (a, b) => yCat ? yDomain.indexOf(a) < yDomain.indexOf(b) : a < +b;
185
- const gtY = (a, b) => yCat ? yDomain.indexOf(a) > yDomain.indexOf(b) : a > +b;
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() || this.x[1]?.valueOf() !== newX[1]?.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() || this.y[1]?.valueOf() !== newY[1]?.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
- const visibleKeys = new Set(this.seriesState.visibleSeries.map((s) => s.key));
682
- return this.props.x1Domain.filter((key) => visibleKeys.has(key));
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
- const visibleKeys = new Set(this.seriesState.visibleSeries.map((s) => s.key));
696
- return this.props.y1Domain.filter((key) => visibleKeys.has(key));
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({ key: 'apples', color: 'red', value: 'apples' });
291
- expect(state.seriesState.series[1]).toMatchObject({ key: 'bananas', color: 'yellow', value: 'bananas' });
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({ key: 'earthquakes', color: 'red', label: 'Earthquakes' });
615
- expect(state.seriesState.series[1]).toMatchObject({ key: 'volcanos', color: 'orange', label: 'Volcanos' });
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
@@ -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' && styleOptions.styles?.fill instanceof CanvasGradient) ||
200
- (typeof CanvasPattern !== 'undefined' && styleOptions.styles?.fill instanceof CanvasPattern) ||
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' && styleOptions.styles?.stroke instanceof CanvasGradient) ||
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' ? (window.devicePixelRatio || 1) : 1;
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`;
@@ -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] + Math.hypot(original[i].x - original[i - 1].x, original[i].y - original[i - 1].y));
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
@@ -5,7 +5,7 @@
5
5
  "license": "MIT",
6
6
  "repository": "techniq/layerchart",
7
7
  "homepage": "https://layerchart.com",
8
- "version": "2.0.0-next.55",
8
+ "version": "2.0.0-next.57",
9
9
  "devDependencies": {
10
10
  "@changesets/cli": "^2.30.0",
11
11
  "@napi-rs/canvas": "^0.1.97",