@classic-homes/charts-svelte 0.5.0 → 0.7.0

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.
@@ -29,8 +29,12 @@
29
29
  seriesLabelLookup,
30
30
  toPercentStack,
31
31
  withPointLabels,
32
+ withGhostSeries,
32
33
  withReferenceMarks,
33
34
  withZoomInset,
35
+ selectionShowsSymbols,
36
+ withSelectedPoints,
37
+ withSelectionDescription,
34
38
  } from '@classic-homes/charts-core';
35
39
 
36
40
  import { cn } from '../../utils.js';
@@ -89,6 +93,8 @@
89
93
  showDataTable,
90
94
  exportable,
91
95
  patterns,
96
+ showDataPoints = false,
97
+ selected,
92
98
  onClick,
93
99
  onHover,
94
100
  class: className,
@@ -119,7 +125,8 @@
119
125
  const plotted = isPercent ? toPercentStack(data.series) : data.series;
120
126
  const getLabel = seriesLabelLookup(data.series);
121
127
  // ECharts hides line labels along with the symbol
122
- const showSymbol = showLabels || hasPointLabels(data.series);
128
+ const showSymbol = showDataPoints || showLabels || hasPointLabels(data.series);
129
+ const pointSize = showDataPoints ? 6 : showSymbol ? 4 : 0;
123
130
  const legendBottom = builtInLegend && (legendConfig?.position ?? 'bottom') === 'bottom';
124
131
 
125
132
  const series = data.series.map((source, index) => {
@@ -128,14 +135,20 @@
128
135
  return {
129
136
  name: source.name,
130
137
  type: 'line',
131
- data: withForecastPoints(
132
- withPointLabels(
133
- pairWithCategories(data.categories, plotted[index].data, xAxisConfig?.type),
134
- source.labels,
135
- formatters.label,
136
- { position: 'top' }
138
+ data: withSelectedPoints(
139
+ withForecastPoints(
140
+ withPointLabels(
141
+ pairWithCategories(data.categories, plotted[index].data, xAxisConfig?.type),
142
+ source.labels,
143
+ formatters.label,
144
+ { position: 'top' }
145
+ ),
146
+ source,
147
+ pointSize
137
148
  ),
138
- source
149
+ data.categories,
150
+ selected,
151
+ { pointSize, ringColor: chartColors.surface }
139
152
  ),
140
153
  smooth,
141
154
  connectNulls,
@@ -158,10 +171,10 @@
158
171
  emphasis: {
159
172
  focus: 'series',
160
173
  },
161
- // Forecast points show even when the others are hidden (size 0)
162
- ...(forecastStart(source) === undefined
163
- ? { showSymbol }
164
- : { showSymbol: true, symbolSize: showSymbol ? 4 : 0 }),
174
+ // Forecast and selected points show even when the others are hidden (size 0)
175
+ showSymbol:
176
+ showSymbol || forecastStart(source) !== undefined || selectionShowsSymbols(selected),
177
+ symbolSize: pointSize,
165
178
  };
166
179
  });
167
180
 
@@ -221,7 +234,7 @@
221
234
  formatters.axis(yAxisConfig)
222
235
  ),
223
236
  series: withForecastLines(
224
- withReferenceMarks(series, referenceLines, referenceBands, {
237
+ withReferenceMarks(withGhostSeries(series, data.series), referenceLines, referenceBands, {
225
238
  valueAxis: 'y',
226
239
  format: formatters.format,
227
240
  color: chartColors.reference,
@@ -240,8 +253,11 @@
240
253
  });
241
254
 
242
255
  const accessibilityDescription = $derived(
243
- description ||
244
- (!isEmpty ? generateAreaChartDescription(title, data, formatters.format) : undefined)
256
+ withSelectionDescription(
257
+ description ||
258
+ (!isEmpty ? generateAreaChartDescription(title, data, formatters.format) : undefined),
259
+ selected
260
+ )
245
261
  );
246
262
 
247
263
  const dataTable = $derived(isEmpty ? undefined : categoryTable(data));
@@ -19,6 +19,7 @@
19
19
  forecastTagLookup,
20
20
  withForecastBars,
21
21
  formatValue,
22
+ ghostStackId,
22
23
  generateBarChartDescription,
23
24
  getChartColors,
24
25
  gridInset,
@@ -31,8 +32,14 @@
31
32
  toPercentStack,
32
33
  withPointLabels,
33
34
  withEndLabelInset,
35
+ withGhostSeries,
36
+ withoutGhostSeries,
34
37
  withReferenceMarks,
35
38
  withZoomInset,
39
+ GHOST_BAR_OPACITY,
40
+ isGhostSeries,
41
+ withSelectedBars,
42
+ withSelectionDescription,
36
43
  } from '@classic-homes/charts-core';
37
44
 
38
45
  import { cn } from '../../utils.js';
@@ -91,6 +98,7 @@
91
98
  showDataTable,
92
99
  exportable,
93
100
  patterns,
101
+ selected,
94
102
  onClick,
95
103
  onHover,
96
104
  class: className,
@@ -146,39 +154,53 @@
146
154
  const zoomAxis = isHorizontal ? 'y' : 'x';
147
155
  const legendBottom = builtInLegend && (legendConfig?.position ?? 'bottom') === 'bottom';
148
156
 
149
- const stackIds = data.series.map((s) => s.stack ?? (stacked ? 'Total' : undefined));
150
- const series = data.series.map((source, index) => ({
151
- name: source.name,
152
- type: 'bar',
153
- data: withForecastBars(
154
- withPointLabels(
155
- pairWithCategories(data.categories, plotted[index].data, categoryAxisType),
156
- source.labels,
157
- formatters.label,
158
- { position: labelPosition }
157
+ // Ghost series stack in a parallel group, beside the real stacks
158
+ const stackIds = data.series.map((s) =>
159
+ ghostStackId(s.stack ?? (stacked ? 'Total' : undefined), s)
160
+ );
161
+ const series = withGhostSeries(
162
+ data.series.map((source, index) => ({
163
+ name: source.name,
164
+ type: 'bar',
165
+ data: withSelectedBars(
166
+ withForecastBars(
167
+ withPointLabels(
168
+ pairWithCategories(data.categories, plotted[index].data, categoryAxisType),
169
+ source.labels,
170
+ formatters.label,
171
+ { position: labelPosition }
172
+ ),
173
+ source,
174
+ source.color || chartColors.series[index % chartColors.series.length]
175
+ ),
176
+ data.categories,
177
+ selected,
178
+ isGhostSeries(source) ? GHOST_BAR_OPACITY : 1
159
179
  ),
160
- source,
161
- source.color || chartColors.series[index % chartColors.series.length]
162
- ),
163
- stack: stackIds[index],
164
- itemStyle: {
165
- ...(source.color && { color: source.color }),
166
- borderRadius: barCornerRadius(isHorizontal, stackIds, index),
167
- },
168
- label: buildDataLabel({ show: true, position: labelPosition }, undefined, {
169
- show: showValues,
170
- formatter: formatters.label,
171
- getLabel,
172
- }),
173
- emphasis: {
174
- focus: 'series',
175
- },
176
- }));
180
+ stack: stackIds[index],
181
+ itemStyle: {
182
+ ...(source.color && { color: source.color }),
183
+ borderRadius: barCornerRadius(isHorizontal, stackIds, index),
184
+ },
185
+ label: buildDataLabel({ show: true, position: labelPosition }, undefined, {
186
+ show: showValues,
187
+ formatter: formatters.label,
188
+ getLabel,
189
+ }),
190
+ emphasis: {
191
+ focus: 'series',
192
+ },
193
+ })),
194
+ data.series
195
+ );
196
+ // Ghost series count toward neither totals nor end labels
197
+ const solid = withoutGhostSeries(data.series);
198
+ const solidPlotted = plotted.filter((_, index) => data.series[index].variant !== 'ghost');
177
199
 
178
200
  // Totals use the value format, not the percent one: they show the raw sum
179
201
  const format = resolveValueFormatter(valueFormat, locale);
180
202
  const totalSeries = showTotal
181
- ? buildStackTotalSeries(data.categories, data.series, {
203
+ ? buildStackTotalSeries(data.categories, solid, {
182
204
  stacked,
183
205
  position: labelPosition,
184
206
  format,
@@ -190,9 +212,11 @@
190
212
  const endLabels = !isHorizontal
191
213
  ? []
192
214
  : totalSeries.length
193
- ? [...stackTotals(data.series, stacked).values()].flat().map((t) => formatValue(t, format))
215
+ ? [...stackTotals(solid, stacked).values()].flat().map((t) => formatValue(t, format))
194
216
  : showValues
195
- ? plotted.flatMap((s) => s.data.map((value) => formatValue(value, formatters.format)))
217
+ ? solidPlotted.flatMap((s) =>
218
+ s.data.map((value) => formatValue(value, formatters.format))
219
+ )
196
220
  : [];
197
221
 
198
222
  return mergeChartOption(
@@ -253,8 +277,11 @@
253
277
  });
254
278
 
255
279
  const accessibilityDescription = $derived(
256
- description ||
257
- (!isEmpty ? generateBarChartDescription(title, data, formatters.format) : undefined)
280
+ withSelectionDescription(
281
+ description ||
282
+ (!isEmpty ? generateBarChartDescription(title, data, formatters.format) : undefined),
283
+ selected
284
+ )
258
285
  );
259
286
 
260
287
  const dataTable = $derived(isEmpty ? undefined : categoryTable(data));
@@ -20,6 +20,8 @@
20
20
  pointLabel,
21
21
  resolveValueFormatter,
22
22
  sliceTable,
23
+ withSelectedSlices,
24
+ withSelectionDescription,
23
25
  } from '@classic-homes/charts-core';
24
26
 
25
27
  import { cn } from '../../utils.js';
@@ -72,6 +74,7 @@
72
74
  showDataTable,
73
75
  exportable,
74
76
  patterns,
77
+ selected,
75
78
  onClick,
76
79
  class: className,
77
80
  }: Props = $props();
@@ -181,12 +184,15 @@
181
184
  radius: [innerRadius, '70%'],
182
185
  center: pieCenter,
183
186
  avoidLabelOverlap: true,
184
- data: data.map((item) => ({
185
- name: item.name,
186
- value: item.value,
187
- itemStyle: item.color ? { color: item.color } : undefined,
188
- label: pointLabel(item.label, labelFormatter, { position: labelPosition }),
189
- })),
187
+ data: withSelectedSlices(
188
+ data.map((item) => ({
189
+ name: item.name,
190
+ value: item.value,
191
+ itemStyle: item.color ? { color: item.color } : undefined,
192
+ label: pointLabel(item.label, labelFormatter, { position: labelPosition }),
193
+ })),
194
+ selected
195
+ ),
190
196
  label: buildDataLabel(
191
197
  {
192
198
  show: true,
@@ -214,7 +220,10 @@
214
220
  });
215
221
 
216
222
  const accessibilityDescription = $derived(
217
- description || (!isEmpty ? generatePieChartDescription(title, data, true) : undefined)
223
+ withSelectionDescription(
224
+ description || (!isEmpty ? generatePieChartDescription(title, data, true) : undefined),
225
+ selected
226
+ )
218
227
  );
219
228
 
220
229
  const dataTable = $derived(isEmpty ? undefined : sliceTable(data));
@@ -28,8 +28,12 @@
28
28
  seriesLabelLookup,
29
29
  toPercentStack,
30
30
  withPointLabels,
31
+ withGhostSeries,
31
32
  withReferenceMarks,
32
33
  withZoomInset,
34
+ selectionShowsSymbols,
35
+ withSelectedPoints,
36
+ withSelectionDescription,
33
37
  } from '@classic-homes/charts-core';
34
38
 
35
39
  import { cn } from '../../utils.js';
@@ -89,6 +93,7 @@
89
93
  showDataTable,
90
94
  exportable,
91
95
  patterns,
96
+ selected,
92
97
  onClick,
93
98
  onHover,
94
99
  class: className,
@@ -125,19 +130,25 @@
125
130
  const series = data.series.map((source, index) => ({
126
131
  name: source.name,
127
132
  type: 'line',
128
- data: withForecastPoints(
129
- withPointLabels(
130
- pairWithCategories(data.categories, plotted[index].data, xAxisConfig?.type),
131
- source.labels,
132
- formatters.label,
133
- { position: 'top' }
133
+ data: withSelectedPoints(
134
+ withForecastPoints(
135
+ withPointLabels(
136
+ pairWithCategories(data.categories, plotted[index].data, xAxisConfig?.type),
137
+ source.labels,
138
+ formatters.label,
139
+ { position: 'top' }
140
+ ),
141
+ source,
142
+ showDataPoints ? 6 : 0
134
143
  ),
135
- source,
136
- showDataPoints ? 6 : 0
144
+ data.categories,
145
+ selected,
146
+ { pointSize: showDataPoints ? 6 : 0, ringColor: chartColors.surface }
137
147
  ),
138
148
  smooth,
139
- // Forecast points show even when the others are hidden (size 0)
140
- showSymbol: showSymbol || forecastStart(source) !== undefined,
149
+ // Forecast and selected points show even when the others are hidden (size 0)
150
+ showSymbol:
151
+ showSymbol || forecastStart(source) !== undefined || selectionShowsSymbols(selected),
141
152
  symbolSize: showDataPoints ? 6 : 0,
142
153
  connectNulls,
143
154
  stack: source.stack ?? (stacked ? 'Total' : undefined),
@@ -212,7 +223,7 @@
212
223
  formatters.axis(yAxisConfig)
213
224
  ),
214
225
  series: withForecastLines(
215
- withReferenceMarks(series, referenceLines, referenceBands, {
226
+ withReferenceMarks(withGhostSeries(series, data.series), referenceLines, referenceBands, {
216
227
  valueAxis: 'y',
217
228
  format: formatters.format,
218
229
  color: chartColors.reference,
@@ -231,8 +242,11 @@
231
242
  });
232
243
 
233
244
  const accessibilityDescription = $derived(
234
- description ||
235
- (!isEmpty ? generateLineChartDescription(title, data, formatters.format) : undefined)
245
+ withSelectionDescription(
246
+ description ||
247
+ (!isEmpty ? generateLineChartDescription(title, data, formatters.format) : undefined),
248
+ selected
249
+ )
236
250
  );
237
251
 
238
252
  const dataTable = $derived(isEmpty ? undefined : categoryTable(data));
@@ -19,6 +19,8 @@
19
19
  pointLabel,
20
20
  resolveValueFormatter,
21
21
  sliceTable,
22
+ withSelectedSlices,
23
+ withSelectionDescription,
22
24
  } from '@classic-homes/charts-core';
23
25
 
24
26
  import { cn } from '../../utils.js';
@@ -69,6 +71,7 @@
69
71
  showDataTable,
70
72
  exportable,
71
73
  patterns,
74
+ selected,
72
75
  onClick,
73
76
  class: className,
74
77
  }: Props = $props();
@@ -122,12 +125,15 @@
122
125
  type: 'pie',
123
126
  radius: innerRadius ? [innerRadius, '70%'] : '70%',
124
127
  center: legendCenter(legendConfig, showLegend && !renderLegend, ['50%', '45%']),
125
- data: data.map((item) => ({
126
- name: item.name,
127
- value: item.value,
128
- itemStyle: item.color ? { color: item.color } : undefined,
129
- label: pointLabel(item.label, labelFormatter, { position: labelPosition }),
130
- })),
128
+ data: withSelectedSlices(
129
+ data.map((item) => ({
130
+ name: item.name,
131
+ value: item.value,
132
+ itemStyle: item.color ? { color: item.color } : undefined,
133
+ label: pointLabel(item.label, labelFormatter, { position: labelPosition }),
134
+ })),
135
+ selected
136
+ ),
131
137
  label: buildDataLabel(
132
138
  {
133
139
  show: true,
@@ -152,7 +158,10 @@
152
158
  });
153
159
 
154
160
  const accessibilityDescription = $derived(
155
- description || (!isEmpty ? generatePieChartDescription(title, data, false) : undefined)
161
+ withSelectionDescription(
162
+ description || (!isEmpty ? generatePieChartDescription(title, data, false) : undefined),
163
+ selected
164
+ )
156
165
  );
157
166
 
158
167
  const dataTable = $derived(isEmpty ? undefined : sliceTable(data));
@@ -1,4 +1,4 @@
1
1
  export { ChartContainer, ChartSkeleton, ChartError, ChartEmpty, ChartLegend, type ChartContainerProps, type ChartLegendProps, type ChartSkeletonProps, type ChartErrorProps, type ChartEmptyProps, ChartDataTable, type ChartDataTableProps, } from './base/index.js';
2
2
  export { LineChart, BarChart, PieChart, AreaChart, ScatterChart, DonutChart, } from './core/index.js';
3
3
  export { RadarChart, FunnelChart, GaugeChart, HeatmapChart, TreemapChart, CandlestickChart, SankeyChart, } from './extended/index.js';
4
- export { ComboChart, DotPlotChart, BulletChart, HistogramChart, BoxPlotChart, WaterfallChart, Sparkline, StatCard, StatGroup, type StatGroupProps, } from './report/index.js';
4
+ export { ComboChart, DotPlotChart, BulletChart, HistogramChart, BoxPlotChart, WaterfallChart, Sparkline, StatCard, DeltaText, StatGroup, type StatGroupProps, } from './report/index.js';
@@ -5,4 +5,4 @@ export { LineChart, BarChart, PieChart, AreaChart, ScatterChart, DonutChart, } f
5
5
  // Extended charts
6
6
  export { RadarChart, FunnelChart, GaugeChart, HeatmapChart, TreemapChart, CandlestickChart, SankeyChart, } from './extended/index.js';
7
7
  // Report charts and stats
8
- export { ComboChart, DotPlotChart, BulletChart, HistogramChart, BoxPlotChart, WaterfallChart, Sparkline, StatCard, StatGroup, } from './report/index.js';
8
+ export { ComboChart, DotPlotChart, BulletChart, HistogramChart, BoxPlotChart, WaterfallChart, Sparkline, StatCard, DeltaText, StatGroup, } from './report/index.js';
@@ -26,6 +26,7 @@
26
26
  valueAxisConfig,
27
27
  valueLabelFormatter,
28
28
  withPointLabels,
29
+ withGhostSeries,
29
30
  withReferenceMarks,
30
31
  withZoomInset,
31
32
  } from '@classic-homes/charts-core';
@@ -243,12 +244,17 @@
243
244
  ]
244
245
  : []),
245
246
  ],
246
- series: withReferenceMarks(series, referenceLines, referenceBands, {
247
- valueAxis: 'y',
248
- format: formatters.format,
249
- color: chartColors.reference,
250
- bandColor: chartColors.referenceBand,
251
- }),
247
+ series: withReferenceMarks(
248
+ withGhostSeries(series, data.series),
249
+ referenceLines,
250
+ referenceBands,
251
+ {
252
+ valueAxis: 'y',
253
+ format: formatters.format,
254
+ color: chartColors.reference,
255
+ bandColor: chartColors.referenceBand,
256
+ }
257
+ ),
252
258
  } as EChartsOption,
253
259
  optionOverride
254
260
  );
@@ -0,0 +1,72 @@
1
+ <script lang="ts">
2
+ import type { DeltaTextProps } from '@classic-homes/charts-core';
3
+ import { computeDelta, formatValue, resolveValueFormatter } from '@classic-homes/charts-core';
4
+ import { cn } from '../../utils.js';
5
+
6
+ let {
7
+ current,
8
+ previous,
9
+ display = 'percent',
10
+ goodWhen = 'up',
11
+ valueFormat,
12
+ locale = 'en-US',
13
+ label,
14
+ class: className,
15
+ }: DeltaTextProps = $props();
16
+
17
+ const format = $derived(resolveValueFormatter(valueFormat, locale));
18
+
19
+ const change = $derived(computeDelta(current, previous));
20
+
21
+ const percentFormat = $derived(
22
+ new Intl.NumberFormat(locale, {
23
+ style: 'percent',
24
+ maximumFractionDigits: 1,
25
+ signDisplay: 'exceptZero',
26
+ })
27
+ );
28
+
29
+ const changeText = $derived.by(() => {
30
+ if (!change) return '';
31
+ const sign = change.change > 0 ? '+' : change.change < 0 ? '−' : '';
32
+ const absolute = `${sign}${formatValue(Math.abs(change.change), format)}`;
33
+ const percent = change.ratio === null ? null : percentFormat.format(change.ratio);
34
+ switch (display) {
35
+ case 'absolute':
36
+ return absolute;
37
+ case 'both':
38
+ return percent ? `${absolute} (${percent})` : absolute;
39
+ default:
40
+ // A change from zero has no percentage
41
+ return percent ?? absolute;
42
+ }
43
+ });
44
+
45
+ const changeTone = $derived.by(() => {
46
+ if (!change || change.direction === 'flat' || goodWhen === 'none') {
47
+ return 'text-muted-foreground';
48
+ }
49
+ return goodWhen === change.direction ? 'text-success' : 'text-destructive';
50
+ });
51
+
52
+ const labelText = $derived(
53
+ label ?? (typeof previous === 'number' ? `vs ${formatValue(previous, format)}` : '')
54
+ );
55
+ </script>
56
+
57
+ {#if change}
58
+ <span
59
+ data-testid="delta-text"
60
+ class={cn('inline-flex flex-wrap items-baseline gap-x-1 text-xs', className)}
61
+ >
62
+ <span class={cn('font-medium tabular-nums', changeTone)}>
63
+ <span aria-hidden="true">
64
+ {change.direction === 'up' ? '▲' : change.direction === 'down' ? '▼' : ''}
65
+ </span>
66
+ {changeText}
67
+ </span>
68
+ {#if labelText}
69
+ <span class="text-muted-foreground">{labelText}</span>
70
+ {/if}
71
+ </span>
72
+ {/if}
@@ -0,0 +1,4 @@
1
+ import type { DeltaTextProps } from '@classic-homes/charts-core';
2
+ declare const DeltaText: import("svelte").Component<DeltaTextProps, {}, "">;
3
+ type DeltaText = ReturnType<typeof DeltaText>;
4
+ export default DeltaText;
@@ -1,7 +1,8 @@
1
1
  <script lang="ts">
2
2
  import type { StatCardProps } from '@classic-homes/charts-core';
3
- import { computeDelta, formatValue, resolveValueFormatter } from '@classic-homes/charts-core';
3
+ import { formatValue, resolveValueFormatter } from '@classic-homes/charts-core';
4
4
  import { cn } from '../../utils.js';
5
+ import DeltaText from './DeltaText.svelte';
5
6
  import Sparkline from './Sparkline.svelte';
6
7
 
7
8
  let {
@@ -27,47 +28,7 @@
27
28
  typeof value === 'string' ? value : formatValue(value ?? null, format, emptyValue)
28
29
  );
29
30
 
30
- const change = $derived(
31
- delta
32
- ? computeDelta(delta.current ?? (typeof value === 'number' ? value : null), delta.previous)
33
- : null
34
- );
35
-
36
- const percentFormat = $derived(
37
- new Intl.NumberFormat(locale, {
38
- style: 'percent',
39
- maximumFractionDigits: 1,
40
- signDisplay: 'exceptZero',
41
- })
42
- );
43
-
44
- const changeText = $derived.by(() => {
45
- if (!change || !delta) return '';
46
- const sign = change.change > 0 ? '+' : change.change < 0 ? '−' : '';
47
- const absolute = `${sign}${formatValue(Math.abs(change.change), format)}`;
48
- const percent = change.ratio === null ? null : percentFormat.format(change.ratio);
49
- switch (delta.display ?? 'percent') {
50
- case 'absolute':
51
- return absolute;
52
- case 'both':
53
- return percent ? `${absolute} (${percent})` : absolute;
54
- default:
55
- // A change from zero has no percentage
56
- return percent ?? absolute;
57
- }
58
- });
59
-
60
- const changeTone = $derived.by(() => {
61
- if (!change || change.direction === 'flat' || delta?.goodWhen === 'none') {
62
- return 'text-muted-foreground';
63
- }
64
- const good = (delta?.goodWhen ?? 'up') === change.direction;
65
- return good ? 'text-success' : 'text-destructive';
66
- });
67
-
68
- const previousText = $derived(
69
- delta && typeof delta.previous === 'number' ? formatValue(delta.previous, format) : ''
70
- );
31
+ const deltaCurrent = $derived(delta?.current ?? (typeof value === 'number' ? value : null));
71
32
 
72
33
  const valueTone = $derived(
73
34
  {
@@ -114,18 +75,17 @@
114
75
  <div class="mt-1 flex items-end justify-between gap-3">
115
76
  <div class="min-w-0">
116
77
  <div class={cn('text-2xl font-semibold tabular-nums', valueTone)}>{display}</div>
117
- {#if changeText}
118
- <div class="mt-1 flex flex-wrap items-baseline gap-x-1 text-xs">
119
- <span class={cn('font-medium tabular-nums', changeTone)}>
120
- <span aria-hidden="true">
121
- {change?.direction === 'up' ? '▲' : change?.direction === 'down' ? '▼' : ''}
122
- </span>
123
- {changeText}
124
- </span>
125
- <span class="text-muted-foreground">
126
- {delta?.label ?? (previousText ? `vs ${previousText}` : '')}
127
- </span>
128
- </div>
78
+ {#if delta}
79
+ <DeltaText
80
+ class="mt-1 flex"
81
+ current={deltaCurrent}
82
+ previous={delta.previous}
83
+ display={delta.display}
84
+ goodWhen={delta.goodWhen}
85
+ {valueFormat}
86
+ {locale}
87
+ label={delta.label}
88
+ />
129
89
  {/if}
130
90
  {#if hint}
131
91
  <p class="mt-1 text-xs text-muted-foreground">{hint}</p>
@@ -6,6 +6,7 @@ export { default as BoxPlotChart } from './BoxPlotChart.svelte';
6
6
  export { default as WaterfallChart } from './WaterfallChart.svelte';
7
7
  export { default as Sparkline } from './Sparkline.svelte';
8
8
  export { default as StatCard } from './StatCard.svelte';
9
+ export { default as DeltaText } from './DeltaText.svelte';
9
10
  export { default as StatGroup } from './StatGroup.svelte';
10
11
  import type { ComponentProps } from 'svelte';
11
12
  import type StatGroup from './StatGroup.svelte';
@@ -6,4 +6,5 @@ export { default as BoxPlotChart } from './BoxPlotChart.svelte';
6
6
  export { default as WaterfallChart } from './WaterfallChart.svelte';
7
7
  export { default as Sparkline } from './Sparkline.svelte';
8
8
  export { default as StatCard } from './StatCard.svelte';
9
+ export { default as DeltaText } from './DeltaText.svelte';
9
10
  export { default as StatGroup } from './StatGroup.svelte';
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * Svelte ECharts components for the Classic Theme design system
5
5
  */
6
- export { ChartContainer, ChartSkeleton, ChartError, ChartEmpty, ChartLegend, type ChartContainerProps, type ChartLegendProps, type ChartSkeletonProps, type ChartErrorProps, type ChartEmptyProps, ChartDataTable, type ChartDataTableProps, LineChart, BarChart, PieChart, AreaChart, ScatterChart, DonutChart, RadarChart, FunnelChart, GaugeChart, HeatmapChart, TreemapChart, CandlestickChart, SankeyChart, ComboChart, DotPlotChart, BulletChart, HistogramChart, BoxPlotChart, WaterfallChart, Sparkline, StatCard, StatGroup, type StatGroupProps, } from './components/index.js';
6
+ export { ChartContainer, ChartSkeleton, ChartError, ChartEmpty, ChartLegend, type ChartContainerProps, type ChartLegendProps, type ChartSkeletonProps, type ChartErrorProps, type ChartEmptyProps, ChartDataTable, type ChartDataTableProps, LineChart, BarChart, PieChart, AreaChart, ScatterChart, DonutChart, RadarChart, FunnelChart, GaugeChart, HeatmapChart, TreemapChart, CandlestickChart, SankeyChart, ComboChart, DotPlotChart, BulletChart, HistogramChart, BoxPlotChart, WaterfallChart, Sparkline, StatCard, DeltaText, StatGroup, type StatGroupProps, } from './components/index.js';
7
7
  export { useChart, exportChartImage, registerEchartsModules, type ExportImageOptions, useChartTheme, useChartResize, registerClassicThemes, useReducedMotion, useChartLegend, type CustomLegendProps, type UseChartLegendOptions, type UseChartLegendReturn, type UseChartOptions, type UseChartReturn, } from './composables/index.js';
8
8
  export { cn } from './utils.js';
9
9
  export { fromRecords, toSliceData, resolveValueFormatter, formatValue, computeDelta, binValues, boxplotStats, waterfallLayout, toPercentStack, tableToCsv, downloadCsv, chartFilename, type FromRecordsOptions, type ToSliceDataOptions, type Delta, } from '@classic-homes/charts-core';
10
- export type { BaseChartProps, LegendChartProps, LabelChartProps, AxisChartProps, AxisConfig, ChartFormatter, ChartLegendConfig, ChartLegendPosition, LabelFormatterParams, TooltipFormatterParams, LegendItem, LegendRenderProps, LineChartProps, BarChartProps, PieChartProps, DonutChartProps, AreaChartProps, ScatterChartProps, RadarChartProps, FunnelChartProps, GaugeChartProps, HeatmapChartProps, TreemapChartProps, CandlestickChartProps, SankeyChartProps, LineChartData, LineSeriesData, BarChartData, BarSeriesData, PieSliceData, AreaChartData, AreaSeriesData, ScatterChartData, ScatterSeriesData, RadarChartData, RadarSeriesData, RadarIndicator, FunnelSliceData, HeatmapData, TreemapNode, TreemapLevel, CandlestickData, CandlestickItem, SankeyData, SankeyNode, SankeyLink, BaseEventParams, DataPointEventParams, SliceEventParams, HeatmapEventParams, TreemapEventParams, CandlestickEventParams, SankeyEventParams, ChartEventHandlers, ChartExportConfig, ReferenceChartProps, ZoomChartProps, RowHeightProps, ComboChartProps, DotPlotChartProps, BulletChartProps, HistogramChartProps, BoxPlotChartProps, WaterfallChartProps, SparklineProps, StatCardProps, StatDelta, CategoryChartData, CategorySeriesData, ComboChartData, ComboSeriesData, DotPlotChartData, DotPlotSeriesData, BulletItem, HistogramBin, HistogramBucket, BoxPlotGroup, BoxPlotStats, WaterfallStep, ValueFormat, ValueFormatPreset, ReferenceLine, ReferenceBand, ChartDataTable as ChartDataTableData, } from '@classic-homes/charts-core';
10
+ export type { BaseChartProps, LegendChartProps, LabelChartProps, AxisChartProps, AxisConfig, ChartFormatter, ChartLegendConfig, ChartLegendPosition, LabelFormatterParams, TooltipFormatterParams, LegendItem, LegendRenderProps, LineChartProps, BarChartProps, PieChartProps, DonutChartProps, AreaChartProps, ScatterChartProps, RadarChartProps, FunnelChartProps, GaugeChartProps, HeatmapChartProps, TreemapChartProps, CandlestickChartProps, SankeyChartProps, LineChartData, LineSeriesData, BarChartData, BarSeriesData, PieSliceData, AreaChartData, AreaSeriesData, ScatterChartData, ScatterSeriesData, RadarChartData, RadarSeriesData, RadarIndicator, FunnelSliceData, HeatmapData, TreemapNode, TreemapLevel, CandlestickData, CandlestickItem, SankeyData, SankeyNode, SankeyLink, BaseEventParams, DataPointEventParams, SliceEventParams, HeatmapEventParams, TreemapEventParams, CandlestickEventParams, SankeyEventParams, ChartEventHandlers, ChartExportConfig, ReferenceChartProps, ZoomChartProps, RowHeightProps, ComboChartProps, DotPlotChartProps, BulletChartProps, HistogramChartProps, BoxPlotChartProps, WaterfallChartProps, SparklineProps, StatCardProps, StatDelta, DeltaTextProps, CategoryChartData, CategorySeriesData, ComboChartData, ComboSeriesData, DotPlotChartData, DotPlotSeriesData, BulletItem, HistogramBin, HistogramBucket, BoxPlotGroup, BoxPlotStats, WaterfallStep, ValueFormat, ValueFormatPreset, ReferenceLine, ReferenceBand, ChartDataTable as ChartDataTableData, } from '@classic-homes/charts-core';
package/dist/lib/index.js CHANGED
@@ -12,7 +12,7 @@ LineChart, BarChart, PieChart, AreaChart, ScatterChart, DonutChart,
12
12
  // Extended charts
13
13
  RadarChart, FunnelChart, GaugeChart, HeatmapChart, TreemapChart, CandlestickChart, SankeyChart,
14
14
  // Report charts and stats
15
- ComboChart, DotPlotChart, BulletChart, HistogramChart, BoxPlotChart, WaterfallChart, Sparkline, StatCard, StatGroup, } from './components/index.js';
15
+ ComboChart, DotPlotChart, BulletChart, HistogramChart, BoxPlotChart, WaterfallChart, Sparkline, StatCard, DeltaText, StatGroup, } from './components/index.js';
16
16
  // Composables
17
17
  export { useChart, exportChartImage, registerEchartsModules, useChartTheme, useChartResize, registerClassicThemes, useReducedMotion, useChartLegend, } from './composables/index.js';
18
18
  // Utils
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classic-homes/charts-svelte",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Svelte ECharts components for Classic Theme",
5
5
  "type": "module",
6
6
  "svelte": "./dist/lib/index.js",