layerchart 2.0.0-next.51 → 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.
@@ -77,6 +77,14 @@
77
77
  */
78
78
  selected?: string[];
79
79
 
80
+ /**
81
+ * Value to indicate on the ramp (e.g. the currently hovered data point).
82
+ * When set, a downward-pointing arrow is drawn above the bar at this value's
83
+ * position. Defaults to auto-detecting from `ctx.tooltip.data` via the
84
+ * chart's color accessor (`ctx.c`).
85
+ */
86
+ value?: number | string | null;
87
+
80
88
  /**
81
89
  * Classes to apply to the elements.
82
90
  *
@@ -149,6 +157,7 @@
149
157
  onpointerleave: onpointerleaveProp,
150
158
  variant: variantProp,
151
159
  selected: selectedProp,
160
+ value: valueProp,
152
161
  classes = {},
153
162
  ref: refProp = $bindable(),
154
163
  class: className,
@@ -345,6 +354,36 @@
345
354
  const variant = $derived(variantProp ?? (seriesItems ? 'swatches' : 'ramp'));
346
355
  const selected = $derived(selectedProp ?? ctx.series?.selectedKeys?.current ?? []);
347
356
 
357
+ // Position indicator for the currently hovered value on the ramp. If `value`
358
+ // is explicitly provided, use it; otherwise fall back to `ctx.tooltip.data`
359
+ // piped through the chart's color accessor (`ctx.c`).
360
+ const indicatorX = $derived.by(() => {
361
+ if (variant !== 'ramp' || !scale) return null;
362
+ let value: any = valueProp;
363
+ if (value == null) {
364
+ const data = ctx.tooltip?.data;
365
+ if (data == null) return null;
366
+ value = ctx.c?.(data);
367
+ }
368
+ if (value == null) return null;
369
+
370
+ // Threshold / quantize / quantile scales — scaleConfig.xScale maps swatch
371
+ // *indices* to pixels, not the raw domain value. Find which bucket the
372
+ // value falls into and center on that swatch.
373
+ if ((scale as any).invertExtent) {
374
+ const i = scale.range().indexOf(scale(value));
375
+ if (i < 0) return null;
376
+ const x0 = scaleConfig.xScale?.(i - 1);
377
+ const x1 = scaleConfig.xScale?.(i);
378
+ if (typeof x0 !== 'number' || typeof x1 !== 'number') return null;
379
+ return (x0 + x1) / 2;
380
+ }
381
+
382
+ const x = scaleConfig.xScale?.(value);
383
+ if (typeof x !== 'number' || !Number.isFinite(x)) return null;
384
+ return x + scaleConfig.tickLabelOffset;
385
+ });
386
+
348
387
  const swatchItems = $derived.by(() => {
349
388
  if (seriesItems) {
350
389
  // Series-based legend items
@@ -397,10 +436,13 @@
397
436
  seriesItems,
398
437
  })}
399
438
  {:else if variant === 'ramp'}
439
+ {@const indicatorSize = 6}
440
+ {@const tickLabelY = height + tickLengthProp + tickFontSize}
441
+ {@const svgHeight = tickLabelY}
400
442
  <svg
401
443
  {width}
402
- height={height + tickLengthProp + tickFontSize}
403
- viewBox="0 0 {width} {height + tickLengthProp + tickFontSize}"
444
+ height={svgHeight}
445
+ viewBox="0 0 {width} {svgHeight}"
404
446
  class={cls('lc-legend-ramp-svg')}
405
447
  >
406
448
  <g class="lc-legend-ramp-g">
@@ -423,7 +465,7 @@
423
465
  <text
424
466
  text-anchor="middle"
425
467
  x={scaleConfig.xScale?.(tick) + scaleConfig.tickLabelOffset}
426
- y={height + tickLengthProp + tickFontSize}
468
+ y={tickLabelY}
427
469
  style:font-size={tickFontSize}
428
470
  class={cls('lc-legend-tick-text', classes.label)}
429
471
  >
@@ -442,6 +484,15 @@
442
484
  {/if}
443
485
  {/each}
444
486
  </g>
487
+
488
+ {#if indicatorX != null}
489
+ <path
490
+ d="M{indicatorX - 4},{height + indicatorSize + 1} L{indicatorX + 4},{height +
491
+ indicatorSize +
492
+ 1} L{indicatorX},{height} Z"
493
+ class={cls('lc-legend-indicator')}
494
+ />
495
+ {/if}
445
496
  </svg>
446
497
  {:else if variant === 'swatches'}
447
498
  <div class={cls('lc-legend-swatch-group', classes.items)} data-orientation={orientation}>
@@ -537,6 +588,10 @@
537
588
  stroke: var(--color-surface-content, currentColor);
538
589
  }
539
590
 
591
+ :where(.lc-legend-indicator) {
592
+ fill: var(--color-surface-content, currentColor);
593
+ }
594
+
540
595
  :where(.lc-legend-swatch-group) {
541
596
  display: flex;
542
597
  gap: 0.25rem 1rem;
@@ -64,6 +64,13 @@ export type LegendPropsWithoutHTML = {
64
64
  * An array of selected items. If provided, the legend fades unselected items.
65
65
  */
66
66
  selected?: string[];
67
+ /**
68
+ * Value to indicate on the ramp (e.g. the currently hovered data point).
69
+ * When set, a downward-pointing arrow is drawn above the bar at this value's
70
+ * position. Defaults to auto-detecting from `ctx.tooltip.data` via the
71
+ * chart's color accessor (`ctx.c`).
72
+ */
73
+ value?: number | string | null;
67
74
  /**
68
75
  * Classes to apply to the elements.
69
76
  *
@@ -7,6 +7,7 @@
7
7
  import type { SeriesData } from './types.js';
8
8
 
9
9
  import Arc from '../Arc.svelte';
10
+ import ArcLabel, { type ArcLabelConfig } from '../ArcLabel.svelte';
10
11
  import Group from '../Group.svelte';
11
12
 
12
13
  export type ArcChartExtraSnippetProps<TData> = {
@@ -28,8 +29,15 @@
28
29
  ChartProps<any>,
29
30
  // Props that don't apply to ArcChart
30
31
  'data' | 'axis' | 'brush' | 'grid' | 'highlight' | 'labels' | 'points' | 'rule'
31
- > &
32
- Pick<
32
+ > & {
33
+ /**
34
+ * Render text labels on each arc.
35
+ *
36
+ * Pass `true` to enable with default placement (`centroid`), or an object
37
+ * to customize via `ArcLabel` props (placement, format, value accessor, etc).
38
+ */
39
+ labels?: boolean | (ArcLabelConfig & { value?: Accessor });
40
+ } & Pick<
33
41
  ArcPropsWithoutHTML,
34
42
  | 'cornerRadius'
35
43
  | 'trackCornerRadius'
@@ -163,6 +171,7 @@
163
171
  marks,
164
172
  tooltip: tooltipProp,
165
173
  arc,
174
+ labels = false,
166
175
  context = $bindable(),
167
176
  trackCornerRadius,
168
177
  trackPadAngle,
@@ -175,6 +184,10 @@
175
184
 
176
185
  const center = $derived(centerProp ?? placement === 'center');
177
186
 
187
+ const labelsConfig = $derived(
188
+ labels === true ? ({} as ArcLabelConfig & { value?: Accessor }) : labels || null
189
+ );
190
+
178
191
  const c = $derived(cProp ?? key);
179
192
 
180
193
  const keyAccessor = $derived(accessor(key));
@@ -359,6 +372,30 @@
359
372
  seriesIndex: i,
360
373
  props: getArcProps(s, i),
361
374
  })}
375
+ {:else if labelsConfig}
376
+ {@const arcProps = getArcProps(s, i)}
377
+ <Arc {...arcProps}>
378
+ {#snippet children({
379
+ centroid,
380
+ startAngle,
381
+ endAngle,
382
+ innerRadius: arcInnerRadius,
383
+ outerRadius: arcOuterRadius,
384
+ getArcTextProps,
385
+ })}
386
+ {@const { value: labelValue, ...labelRest } = labelsConfig}
387
+ <ArcLabel
388
+ {centroid}
389
+ {startAngle}
390
+ {endAngle}
391
+ innerRadius={arcInnerRadius}
392
+ outerRadius={arcOuterRadius}
393
+ {getArcTextProps}
394
+ value={accessor(labelValue ?? value)(s.data?.[0] || chartData[0])}
395
+ {...labelRest}
396
+ />
397
+ {/snippet}
398
+ </Arc>
362
399
  {:else}
363
400
  <Arc {...getArcProps(s, i)} />
364
401
  {/if}
@@ -5,6 +5,7 @@ import type { ArcPropsWithoutHTML } from '../Arc.svelte';
5
5
  import type { Accessor } from '../../utils/common.js';
6
6
  import type { SeriesData } from './types.js';
7
7
  import Arc from '../Arc.svelte';
8
+ import { type ArcLabelConfig } from '../ArcLabel.svelte';
8
9
  import Group from '../Group.svelte';
9
10
  export type ArcChartExtraSnippetProps<TData> = {
10
11
  key: Accessor<TData>;
@@ -19,7 +20,17 @@ export type ArcChartProps<TData> = {
19
20
  * The data for the chart
20
21
  */
21
22
  data?: TData[] | readonly TData[];
22
- } & Omit<ChartProps<any>, 'data' | 'axis' | 'brush' | 'grid' | 'highlight' | 'labels' | 'points' | 'rule'> & Pick<ArcPropsWithoutHTML, 'cornerRadius' | 'trackCornerRadius' | 'padAngle' | 'trackPadAngle' | 'trackStartAngle' | 'trackEndAngle' | 'trackInnerRadius' | 'trackOuterRadius' | 'innerRadius' | 'outerRadius' | 'range'> & {
23
+ } & Omit<ChartProps<any>, 'data' | 'axis' | 'brush' | 'grid' | 'highlight' | 'labels' | 'points' | 'rule'> & {
24
+ /**
25
+ * Render text labels on each arc.
26
+ *
27
+ * Pass `true` to enable with default placement (`centroid`), or an object
28
+ * to customize via `ArcLabel` props (placement, format, value accessor, etc).
29
+ */
30
+ labels?: boolean | (ArcLabelConfig & {
31
+ value?: Accessor;
32
+ });
33
+ } & Pick<ArcPropsWithoutHTML, 'cornerRadius' | 'trackCornerRadius' | 'padAngle' | 'trackPadAngle' | 'trackStartAngle' | 'trackEndAngle' | 'trackInnerRadius' | 'trackOuterRadius' | 'innerRadius' | 'outerRadius' | 'range'> & {
23
34
  /**
24
35
  * The series data to be used for the chart.
25
36
  */
@@ -6,6 +6,7 @@
6
6
  import type { SeriesData } from './types.js';
7
7
 
8
8
  import Arc from '../Arc.svelte';
9
+ import ArcLabel, { type ArcLabelConfig } from '../ArcLabel.svelte';
9
10
  import Group from '../Group.svelte';
10
11
  import Pie from '../Pie.svelte';
11
12
 
@@ -28,6 +29,13 @@
28
29
  // Props that don't apply to PieChart
29
30
  'data' | 'axis' | 'brush' | 'grid' | 'highlight' | 'labels' | 'points' | 'rule'
30
31
  > & {
32
+ /**
33
+ * Render text labels on each arc.
34
+ *
35
+ * Pass `true` to enable with default placement (`centroid`), or an object
36
+ * to customize via `ArcLabel` props (placement, format, value accessor, etc).
37
+ */
38
+ labels?: boolean | (ArcLabelConfig & { value?: Accessor });
31
39
  /**
32
40
  * The series data to be used for the chart.
33
41
  */
@@ -214,10 +222,17 @@
214
222
  tooltip: tooltipProp,
215
223
  pie,
216
224
  arc,
225
+ labels = false,
217
226
  context = $bindable(),
218
227
  ...restProps
219
228
  }: PieChartProps<TData> = $props();
220
229
 
230
+ const labelsConfig = $derived.by<(ArcLabelConfig & { value?: Accessor }) | null>(() => {
231
+ if (labels === true) return { placement: 'callout' };
232
+ if (labels) return { placement: 'callout', ...labels };
233
+ return null;
234
+ });
235
+
221
236
  const series = $derived(
222
237
  seriesProp === undefined ? [{ key: 'default', value: value }] : seriesProp
223
238
  );
@@ -402,6 +417,29 @@
402
417
  index: arcIdx,
403
418
  seriesIndex: seriesIdx,
404
419
  })}
420
+ {:else if labelsConfig}
421
+ <Arc {...arcProps}>
422
+ {#snippet children({
423
+ centroid,
424
+ startAngle,
425
+ endAngle,
426
+ innerRadius: arcInnerRadius,
427
+ outerRadius: arcOuterRadius,
428
+ getArcTextProps,
429
+ })}
430
+ {@const { value: labelValue, ...labelRest } = labelsConfig}
431
+ <ArcLabel
432
+ {centroid}
433
+ {startAngle}
434
+ {endAngle}
435
+ innerRadius={arcInnerRadius}
436
+ outerRadius={arcOuterRadius}
437
+ {getArcTextProps}
438
+ value={accessor(labelValue ?? value)(arcData.data)}
439
+ {...labelRest}
440
+ />
441
+ {/snippet}
442
+ </Arc>
405
443
  {:else}
406
444
  <Arc {...arcProps} />
407
445
  {/if}
@@ -4,6 +4,7 @@ import type { ChartState } from '../../contexts/chart.js';
4
4
  import type { Accessor } from '../../utils/common.js';
5
5
  import type { SeriesData } from './types.js';
6
6
  import Arc from '../Arc.svelte';
7
+ import { type ArcLabelConfig } from '../ArcLabel.svelte';
7
8
  import Group from '../Group.svelte';
8
9
  import Pie from '../Pie.svelte';
9
10
  export type PieChartExtraSnippetProps<TData> = {
@@ -19,6 +20,15 @@ export type PieChartProps<TData> = {
19
20
  */
20
21
  data?: TData[] | readonly TData[];
21
22
  } & Omit<ChartProps<any>, 'data' | 'axis' | 'brush' | 'grid' | 'highlight' | 'labels' | 'points' | 'rule'> & {
23
+ /**
24
+ * Render text labels on each arc.
25
+ *
26
+ * Pass `true` to enable with default placement (`centroid`), or an object
27
+ * to customize via `ArcLabel` props (placement, format, value accessor, etc).
28
+ */
29
+ labels?: boolean | (ArcLabelConfig & {
30
+ value?: Accessor;
31
+ });
22
32
  /**
23
33
  * The series data to be used for the chart.
24
34
  */
@@ -8,6 +8,8 @@ export { default as AnnotationRange } from './AnnotationRange.svelte';
8
8
  export * from './AnnotationRange.svelte';
9
9
  export { default as Arc } from './Arc.svelte';
10
10
  export * from './Arc.svelte';
11
+ export { default as ArcLabel } from './ArcLabel.svelte';
12
+ export * from './ArcLabel.svelte';
11
13
  export { default as Area } from './Area.svelte';
12
14
  export * from './Area.svelte';
13
15
  export { default as Axis } from './Axis.svelte';
@@ -94,6 +96,10 @@ export { default as Labels } from './Labels.svelte';
94
96
  export * from './Labels.svelte';
95
97
  export { default as Layer } from './layers/Layer.svelte';
96
98
  export * from './layers/Layer.svelte';
99
+ export { default as CircleLegend } from './CircleLegend.svelte';
100
+ export * from './CircleLegend.svelte';
101
+ export { default as GeoLegend } from './GeoLegend.svelte';
102
+ export * from './GeoLegend.svelte';
97
103
  export { default as Legend } from './Legend.svelte';
98
104
  export * from './Legend.svelte';
99
105
  export { default as Line } from './Line.svelte';
@@ -8,6 +8,8 @@ export { default as AnnotationRange } from './AnnotationRange.svelte';
8
8
  export * from './AnnotationRange.svelte';
9
9
  export { default as Arc } from './Arc.svelte';
10
10
  export * from './Arc.svelte';
11
+ export { default as ArcLabel } from './ArcLabel.svelte';
12
+ export * from './ArcLabel.svelte';
11
13
  export { default as Area } from './Area.svelte';
12
14
  export * from './Area.svelte';
13
15
  export { default as Axis } from './Axis.svelte';
@@ -94,6 +96,10 @@ export { default as Labels } from './Labels.svelte';
94
96
  export * from './Labels.svelte';
95
97
  export { default as Layer } from './layers/Layer.svelte';
96
98
  export * from './layers/Layer.svelte';
99
+ export { default as CircleLegend } from './CircleLegend.svelte';
100
+ export * from './CircleLegend.svelte';
101
+ export { default as GeoLegend } from './GeoLegend.svelte';
102
+ export * from './GeoLegend.svelte';
97
103
  export { default as Legend } from './Legend.svelte';
98
104
  export * from './Legend.svelte';
99
105
  export { default as Line } from './Line.svelte';
@@ -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
- if (reverseText) {
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 ?? '100%',
178
- textAnchor: 'end',
180
+ startOffset: opts.startOffset,
181
+ textAnchor: 'start',
179
182
  };
180
183
  }
181
184
  return {
182
- startOffset: opts.startOffset ?? undefined,
185
+ startOffset: '50%',
186
+ textAnchor: 'middle',
183
187
  };
184
188
  });
185
189
  const radialPositionProps = $derived.by(() => {
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.51",
8
+ "version": "2.0.0-next.52",
9
9
  "devDependencies": {
10
10
  "@changesets/cli": "^2.30.0",
11
11
  "@napi-rs/canvas": "^0.1.97",