layerchart 2.0.0-next.55 → 2.0.0-next.56

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 (54) hide show
  1. package/dist/bench/ComposableLineChart.svelte +1 -1
  2. package/dist/bench/GeoBench.svelte +1 -8
  3. package/dist/components/AnnotationRange.svelte +3 -1
  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 +22 -4
  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.test.js +24 -19
  50. package/dist/states/geo.svelte.js +1 -4
  51. package/dist/states/series.svelte.js +1 -1
  52. package/dist/utils/canvas.js +7 -4
  53. package/dist/utils/trail.js +3 -4
  54. package/package.json +1 -1
@@ -34,7 +34,7 @@
34
34
  }: Props = $props();
35
35
  </script>
36
36
 
37
- <Chart {data} {x} {y} {width} {height} {series} xDomain={xDomain} yDomain={yDomain}>
37
+ <Chart {data} {x} {y} {width} {height} {series} {xDomain} {yDomain}>
38
38
  <Layer type={layer}>
39
39
  {#if axis}
40
40
  <Axis placement="left" />
@@ -12,14 +12,7 @@
12
12
  width?: number;
13
13
  };
14
14
 
15
- let {
16
- features,
17
- fitGeojson,
18
- projection,
19
- layer = 'svg',
20
- height = 400,
21
- width,
22
- }: Props = $props();
15
+ let { features, fitGeojson, projection, layer = 'svg', height = 400, width }: Props = $props();
23
16
  </script>
24
17
 
25
18
  <Chart geo={{ projection, fitGeojson }} {width} {height}>
@@ -78,7 +78,9 @@
78
78
  const y0 = y ? ctx.yScale(y[0] ?? ctx.yDomain[0]) : ctx.yRange[0];
79
79
  const y1 = y ? ctx.yScale(y[1] ?? ctx.yDomain[1]) : ctx.yRange[1];
80
80
 
81
- const bandPadding = isScaleBand(ctx.xScale) ? (ctx.xScale.padding() * ctx.xScale.step()) / 2 : 0;
81
+ const bandPadding = isScaleBand(ctx.xScale)
82
+ ? (ctx.xScale.padding() * ctx.xScale.step()) / 2
83
+ : 0;
82
84
  const bandStep = isScaleBand(ctx.xScale) ? ctx.xScale.step() : 0;
83
85
 
84
86
  return {
@@ -255,9 +255,7 @@
255
255
 
256
256
  const endAngle = $derived(
257
257
  endAngleProp ??
258
- degreesToRadians(
259
- (ctx.config.xRange ? max(ctx.config.xRange as number[]) : max(range))!
260
- )
258
+ degreesToRadians((ctx.config.xRange ? max(ctx.config.xRange as number[]) : max(range))!)
261
259
  );
262
260
 
263
261
  const motionEndAngle = createMotion(initialValue, () => value, motion);
@@ -19,7 +19,7 @@ describe('ArcLabel', () => {
19
19
  childComponents: [
20
20
  {
21
21
  component: ArcLabel,
22
- props: ({ centroid, startAngle, endAngle, innerRadius, outerRadius, getArcTextProps }) => ({
22
+ props: ({ centroid, startAngle, endAngle, innerRadius, outerRadius, getArcTextProps, }) => ({
23
23
  centroid,
24
24
  startAngle,
25
25
  endAngle,
@@ -46,7 +46,7 @@ describe('ArcLabel', () => {
46
46
  childComponents: [
47
47
  {
48
48
  component: ArcLabel,
49
- props: ({ centroid, startAngle, endAngle, innerRadius, outerRadius, getArcTextProps }) => ({
49
+ props: ({ centroid, startAngle, endAngle, innerRadius, outerRadius, getArcTextProps, }) => ({
50
50
  centroid,
51
51
  startAngle,
52
52
  endAngle,
@@ -77,7 +77,7 @@ describe('ArcLabel', () => {
77
77
  childComponents: [
78
78
  {
79
79
  component: ArcLabel,
80
- props: ({ centroid, startAngle, endAngle, innerRadius, outerRadius, getArcTextProps }) => ({
80
+ props: ({ centroid, startAngle, endAngle, innerRadius, outerRadius, getArcTextProps, }) => ({
81
81
  centroid,
82
82
  startAngle,
83
83
  endAngle,
@@ -109,7 +109,7 @@ describe('ArcLabel', () => {
109
109
  childComponents: [
110
110
  {
111
111
  component: ArcLabel,
112
- props: ({ centroid, startAngle, endAngle, innerRadius, outerRadius, getArcTextProps }) => ({
112
+ props: ({ centroid, startAngle, endAngle, innerRadius, outerRadius, getArcTextProps, }) => ({
113
113
  centroid,
114
114
  startAngle,
115
115
  endAngle,
@@ -141,7 +141,7 @@ describe('ArcLabel', () => {
141
141
  childComponents: [
142
142
  {
143
143
  component: ArcLabel,
144
- props: ({ centroid, startAngle, endAngle, innerRadius, outerRadius, getArcTextProps }) => ({
144
+ props: ({ centroid, startAngle, endAngle, innerRadius, outerRadius, getArcTextProps, }) => ({
145
145
  centroid,
146
146
  startAngle,
147
147
  endAngle,
@@ -180,7 +180,7 @@ describe('ArcLabel', () => {
180
180
  childComponents: [
181
181
  {
182
182
  component: ArcLabel,
183
- props: ({ centroid, startAngle, endAngle, innerRadius, outerRadius, getArcTextProps }) => ({
183
+ props: ({ centroid, startAngle, endAngle, innerRadius, outerRadius, getArcTextProps, }) => ({
184
184
  centroid,
185
185
  startAngle,
186
186
  endAngle,
@@ -210,7 +210,7 @@ describe('ArcLabel', () => {
210
210
  childComponents: [
211
211
  {
212
212
  component: ArcLabel,
213
- props: ({ centroid, startAngle, endAngle, innerRadius, outerRadius, getArcTextProps }) => ({
213
+ props: ({ centroid, startAngle, endAngle, innerRadius, outerRadius, getArcTextProps, }) => ({
214
214
  centroid,
215
215
  startAngle,
216
216
  endAngle,
@@ -264,13 +264,15 @@
264
264
 
265
265
  // Auto-compute initial values for mount animation when motion is configured
266
266
  const resolvedInitialY = $derived(
267
- initialY ?? (motion && ctx.valueAxis === 'y' ? Math.max(ctx.yRange[0], ctx.yRange[1]) : undefined)
267
+ initialY ??
268
+ (motion && ctx.valueAxis === 'y' ? Math.max(ctx.yRange[0], ctx.yRange[1]) : undefined)
268
269
  );
269
270
  const resolvedInitialHeight = $derived(
270
271
  initialHeight ?? (motion && ctx.valueAxis === 'y' ? 0 : undefined)
271
272
  );
272
273
  const resolvedInitialX = $derived(
273
- initialX ?? (motion && ctx.valueAxis === 'x' ? Math.min(ctx.xRange[0], ctx.xRange[1]) : undefined)
274
+ initialX ??
275
+ (motion && ctx.valueAxis === 'x' ? Math.min(ctx.xRange[0], ctx.xRange[1]) : undefined)
274
276
  );
275
277
  const resolvedInitialWidth = $derived(
276
278
  initialWidth ?? (motion && ctx.valueAxis === 'x' ? 0 : undefined)
@@ -134,21 +134,13 @@
134
134
  });
135
135
 
136
136
  // Resolve final statistics: explicit props take priority over computed values
137
- const minVal = $derived(
138
- minProp != null ? accessor(minProp)(data) : computedStats?.min
139
- );
140
- const q1Val = $derived(
141
- q1Prop != null ? accessor(q1Prop)(data) : computedStats?.q1
142
- );
137
+ const minVal = $derived(minProp != null ? accessor(minProp)(data) : computedStats?.min);
138
+ const q1Val = $derived(q1Prop != null ? accessor(q1Prop)(data) : computedStats?.q1);
143
139
  const medianVal = $derived(
144
140
  medianProp != null ? accessor(medianProp)(data) : computedStats?.median
145
141
  );
146
- const q3Val = $derived(
147
- q3Prop != null ? accessor(q3Prop)(data) : computedStats?.q3
148
- );
149
- const maxVal = $derived(
150
- maxProp != null ? accessor(maxProp)(data) : computedStats?.max
151
- );
142
+ const q3Val = $derived(q3Prop != null ? accessor(q3Prop)(data) : computedStats?.q3);
143
+ const maxVal = $derived(maxProp != null ? accessor(maxProp)(data) : computedStats?.max);
152
144
  const outliersVal = $derived<number[]>(
153
145
  outliersProp != null ? (accessor(outliersProp)(data) ?? []) : (computedStats?.outliers ?? [])
154
146
  );
@@ -4,7 +4,18 @@
4
4
 
5
5
  type BaseRectCellProps = Omit<
6
6
  RectProps,
7
- 'width' | 'height' | 'x0' | 'x1' | 'y0' | 'y1' | 'initialX' | 'initialY' | 'initialWidth' | 'initialHeight' | 'motion' | 'ref'
7
+ | 'width'
8
+ | 'height'
9
+ | 'x0'
10
+ | 'x1'
11
+ | 'y0'
12
+ | 'y1'
13
+ | 'initialX'
14
+ | 'initialY'
15
+ | 'initialWidth'
16
+ | 'initialHeight'
17
+ | 'motion'
18
+ | 'ref'
8
19
  >;
9
20
 
10
21
  export type CellProps = BaseRectCellProps & {
@@ -36,13 +47,7 @@
36
47
  import Circle from './Circle.svelte';
37
48
  import Group from './Group.svelte';
38
49
 
39
- let {
40
- shape = 'rect',
41
- r,
42
- x,
43
- y,
44
- ...restProps
45
- }: CellProps = $props();
50
+ let { shape = 'rect', r, x, y, ...restProps }: CellProps = $props();
46
51
 
47
52
  const chartCtx = getChartContext();
48
53
  const cellWidth = $derived(isScaleBand(chartCtx.xScale) ? chartCtx.xScale.bandwidth() : 0);
@@ -22,7 +22,11 @@
22
22
  import { geoFitObjectTransform } from '../utils/geo.js';
23
23
  import TransformContext from './TransformContext.svelte';
24
24
  import BrushContext from './BrushContext.svelte';
25
- import { type BrushDomainType, type BrushState, expandBandBrushDomain } from '../states/brush.svelte.js';
25
+ import {
26
+ type BrushDomainType,
27
+ type BrushState,
28
+ expandBandBrushDomain,
29
+ } from '../states/brush.svelte.js';
26
30
 
27
31
  import { setChartContext } from '../contexts/chart.js';
28
32
  import { ChartState } from '../states/chart.svelte.js';
@@ -741,7 +745,8 @@
741
745
 
742
746
  // Resolve which projection properties the transform state applies to
743
747
  const resolvedApply = $derived.by(() => {
744
- if (transform?.mode !== 'projection') return { rotation: false, scale: false, translate: false };
748
+ if (transform?.mode !== 'projection')
749
+ return { rotation: false, scale: false, translate: false };
745
750
 
746
751
  // Auto-detect globe projections from clipAngle (flat projections return 0, globes return > 0)
747
752
  let isGlobe = false;
@@ -773,7 +778,10 @@
773
778
  });
774
779
 
775
780
  const initialTransform = $derived(
776
- transform?.mode === 'projection' && (resolvedApply.translate || resolvedApply.scale) && geo?.fitGeojson && geo?.projection
781
+ transform?.mode === 'projection' &&
782
+ (resolvedApply.translate || resolvedApply.scale) &&
783
+ geo?.fitGeojson &&
784
+ geo?.projection
777
785
  ? geoFitObjectTransform(
778
786
  geo.projection(),
779
787
  [chartState.width, chartState.height],
@@ -827,7 +835,9 @@
827
835
  axisScale: number,
828
836
  dimension: number,
829
837
  baseDomain: number[],
830
- extent: { min?: number | Date | 'data'; max?: number | Date | 'data'; minRange?: number } | undefined
838
+ extent:
839
+ | { min?: number | Date | 'data'; max?: number | Date | 'data'; minRange?: number }
840
+ | undefined
831
841
  ): number => {
832
842
  if (!extent || baseDomain.length < 2 || dimension <= 0) return axisTranslate;
833
843
 
@@ -844,13 +854,17 @@
844
854
 
845
855
  // Normalize reversed domains by flipping the translate
846
856
  const reversed = rawD0 > rawD1;
847
- const normTranslate = reversed ? dimension * axisScale - axisTranslate - dimension : axisTranslate;
857
+ const normTranslate = reversed
858
+ ? dimension * axisScale - axisTranslate - dimension
859
+ : axisTranslate;
848
860
  const numMin = Math.min(rawD0, rawD1);
849
861
 
850
862
  const rawMinVal = resolveValue(extent.min, baseDomain[0]);
851
863
  const rawMaxVal = resolveValue(extent.max, baseDomain[1]);
852
- const minVal = rawMinVal != null && rawMaxVal != null ? Math.min(rawMinVal, rawMaxVal) : rawMinVal;
853
- const maxVal = rawMinVal != null && rawMaxVal != null ? Math.max(rawMinVal, rawMaxVal) : rawMaxVal;
864
+ const minVal =
865
+ rawMinVal != null && rawMaxVal != null ? Math.min(rawMinVal, rawMaxVal) : rawMinVal;
866
+ const maxVal =
867
+ rawMinVal != null && rawMaxVal != null ? Math.max(rawMinVal, rawMaxVal) : rawMaxVal;
854
868
 
855
869
  // Current visible domain from translate/scale
856
870
  const f0 = -normTranslate / axisScale / dimension;
@@ -869,11 +883,15 @@
869
883
  // Enforce domain min/max (pan boundaries)
870
884
  if (minVal != null && visMin < minVal) {
871
885
  visMin = minVal;
872
- visMax = visMin + (extent.minRange != null && visRange < extent.minRange ? extent.minRange : visRange);
886
+ visMax =
887
+ visMin +
888
+ (extent.minRange != null && visRange < extent.minRange ? extent.minRange : visRange);
873
889
  }
874
890
  if (maxVal != null && visMax > maxVal) {
875
891
  visMax = maxVal;
876
- visMin = visMax - (extent.minRange != null && visRange < extent.minRange ? extent.minRange : visRange);
892
+ visMin =
893
+ visMax -
894
+ (extent.minRange != null && visRange < extent.minRange ? extent.minRange : visRange);
877
895
  if (minVal != null && visMin < minVal) visMin = minVal;
878
896
  }
879
897
 
@@ -930,10 +948,9 @@
930
948
 
931
949
  // Whether this is a band scale domain transform (affects scaleExtent and constrain defaults)
932
950
  const isBandDomainTransform = $derived(
933
- transform?.mode === 'domain' && (
934
- ((transform.axis ?? 'both') !== 'y' && isScaleBand(chartState._xScaleProp)) ||
935
- ((transform.axis ?? 'both') !== 'x' && isScaleBand(chartState._yScaleProp))
936
- )
951
+ transform?.mode === 'domain' &&
952
+ (((transform.axis ?? 'both') !== 'y' && isScaleBand(chartState._xScaleProp)) ||
953
+ ((transform.axis ?? 'both') !== 'x' && isScaleBand(chartState._yScaleProp)))
937
954
  );
938
955
 
939
956
  // For projection mode, scaleExtent is relative to the initial fitted scale (like d3-zoom).
@@ -942,10 +959,10 @@
942
959
  const resolvedScaleExtent = $derived.by(() => {
943
960
  if (transform?.mode === 'projection' && transform?.scaleExtent && initialTransform) {
944
961
  const baseScale = initialTransform.scale;
945
- return [
946
- transform.scaleExtent[0] * baseScale,
947
- transform.scaleExtent[1] * baseScale,
948
- ] as [number, number];
962
+ return [transform.scaleExtent[0] * baseScale, transform.scaleExtent[1] * baseScale] as [
963
+ number,
964
+ number,
965
+ ];
949
966
  }
950
967
  if (!isBandDomainTransform) return transform?.scaleExtent;
951
968
  const userExtent = transform?.scaleExtent;
@@ -972,7 +989,12 @@
972
989
  // The viewport (at current zoom) must overlap with the translateExtent world bounds.
973
990
  // As zoom increases, the allowed translate range grows proportionally.
974
991
  const projectionTranslateConstrain = $derived.by(() => {
975
- if (transform?.mode !== 'projection' || !transform?.translateExtent || !initialTransform || resolvedApply.rotation) {
992
+ if (
993
+ transform?.mode !== 'projection' ||
994
+ !transform?.translateExtent ||
995
+ !initialTransform ||
996
+ resolvedApply.rotation
997
+ ) {
976
998
  return undefined;
977
999
  }
978
1000
 
@@ -998,10 +1020,8 @@
998
1020
  // Default constrain for band scale domain transforms: prevent panning past data boundaries
999
1021
  const bandScaleConstrain = $derived.by(() => {
1000
1022
  if (!isBandDomainTransform) return undefined;
1001
- const xIsBand =
1002
- (transform!.axis ?? 'both') !== 'y' && isScaleBand(chartState._xScaleProp);
1003
- const yIsBand =
1004
- (transform!.axis ?? 'both') !== 'x' && isScaleBand(chartState._yScaleProp);
1023
+ const xIsBand = (transform!.axis ?? 'both') !== 'y' && isScaleBand(chartState._xScaleProp);
1024
+ const yIsBand = (transform!.axis ?? 'both') !== 'x' && isScaleBand(chartState._yScaleProp);
1005
1025
 
1006
1026
  return (t: { scale: number; translate: { x: number; y: number } }) => {
1007
1027
  let { scale, translate } = t;
@@ -1021,7 +1041,17 @@
1021
1041
  // Compose user-provided constrain with domainExtent constrain and band scale constrain
1022
1042
  const composedConstrain = $derived.by(() => {
1023
1043
  const userConstrain = transform?.constrain;
1024
- const constrains = [bandScaleConstrain, domainExtentConstrain, projectionTranslateConstrain, userConstrain].filter(Boolean) as Array<(t: { scale: number; translate: { x: number; y: number } }) => { scale: number; translate: { x: number; y: number } }>;
1044
+ const constrains = [
1045
+ bandScaleConstrain,
1046
+ domainExtentConstrain,
1047
+ projectionTranslateConstrain,
1048
+ userConstrain,
1049
+ ].filter(Boolean) as Array<
1050
+ (t: { scale: number; translate: { x: number; y: number } }) => {
1051
+ scale: number;
1052
+ translate: { x: number; y: number };
1053
+ }
1054
+ >;
1025
1055
  if (constrains.length === 0) return undefined;
1026
1056
  if (constrains.length === 1) return constrains[0];
1027
1057
  return (t: { scale: number; translate: { x: number; y: number } }) => {
@@ -1089,7 +1119,14 @@
1089
1119
  >
1090
1120
  {#key chartState.isMounted}
1091
1121
  <!-- svelte-ignore ownership_invalid_binding -->
1092
- {@const { domainExtent: _de, constrain: _uc, apply: _apply, scaleExtent: _se, translateExtent: _te, ...transformProps } = transform ?? {}}
1122
+ {@const {
1123
+ domainExtent: _de,
1124
+ constrain: _uc,
1125
+ apply: _apply,
1126
+ scaleExtent: _se,
1127
+ translateExtent: _te,
1128
+ ...transformProps
1129
+ } = transform ?? {}}
1093
1130
  <TransformContext
1094
1131
  bind:state={chartState.transformState}
1095
1132
  mode={transform?.mode ?? 'none'}
@@ -1100,7 +1137,9 @@
1100
1137
  scaleExtent={resolvedScaleExtent}
1101
1138
  translateExtent={resolvedTranslateExtent}
1102
1139
  constrain={composedConstrain}
1103
- disablePointer={(brush === true || (typeof brush === 'object' && !brush.disabled)) || transform?.disablePointer}
1140
+ disablePointer={brush === true ||
1141
+ (typeof brush === 'object' && !brush.disabled) ||
1142
+ transform?.disablePointer}
1104
1143
  {ondragstart}
1105
1144
  {onTransform}
1106
1145
  {ondragend}
@@ -1108,7 +1147,11 @@
1108
1147
  <!-- svelte-ignore ownership_invalid_binding -->
1109
1148
  <BrushContext {...enhancedBrushProps} bind:state={chartState.brushState}>
1110
1149
  <!-- svelte-ignore ownership_invalid_binding -->
1111
- <TooltipContext onclick={onTooltipClick} {...getObjectOrNull(tooltipContext)} bind:state={chartState.tooltipState}>
1150
+ <TooltipContext
1151
+ onclick={onTooltipClick}
1152
+ {...getObjectOrNull(tooltipContext)}
1153
+ bind:state={chartState.tooltipState}
1154
+ >
1112
1155
  <ChartChildren {children} {tooltipContext} {...restProps} />
1113
1156
  </TooltipContext>
1114
1157
  </BrushContext>
@@ -195,7 +195,12 @@
195
195
  {#if typeof grid === 'function'}
196
196
  {@render grid(snippetProps)}
197
197
  {:else if grid}
198
- <Grid x={context.valueAxis === 'x' || context.radial} y={context.valueAxis === 'y' || context.radial} {...getObjectOrNull(grid)} {...props.grid} />
198
+ <Grid
199
+ x={context.valueAxis === 'x' || context.radial}
200
+ y={context.valueAxis === 'y' || context.radial}
201
+ {...getObjectOrNull(grid)}
202
+ {...props.grid}
203
+ />
199
204
  {/if}
200
205
 
201
206
  <ChartClipPath disabled={!context.props.brush && context.transformState?.mode !== 'domain'}>
@@ -212,7 +217,12 @@
212
217
  {#if typeof rule === 'function'}
213
218
  {@render rule(snippetProps)}
214
219
  {:else if rule}
215
- <Rule x={context.valueAxis === 'x' ? 0 : false} y={context.valueAxis === 'y' ? 0 : false} {...getObjectOrNull(rule)} {...props.rule} />
220
+ <Rule
221
+ x={context.valueAxis === 'x' ? 0 : false}
222
+ y={context.valueAxis === 'y' ? 0 : false}
223
+ {...getObjectOrNull(rule)}
224
+ {...props.rule}
225
+ />
216
226
  {/if}
217
227
  {:else if axis}
218
228
  {#if axis !== 'x'}
@@ -236,12 +246,20 @@
236
246
  {#if typeof rule === 'function'}
237
247
  {@render rule(snippetProps)}
238
248
  {:else if rule}
239
- <Rule x={context.valueAxis === 'x' ? 0 : false} y={context.valueAxis === 'y' ? 0 : false} {...getObjectOrNull(rule)} {...props.rule} />
249
+ <Rule
250
+ x={context.valueAxis === 'x' ? 0 : false}
251
+ y={context.valueAxis === 'y' ? 0 : false}
252
+ {...getObjectOrNull(rule)}
253
+ {...props.rule}
254
+ />
240
255
  {/if}
241
256
  {/if}
242
257
 
243
258
  <!-- Use `full` to allow labels on edge to not be cropped (bleed into padding) -->
244
- <ChartClipPath disabled={!context.props.brush && context.transformState?.mode !== 'domain'} full>
259
+ <ChartClipPath
260
+ disabled={!context.props.brush && context.transformState?.mode !== 'domain'}
261
+ full
262
+ >
245
263
  {#if typeof points === 'function'}
246
264
  {@render points(snippetProps)}
247
265
  {:else if points}
@@ -227,6 +227,15 @@
227
227
  const staticStrokeWidth = $derived(typeof strokeWidth === 'number' ? strokeWidth : undefined);
228
228
  const staticOpacity = $derived(typeof opacity === 'number' ? opacity : undefined);
229
229
  const staticClassName = $derived(typeof className === 'string' ? className : undefined);
230
+ // Match SVG's implicit `stroke-width: 1` default: if `stroke` is set but
231
+ // `strokeWidth` is not, render a 1px border so HTML matches SVG/Canvas layers.
232
+ const staticBorderWidth = $derived(
233
+ typeof strokeWidth === 'number'
234
+ ? `${strokeWidth}px`
235
+ : typeof stroke === 'string'
236
+ ? '1px'
237
+ : undefined
238
+ );
230
239
 
231
240
  // Style options (shared between pixel and data mode)
232
241
  function getStyleOptions(
@@ -262,12 +271,13 @@
262
271
  'lc-circle',
263
272
  itemClass ?? (typeof className === 'string' ? className : undefined)
264
273
  ),
265
- style: [
266
- restProps.style as string | undefined,
267
- dashArrayAttr ? `stroke-dasharray: ${dashArrayAttr}` : undefined,
268
- ]
269
- .filter(Boolean)
270
- .join('; ') || undefined,
274
+ style:
275
+ [
276
+ restProps.style as string | undefined,
277
+ dashArrayAttr ? `stroke-dasharray: ${dashArrayAttr}` : undefined,
278
+ ]
279
+ .filter(Boolean)
280
+ .join('; ') || undefined,
271
281
  };
272
282
  }
273
283
 
@@ -400,6 +410,12 @@
400
410
  {@const resolvedStrokeWidth = resolveStyleProp(strokeWidth, item.d)}
401
411
  {@const resolvedOpacity = resolveStyleProp(opacity, item.d)}
402
412
  {@const resolvedClass = resolveStyleProp(className, item.d)}
413
+ {@const resolvedBorderWidth =
414
+ resolvedStrokeWidth != null
415
+ ? `${resolvedStrokeWidth}px`
416
+ : resolvedStroke != null
417
+ ? '1px'
418
+ : undefined}
403
419
  <div
404
420
  style:position="absolute"
405
421
  style:left="{item.cx}px"
@@ -407,9 +423,10 @@
407
423
  style:width="{item.r * 2}px"
408
424
  style:height="{item.r * 2}px"
409
425
  style:border-radius="50%"
410
- style:background-color={resolvedFill}
426
+ style:background={resolvedFill}
427
+ style:background-origin="border-box"
411
428
  style:opacity={resolvedOpacity}
412
- style:border-width={resolvedStrokeWidth}
429
+ style:border-width={resolvedBorderWidth}
413
430
  style:border-color={resolvedStroke}
414
431
  style:border-style={dashArrayResolved ? 'dashed' : 'solid'}
415
432
  style:transform="translate(-50%, -50%)"
@@ -425,9 +442,10 @@
425
442
  style:width="{motionR.current * 2}px"
426
443
  style:height="{motionR.current * 2}px"
427
444
  style:border-radius="50%"
428
- style:background-color={staticFill}
445
+ style:background={staticFill}
446
+ style:background-origin="border-box"
429
447
  style:opacity={staticOpacity}
430
- style:border-width={staticStrokeWidth}
448
+ style:border-width={staticBorderWidth}
431
449
  style:border-color={staticStroke}
432
450
  style:border-style={dashArrayResolved ? 'dashed' : 'solid'}
433
451
  style:transform="translate(-50%, -50%)"
@@ -455,8 +473,12 @@
455
473
  }
456
474
 
457
475
  /* Html layers */
458
- :global(:where(.lc-layout-html .lc-circle):not([background-color])) {
459
- background-color: var(--fill-color);
476
+ :global(:where(.lc-layout-html .lc-circle)) {
477
+ /* Match SVG sizing (visual extent equals `r * 2`, border on outer edge) */
478
+ box-sizing: border-box;
479
+ }
480
+ :global(:where(.lc-layout-html .lc-circle):not([background])) {
481
+ background: var(--fill-color);
460
482
  }
461
483
  :global(:where(.lc-layout-html .lc-circle):not([border-color])) {
462
484
  border-color: var(--stroke-color);
@@ -86,14 +86,10 @@
86
86
 
87
87
  // Outer rect covering the chart bounds — combined with the clip shape under
88
88
  // the even-odd fill rule to invert the clip.
89
- const outerRect = $derived(
90
- `M0,0 H${chartCtx.width} V${chartCtx.height} H0 Z`
91
- );
89
+ const outerRect = $derived(`M0,0 H${chartCtx.width} V${chartCtx.height} H0 Z`);
92
90
 
93
91
  // Effective path used for canvas + html layers when inverting.
94
- const effectivePath = $derived(
95
- invert && path ? `${outerRect} ${path}` : path
96
- );
92
+ const effectivePath = $derived(invert && path ? `${outerRect} ${path}` : path);
97
93
 
98
94
  // Cache the Path2D so `ctx.clip()` gets a stable reference per `path` change.
99
95
  const canvasPath = $derived(
@@ -144,9 +140,7 @@
144
140
  class="lc-clip-path-div"
145
141
  style:position="absolute"
146
142
  style:inset="0"
147
- style:clip-path={invert
148
- ? `path(evenodd, "${effectivePath}")`
149
- : `path("${effectivePath}")`}
143
+ style:clip-path={invert ? `path(evenodd, "${effectivePath}")` : `path("${effectivePath}")`}
150
144
  >
151
145
  {@render children({ id, url, useId })}
152
146
  </div>
@@ -310,6 +310,15 @@
310
310
  const staticStrokeWidth = $derived(typeof strokeWidth === 'number' ? strokeWidth : undefined);
311
311
  const staticOpacity = $derived(typeof opacity === 'number' ? opacity : undefined);
312
312
  const staticClassName = $derived(typeof className === 'string' ? className : undefined);
313
+ // Match SVG's implicit `stroke-width: 1` default: if `stroke` is set but
314
+ // `strokeWidth` is not, render a 1px border so HTML matches SVG/Canvas layers.
315
+ const staticBorderWidth = $derived(
316
+ typeof strokeWidth === 'number'
317
+ ? `${strokeWidth}px`
318
+ : typeof stroke === 'string'
319
+ ? '1px'
320
+ : undefined
321
+ );
313
322
 
314
323
  chartCtx.registerComponent({
315
324
  name: 'Ellipse',
@@ -401,6 +410,12 @@
401
410
  {@const resolvedStrokeWidth = resolveStyleProp(strokeWidth, item.d)}
402
411
  {@const resolvedOpacity = resolveStyleProp(opacity, item.d)}
403
412
  {@const resolvedClass = resolveStyleProp(className, item.d)}
413
+ {@const resolvedBorderWidth =
414
+ resolvedStrokeWidth != null
415
+ ? `${resolvedStrokeWidth}px`
416
+ : resolvedStroke != null
417
+ ? '1px'
418
+ : undefined}
404
419
  <div
405
420
  style:position="absolute"
406
421
  style:left="{item.cx}px"
@@ -408,9 +423,10 @@
408
423
  style:width="{item.rx * 2}px"
409
424
  style:height="{item.ry * 2}px"
410
425
  style:border-radius="50%"
411
- style:background-color={resolvedFill}
426
+ style:background={resolvedFill}
427
+ style:background-origin="border-box"
412
428
  style:opacity={resolvedOpacity}
413
- style:border-width={resolvedStrokeWidth}
429
+ style:border-width={resolvedBorderWidth}
414
430
  style:border-color={resolvedStroke}
415
431
  style:border-style="solid"
416
432
  style:transform="translate(-50%, -50%)"
@@ -426,9 +442,10 @@
426
442
  style:width="{motionRx.current * 2}px"
427
443
  style:height="{motionRy.current * 2}px"
428
444
  style:border-radius="50%"
429
- style:background-color={staticFill}
445
+ style:background={staticFill}
446
+ style:background-origin="border-box"
430
447
  style:opacity={staticOpacity}
431
- style:border-width={staticStrokeWidth}
448
+ style:border-width={staticBorderWidth}
432
449
  style:border-color={staticStroke}
433
450
  style:border-style="solid"
434
451
  style:transform="translate(-50%, -50%)"
@@ -454,8 +471,12 @@
454
471
  }
455
472
 
456
473
  /* Html layers */
457
- :global(:where(.lc-layout-html .lc-ellipse):not([background-color])) {
458
- background-color: var(--fill-color);
474
+ :global(:where(.lc-layout-html .lc-ellipse)) {
475
+ /* Match SVG sizing (visual extent equals `rx * 2`×`ry * 2`, border on outer edge) */
476
+ box-sizing: border-box;
477
+ }
478
+ :global(:where(.lc-layout-html .lc-ellipse):not([background])) {
479
+ background: var(--fill-color);
459
480
  }
460
481
  :global(:where(.lc-layout-html .lc-ellipse):not([border-color])) {
461
482
  border-color: var(--stroke-color);
@@ -258,9 +258,7 @@
258
258
  const width = $derived(Math.ceil(barWidth) + padding * 2);
259
259
  const svgHeight = $derived(titleHeight + height + tickLabelHeight + padding * 2 + 3);
260
260
  const barY = $derived(
261
- labelPlacement === 'top'
262
- ? titleHeight + padding + tickLabelHeight
263
- : titleHeight + padding
261
+ labelPlacement === 'top' ? titleHeight + padding + tickLabelHeight : titleHeight + padding
264
262
  );
265
263
  const tickLabelY = $derived(
266
264
  labelPlacement === 'top'