@classic-homes/charts-core 0.4.1 → 0.5.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.
package/dist/index.cjs CHANGED
@@ -424,6 +424,159 @@ function createTooltipFormatter(options = {}) {
424
424
  };
425
425
  }
426
426
 
427
+ // src/utils/forecast.ts
428
+ var DEFAULT_FORECAST_LABEL = "Forecast";
429
+ function forecastStart(source) {
430
+ const from = source.forecastFrom;
431
+ if (from == null || !Number.isFinite(from)) return void 0;
432
+ const index = Math.max(0, Math.floor(from));
433
+ return index < source.data.length ? index : void 0;
434
+ }
435
+ function hasForecast(series) {
436
+ return !!series?.some((s) => forecastStart(s) !== void 0);
437
+ }
438
+ function forecastTagLookup(series) {
439
+ if (!hasForecast(series)) return void 0;
440
+ return (dataIndex) => {
441
+ for (const s of series) {
442
+ const from = forecastStart(s);
443
+ if (from !== void 0 && dataIndex >= from) return s.forecastLabel || DEFAULT_FORECAST_LABEL;
444
+ }
445
+ return void 0;
446
+ };
447
+ }
448
+ function describeForecast(categories, series) {
449
+ let first;
450
+ for (const s of series) {
451
+ const from = forecastStart(s);
452
+ if (from === void 0 || first && first.index <= from) continue;
453
+ first = { index: from, label: s.forecastLabel || DEFAULT_FORECAST_LABEL };
454
+ }
455
+ if (!first) return void 0;
456
+ const category = categories[first.index] ?? "";
457
+ return `Values from ${category} onward are marked ${first.label.toLowerCase()}`;
458
+ }
459
+ var isPlainObject = (item) => typeof item === "object" && item !== null && !Array.isArray(item);
460
+ function blank(item) {
461
+ if (Array.isArray(item)) return [item[0], null];
462
+ return null;
463
+ }
464
+ function bare(item) {
465
+ return isPlainObject(item) && "value" in item ? item.value : item;
466
+ }
467
+ function withItem(item, extra) {
468
+ if (isPlainObject(item) && "value" in item) {
469
+ return {
470
+ ...item,
471
+ ...extra,
472
+ itemStyle: { ...item.itemStyle, ...extra.itemStyle }
473
+ };
474
+ }
475
+ return { value: item, ...extra };
476
+ }
477
+ function withForecastPoints(data, source, pointSize = 0) {
478
+ const from = forecastStart(source);
479
+ if (from === void 0) return [...data];
480
+ const size = Math.max(pointSize, 6) + 1;
481
+ return data.map(
482
+ (item, index) => index >= from ? withItem(item, { symbol: "emptyCircle", symbolSize: size }) : item
483
+ );
484
+ }
485
+ function withForecastBars(data, source, color) {
486
+ const from = forecastStart(source);
487
+ if (from === void 0) return [...data];
488
+ return data.map(
489
+ (item, index) => index >= from ? withItem(item, {
490
+ itemStyle: {
491
+ opacity: 0.45,
492
+ borderColor: color,
493
+ borderWidth: 1.5,
494
+ borderType: "dashed"
495
+ }
496
+ }) : item
497
+ );
498
+ }
499
+ function withForecastLines(series, sources, { colors: colors3 }) {
500
+ if (!hasForecast(sources)) return series;
501
+ const stackFrom = /* @__PURE__ */ new Map();
502
+ series.forEach((s, index) => {
503
+ const from = forecastStart(sources[index]);
504
+ if (s.stack == null || from === void 0) return;
505
+ stackFrom.set(s.stack, Math.min(stackFrom.get(s.stack) ?? Infinity, from));
506
+ });
507
+ const mains = [];
508
+ const companions = [];
509
+ series.forEach((s, index) => {
510
+ const from = s.stack != null ? stackFrom.get(s.stack) : forecastStart(sources[index]);
511
+ if (from === void 0) {
512
+ mains.push(s);
513
+ return;
514
+ }
515
+ const color = colors3[index];
516
+ const itemStyle = { ...s.itemStyle, color };
517
+ if (from === 0) {
518
+ mains.push({
519
+ ...s,
520
+ itemStyle,
521
+ lineStyle: { ...s.lineStyle, type: "dashed" },
522
+ ...s.areaStyle && { areaStyle: fadedArea(s.areaStyle) }
523
+ });
524
+ return;
525
+ }
526
+ const data = s.data ?? [];
527
+ const common = {
528
+ name: s.name,
529
+ type: "line",
530
+ smooth: s.smooth,
531
+ connectNulls: s.connectNulls,
532
+ showSymbol: false,
533
+ symbol: "none",
534
+ silent: true,
535
+ legendHoverLink: false,
536
+ tooltip: { show: false },
537
+ emphasis: { disabled: true },
538
+ itemStyle,
539
+ z: s.z
540
+ };
541
+ companions.push(
542
+ {
543
+ ...common,
544
+ data: data.map((item, i) => i < from ? bare(item) : blank(item)),
545
+ stack: s.stack != null ? `${s.stack}\0actual` : void 0,
546
+ lineStyle: s.lineStyle,
547
+ areaStyle: s.areaStyle
548
+ },
549
+ {
550
+ ...common,
551
+ data: data.map((item, i) => i >= from - 1 ? bare(item) : blank(item)),
552
+ stack: s.stack != null ? `${s.stack}\0forecast` : void 0,
553
+ lineStyle: { ...s.lineStyle, type: "dashed" },
554
+ areaStyle: s.areaStyle ? fadedArea(s.areaStyle) : void 0
555
+ }
556
+ );
557
+ mains.push({
558
+ ...s,
559
+ itemStyle,
560
+ // Forecast points carry their own size; the rest keep theirs (none
561
+ // when the series hid its points)
562
+ showSymbol: true,
563
+ symbolSize: s.showSymbol === false ? 0 : s.symbolSize ?? 4,
564
+ lineStyle: { ...s.lineStyle, opacity: 0 },
565
+ areaStyle: void 0,
566
+ emphasis: {
567
+ ...s.emphasis,
568
+ focus: "none",
569
+ lineStyle: { ...s.emphasis?.lineStyle, opacity: 0 }
570
+ },
571
+ z: (s.z ?? 2) + 1
572
+ });
573
+ });
574
+ return [...mains, ...companions];
575
+ }
576
+ function fadedArea(areaStyle) {
577
+ return { ...areaStyle, opacity: (areaStyle.opacity ?? 0.7) * 0.5 };
578
+ }
579
+
427
580
  // src/utils/accessibility.ts
428
581
  var isNumber = (value) => typeof value === "number" && Number.isFinite(value);
429
582
  function seriesList(names) {
@@ -449,6 +602,8 @@ function describeTrends(title, kind, data, format) {
449
602
  0
450
603
  );
451
604
  if (missing > 0) description += `. ${missing} values are missing`;
605
+ const forecast = describeForecast(data.categories, data.series);
606
+ if (forecast) description += `. ${forecast}`;
452
607
  return description;
453
608
  }
454
609
  function generateLineChartDescription(title, data, format) {
@@ -484,6 +639,8 @@ function generateBarChartDescription(title, data, format) {
484
639
  if (max && min) {
485
640
  description += `. Highest value is ${formatValue(max.value, format)} for ${max.category}. Lowest value is ${formatValue(min.value, format)} for ${min.category}`;
486
641
  }
642
+ const forecast = describeForecast(data.categories, data.series);
643
+ if (forecast) description += `. ${forecast}`;
487
644
  return description;
488
645
  }
489
646
  function generateDotPlotDescription(title, data, format) {
@@ -573,14 +730,39 @@ function generateDataTable(headers, rows) {
573
730
  }
574
731
 
575
732
  // src/utils/merge.ts
576
- var ALWAYS_REPLACE = ["series", "graphic"];
733
+ var ALWAYS_REPLACE = ["graphic"];
577
734
  function countComponents(value) {
578
735
  if (value == null || value === false) return 0;
579
736
  if (Array.isArray(value)) return value.filter((item) => item != null).length;
580
737
  return 1;
581
738
  }
739
+ function isRecord(value) {
740
+ return value !== null && typeof value === "object" && !Array.isArray(value);
741
+ }
742
+ function sameShape(a, b) {
743
+ if (!isRecord(a) || !isRecord(b)) return !isRecord(a) && !isRecord(b);
744
+ const keys = Object.keys(a).filter((key) => a[key] !== void 0);
745
+ const other = Object.keys(b).filter((key) => b[key] !== void 0);
746
+ if (keys.length !== other.length) return false;
747
+ return keys.every(
748
+ (key) => key in b && b[key] !== void 0 && (key === "type" || key === "id" ? a[key] === b[key] : sameShape(a[key], b[key]))
749
+ );
750
+ }
751
+ function asList(value) {
752
+ if (value == null || value === false) return [];
753
+ return Array.isArray(value) ? value : [value];
754
+ }
755
+ function sameSeriesShape(prev, next) {
756
+ const before = asList(prev);
757
+ const after = asList(next);
758
+ return before.length === after.length && before.every((item, i) => sameShape(item, after[i]));
759
+ }
582
760
  function getReplaceMerge(prev, next, isComponentType) {
583
761
  const types = new Set(ALWAYS_REPLACE);
762
+ const prior = prev;
763
+ if (!prior || !sameSeriesShape(prior.series, next.series)) {
764
+ types.add("series");
765
+ }
584
766
  if (prev) {
585
767
  const before = prev;
586
768
  const after = next;
@@ -592,7 +774,7 @@ function getReplaceMerge(prev, next, isComponentType) {
592
774
  }
593
775
  return [...types].filter((type) => isComponentType(type));
594
776
  }
595
- function isPlainObject(value) {
777
+ function isPlainObject2(value) {
596
778
  if (value === null || typeof value !== "object") return false;
597
779
  const proto = Object.getPrototypeOf(value);
598
780
  return proto === Object.prototype || proto === null;
@@ -605,7 +787,7 @@ function mergeValue(base, override, topLevel = false) {
605
787
  const length = Math.max(from.length, to.length);
606
788
  return Array.from({ length }, (_, index) => mergeValue(from[index], to[index]));
607
789
  }
608
- if (isPlainObject(base) && isPlainObject(override)) {
790
+ if (isPlainObject2(base) && isPlainObject2(override)) {
609
791
  const merged = { ...base };
610
792
  for (const key of Object.keys(override)) {
611
793
  merged[key] = mergeValue(base[key], override[key]);
@@ -1013,12 +1195,17 @@ function nameLabelLookup(nodes) {
1013
1195
  function hasPointLabels(series) {
1014
1196
  return series.some((s) => s.labels?.some((text) => text != null && text !== ""));
1015
1197
  }
1016
- function buildTooltip(base, { show, formatter, valueFormatter, getLabel }) {
1198
+ function buildTooltip(base, { show, formatter, valueFormatter, getLabel, forecastTag }) {
1017
1199
  if (!show) return void 0;
1018
- if (formatter === void 0 && valueFormatter === void 0) return base;
1200
+ if (formatter === void 0 && valueFormatter === void 0 && !forecastTag) return base;
1019
1201
  const tooltip = { ...base };
1020
- if (valueFormatter) {
1021
- tooltip.valueFormatter = (value) => typeof value === "number" ? valueFormatter(value) : String(value ?? "");
1202
+ if (valueFormatter || forecastTag) {
1203
+ const format = (value) => typeof value === "number" ? valueFormatter ? valueFormatter(value) : value.toLocaleString() : String(value ?? "");
1204
+ tooltip.valueFormatter = (value, dataIndex) => {
1205
+ const text = format(value);
1206
+ const tag = dataIndex === void 0 ? void 0 : forecastTag?.(dataIndex);
1207
+ return tag && value != null ? `${text} (${tag})` : text;
1208
+ };
1022
1209
  }
1023
1210
  if (typeof formatter === "string") {
1024
1211
  tooltip.formatter = formatter;
@@ -1562,6 +1749,7 @@ function downloadCsv(table, filename) {
1562
1749
  );
1563
1750
  }
1564
1751
 
1752
+ exports.DEFAULT_FORECAST_LABEL = DEFAULT_FORECAST_LABEL;
1565
1753
  exports.THEME_DARK = THEME_DARK;
1566
1754
  exports.THEME_LIGHT = THEME_LIGHT;
1567
1755
  exports.abbreviateNumber = abbreviateNumber;
@@ -1589,9 +1777,12 @@ exports.computeDelta = computeDelta;
1589
1777
  exports.createTooltipFormatter = createTooltipFormatter;
1590
1778
  exports.darkChartColors = darkChartColors;
1591
1779
  exports.darkTheme = darkTheme;
1780
+ exports.describeForecast = describeForecast;
1592
1781
  exports.downloadCsv = downloadCsv;
1593
1782
  exports.downloadFile = downloadFile;
1594
1783
  exports.escapeHtml = escapeHtml;
1784
+ exports.forecastStart = forecastStart;
1785
+ exports.forecastTagLookup = forecastTagLookup;
1595
1786
  exports.formatCurrency = formatCurrency;
1596
1787
  exports.formatDate = formatDate;
1597
1788
  exports.formatNumber = formatNumber;
@@ -1618,6 +1809,7 @@ exports.getChartColors = getChartColors;
1618
1809
  exports.getLegendItems = getLegendItems;
1619
1810
  exports.getReplaceMerge = getReplaceMerge;
1620
1811
  exports.gridInset = gridInset;
1812
+ exports.hasForecast = hasForecast;
1621
1813
  exports.hasPointLabels = hasPointLabels;
1622
1814
  exports.heatmapTable = heatmapTable;
1623
1815
  exports.histogramTable = histogramTable;
@@ -1649,6 +1841,9 @@ exports.valueLabelFormatter = valueLabelFormatter;
1649
1841
  exports.waterfallLayout = waterfallLayout;
1650
1842
  exports.waterfallTable = waterfallTable;
1651
1843
  exports.withEndLabelInset = withEndLabelInset;
1844
+ exports.withForecastBars = withForecastBars;
1845
+ exports.withForecastLines = withForecastLines;
1846
+ exports.withForecastPoints = withForecastPoints;
1652
1847
  exports.withPointLabels = withPointLabels;
1653
1848
  exports.withReferenceMarks = withReferenceMarks;
1654
1849
  exports.withZoomInset = withZoomInset;
package/dist/index.d.cts CHANGED
@@ -125,8 +125,12 @@ declare function generateDataTable(headers: string[], rows: (string | number)[][
125
125
  * rebuilds every component, so legend selection, dataZoom ranges and other
126
126
  * interaction state reset on each data update. Instead this replaces only:
127
127
  *
128
- * - `series` and `graphic`, so removed series, mark lines and centre labels
129
- * disappear;
128
+ * - `graphic`, so removed centre labels disappear;
129
+ * - `series`, unless every series keeps its keys and type and only its
130
+ * values change. A replaced series is a new model, which ECharts animates
131
+ * in from scratch, so a data refresh with the same shape (often the same
132
+ * data) must merge to avoid re-animating the chart; a changed shape is
133
+ * replaced so removed series, mark lines and options disappear;
130
134
  * - any component type that `next` has fewer of than `prev` (a hidden legend
131
135
  * or tooltip, a dropped volume grid and its axes).
132
136
  *
@@ -147,6 +151,71 @@ declare function getReplaceMerge(prev: object | null | undefined, next: object,
147
151
  */
148
152
  declare function mergeChartOption<T extends object>(generated: T, override: object | undefined): T;
149
153
 
154
+ /**
155
+ * Forecast stretches: the tail of a series whose values are projections (or a
156
+ * period still in progress, such as the current month). Lines draw it dashed
157
+ * with hollow points, bars draw it faded with a dashed outline, and the
158
+ * tooltip tags its values.
159
+ */
160
+ type AnyObject$1 = Record<string, any>;
161
+ declare const DEFAULT_FORECAST_LABEL = "Forecast";
162
+ /** The forecast fields a series may carry */
163
+ interface ForecastSource {
164
+ data: readonly unknown[];
165
+ forecastFrom?: number;
166
+ forecastLabel?: string;
167
+ }
168
+ /**
169
+ * Index of a series' first forecast point, or `undefined` when it has none
170
+ * (no `forecastFrom`, or one past the end of the data).
171
+ */
172
+ declare function forecastStart(source: ForecastSource): number | undefined;
173
+ /** Whether any series has a forecast stretch */
174
+ declare function hasForecast(series: readonly ForecastSource[] | undefined): boolean;
175
+ /**
176
+ * Tooltip tag by data index: the label of the first series forecasting at
177
+ * that index. ECharts' `valueFormatter` only knows the index, so series that
178
+ * share an index share its tag.
179
+ */
180
+ declare function forecastTagLookup(series: readonly ForecastSource[]): ((dataIndex: number) => string | undefined) | undefined;
181
+ /**
182
+ * The first category where any series' forecast starts, for accessible
183
+ * descriptions
184
+ */
185
+ declare function describeForecast(categories: readonly string[], series: readonly ForecastSource[]): string | undefined;
186
+ /**
187
+ * Line forecast points: hollow, a little larger than the series' own points.
188
+ * Returns the data unchanged when the series has no forecast.
189
+ */
190
+ declare function withForecastPoints(data: readonly unknown[], source: ForecastSource,
191
+ /** The series' own point size (0 when its points are hidden) */
192
+ pointSize?: number): unknown[];
193
+ /**
194
+ * Bar forecast items: faded, with a dashed outline in the series colour.
195
+ * Returns the data unchanged when the series has no forecast.
196
+ */
197
+ declare function withForecastBars(data: readonly unknown[], source: ForecastSource, color: string): unknown[];
198
+ interface ForecastLineOptions {
199
+ /** Resolved colour per series (explicit colour, else the theme palette) */
200
+ colors: readonly string[];
201
+ }
202
+ /**
203
+ * Draw line series' forecast stretches dashed.
204
+ *
205
+ * ECharts styles a line as a whole, so a series with a forecast keeps its
206
+ * full data for the tooltip, points and labels but hides its own line; two
207
+ * silent companions (same name, so the legend toggles them together) draw
208
+ * it: a solid one up to the last actual point, and a dashed one from there
209
+ * to the end. Series stacked together split at the same index (the earliest
210
+ * forecast in the stack) and stack in parallel groups, so the companions'
211
+ * totals match the series'.
212
+ *
213
+ * Series that forecast from their first point are simply dashed. Returns
214
+ * the series unchanged when none has a forecast. Apply reference marks
215
+ * before this, so they attach to the series rather than the companions.
216
+ */
217
+ declare function withForecastLines<S extends AnyObject$1>(series: S[], sources: readonly ForecastSource[], { colors }: ForecastLineOptions): AnyObject$1[];
218
+
150
219
  /**
151
220
  * Builders for the label, axis, tooltip and legend parts of a chart option.
152
221
  *
@@ -264,11 +333,13 @@ interface BuildTooltipOptions {
264
333
  valueFormatter?: (value: number) => string;
265
334
  /** Per-point label lookup, exposed to formatter functions as `label` */
266
335
  getLabel?: PointLabelLookup;
336
+ /** Tag for forecast values by data index (see `forecastTagLookup`) */
337
+ forecastTag?: (dataIndex: number) => string | undefined;
267
338
  }
268
339
  /**
269
340
  * Build the `tooltip` component. `base` is the chart's default tooltip.
270
341
  */
271
- declare function buildTooltip(base: AnyObject, { show, formatter, valueFormatter, getLabel }: BuildTooltipOptions): AnyObject | undefined;
342
+ declare function buildTooltip(base: AnyObject, { show, formatter, valueFormatter, getLabel, forecastTag }: BuildTooltipOptions): AnyObject | undefined;
272
343
  /**
273
344
  * Format a value with `tooltipValueFormatter`, for charts whose default
274
345
  * tooltip is custom HTML (where ECharts' `valueFormatter` does not apply)
@@ -673,4 +744,4 @@ declare function downloadFile(content: string, filename: string, mimeType?: stri
673
744
  */
674
745
  declare function downloadCsv(table: ChartDataTable, filename: string): void;
675
746
 
676
- export { type Accessor, type Aggregate, type AutoHeightOptions, AxisConfig, BarChartData, type BinValuesOptions, BoxPlotStats, type BuildDataLabelOptions, type BuildLegendOptions, type BuildTooltipOptions, BulletItem, CandlestickData, CategoryChartData, CategorySeriesData, ChartDataTable, ChartFormatter, type ChartFormatterOptions, type ChartFormatters, ChartLegendConfig, type Delta, type FromRecordsOptions, HeatmapData, HistogramBin, HistogramBucket, LabelFormatterParams, LegendItem, LineChartData, PieSliceData, type PointLabelLookup, RadarChartData, RadarIndicator, ReferenceBand, ReferenceLine, type ReferenceMarkOptions, SankeyData, ScatterChartData, type StackTotalOptions, type ToSliceDataOptions, TooltipFormatterParams, TreemapNode, ValueFormat, type WaterfallBar, WaterfallStep, abbreviateNumber, allSeriesEmpty, anchorCenterGraphic, barCornerRadius, binValues, boxPlotTable, boxplotStats, buildAxis, buildDataLabel, buildDataZoom, buildLegend, buildMarkArea, buildMarkLine, buildStackTotalSeries, buildTooltip, bulletTable, candlestickTable, categoryTable, chartFilename, chartFormatters, computeDelta, createTooltipFormatter, downloadCsv, downloadFile, escapeHtml, formatCurrency, formatDate, formatNumber, formatPercent, formatTableCell, formatTooltipValue, formatValue, fromRecords, generateAreaChartDescription, generateBarChartDescription, generateBoxPlotDescription, generateBulletDescription, generateComboChartDescription, generateDataTable, generateDotPlotDescription, generateHistogramDescription, generateLineChartDescription, generatePieChartDescription, generateScatterChartDescription, generateWaterfallDescription, getLegendItems, getReplaceMerge, gridInset, hasPointLabels, heatmapTable, histogramTable, itemLabelLookup, legendCenter, mergeChartOption, nameLabelLookup, niceStep, pairWithCategories, pointLabel, quantile, radarTable, resolveChartHeight, resolveValueFormatter, sankeyTable, scatterTable, seriesLabelLookup, sliceTable, stackTotals, tableToCsv, toPercentStack, toSliceData, treemapTable, trendLine, valueAxisConfig, valueLabelFormatter, waterfallLayout, waterfallTable, withEndLabelInset, withPointLabels, withReferenceMarks, withZoomInset };
747
+ export { type Accessor, type Aggregate, type AutoHeightOptions, AxisConfig, BarChartData, type BinValuesOptions, BoxPlotStats, type BuildDataLabelOptions, type BuildLegendOptions, type BuildTooltipOptions, BulletItem, CandlestickData, CategoryChartData, CategorySeriesData, ChartDataTable, ChartFormatter, type ChartFormatterOptions, type ChartFormatters, ChartLegendConfig, DEFAULT_FORECAST_LABEL, type Delta, type ForecastLineOptions, type ForecastSource, type FromRecordsOptions, HeatmapData, HistogramBin, HistogramBucket, LabelFormatterParams, LegendItem, LineChartData, PieSliceData, type PointLabelLookup, RadarChartData, RadarIndicator, ReferenceBand, ReferenceLine, type ReferenceMarkOptions, SankeyData, ScatterChartData, type StackTotalOptions, type ToSliceDataOptions, TooltipFormatterParams, TreemapNode, ValueFormat, type WaterfallBar, WaterfallStep, abbreviateNumber, allSeriesEmpty, anchorCenterGraphic, barCornerRadius, binValues, boxPlotTable, boxplotStats, buildAxis, buildDataLabel, buildDataZoom, buildLegend, buildMarkArea, buildMarkLine, buildStackTotalSeries, buildTooltip, bulletTable, candlestickTable, categoryTable, chartFilename, chartFormatters, computeDelta, createTooltipFormatter, describeForecast, downloadCsv, downloadFile, escapeHtml, forecastStart, forecastTagLookup, formatCurrency, formatDate, formatNumber, formatPercent, formatTableCell, formatTooltipValue, formatValue, fromRecords, generateAreaChartDescription, generateBarChartDescription, generateBoxPlotDescription, generateBulletDescription, generateComboChartDescription, generateDataTable, generateDotPlotDescription, generateHistogramDescription, generateLineChartDescription, generatePieChartDescription, generateScatterChartDescription, generateWaterfallDescription, getLegendItems, getReplaceMerge, gridInset, hasForecast, hasPointLabels, heatmapTable, histogramTable, itemLabelLookup, legendCenter, mergeChartOption, nameLabelLookup, niceStep, pairWithCategories, pointLabel, quantile, radarTable, resolveChartHeight, resolveValueFormatter, sankeyTable, scatterTable, seriesLabelLookup, sliceTable, stackTotals, tableToCsv, toPercentStack, toSliceData, treemapTable, trendLine, valueAxisConfig, valueLabelFormatter, waterfallLayout, waterfallTable, withEndLabelInset, withForecastBars, withForecastLines, withForecastPoints, withPointLabels, withReferenceMarks, withZoomInset };
package/dist/index.d.ts CHANGED
@@ -125,8 +125,12 @@ declare function generateDataTable(headers: string[], rows: (string | number)[][
125
125
  * rebuilds every component, so legend selection, dataZoom ranges and other
126
126
  * interaction state reset on each data update. Instead this replaces only:
127
127
  *
128
- * - `series` and `graphic`, so removed series, mark lines and centre labels
129
- * disappear;
128
+ * - `graphic`, so removed centre labels disappear;
129
+ * - `series`, unless every series keeps its keys and type and only its
130
+ * values change. A replaced series is a new model, which ECharts animates
131
+ * in from scratch, so a data refresh with the same shape (often the same
132
+ * data) must merge to avoid re-animating the chart; a changed shape is
133
+ * replaced so removed series, mark lines and options disappear;
130
134
  * - any component type that `next` has fewer of than `prev` (a hidden legend
131
135
  * or tooltip, a dropped volume grid and its axes).
132
136
  *
@@ -147,6 +151,71 @@ declare function getReplaceMerge(prev: object | null | undefined, next: object,
147
151
  */
148
152
  declare function mergeChartOption<T extends object>(generated: T, override: object | undefined): T;
149
153
 
154
+ /**
155
+ * Forecast stretches: the tail of a series whose values are projections (or a
156
+ * period still in progress, such as the current month). Lines draw it dashed
157
+ * with hollow points, bars draw it faded with a dashed outline, and the
158
+ * tooltip tags its values.
159
+ */
160
+ type AnyObject$1 = Record<string, any>;
161
+ declare const DEFAULT_FORECAST_LABEL = "Forecast";
162
+ /** The forecast fields a series may carry */
163
+ interface ForecastSource {
164
+ data: readonly unknown[];
165
+ forecastFrom?: number;
166
+ forecastLabel?: string;
167
+ }
168
+ /**
169
+ * Index of a series' first forecast point, or `undefined` when it has none
170
+ * (no `forecastFrom`, or one past the end of the data).
171
+ */
172
+ declare function forecastStart(source: ForecastSource): number | undefined;
173
+ /** Whether any series has a forecast stretch */
174
+ declare function hasForecast(series: readonly ForecastSource[] | undefined): boolean;
175
+ /**
176
+ * Tooltip tag by data index: the label of the first series forecasting at
177
+ * that index. ECharts' `valueFormatter` only knows the index, so series that
178
+ * share an index share its tag.
179
+ */
180
+ declare function forecastTagLookup(series: readonly ForecastSource[]): ((dataIndex: number) => string | undefined) | undefined;
181
+ /**
182
+ * The first category where any series' forecast starts, for accessible
183
+ * descriptions
184
+ */
185
+ declare function describeForecast(categories: readonly string[], series: readonly ForecastSource[]): string | undefined;
186
+ /**
187
+ * Line forecast points: hollow, a little larger than the series' own points.
188
+ * Returns the data unchanged when the series has no forecast.
189
+ */
190
+ declare function withForecastPoints(data: readonly unknown[], source: ForecastSource,
191
+ /** The series' own point size (0 when its points are hidden) */
192
+ pointSize?: number): unknown[];
193
+ /**
194
+ * Bar forecast items: faded, with a dashed outline in the series colour.
195
+ * Returns the data unchanged when the series has no forecast.
196
+ */
197
+ declare function withForecastBars(data: readonly unknown[], source: ForecastSource, color: string): unknown[];
198
+ interface ForecastLineOptions {
199
+ /** Resolved colour per series (explicit colour, else the theme palette) */
200
+ colors: readonly string[];
201
+ }
202
+ /**
203
+ * Draw line series' forecast stretches dashed.
204
+ *
205
+ * ECharts styles a line as a whole, so a series with a forecast keeps its
206
+ * full data for the tooltip, points and labels but hides its own line; two
207
+ * silent companions (same name, so the legend toggles them together) draw
208
+ * it: a solid one up to the last actual point, and a dashed one from there
209
+ * to the end. Series stacked together split at the same index (the earliest
210
+ * forecast in the stack) and stack in parallel groups, so the companions'
211
+ * totals match the series'.
212
+ *
213
+ * Series that forecast from their first point are simply dashed. Returns
214
+ * the series unchanged when none has a forecast. Apply reference marks
215
+ * before this, so they attach to the series rather than the companions.
216
+ */
217
+ declare function withForecastLines<S extends AnyObject$1>(series: S[], sources: readonly ForecastSource[], { colors }: ForecastLineOptions): AnyObject$1[];
218
+
150
219
  /**
151
220
  * Builders for the label, axis, tooltip and legend parts of a chart option.
152
221
  *
@@ -264,11 +333,13 @@ interface BuildTooltipOptions {
264
333
  valueFormatter?: (value: number) => string;
265
334
  /** Per-point label lookup, exposed to formatter functions as `label` */
266
335
  getLabel?: PointLabelLookup;
336
+ /** Tag for forecast values by data index (see `forecastTagLookup`) */
337
+ forecastTag?: (dataIndex: number) => string | undefined;
267
338
  }
268
339
  /**
269
340
  * Build the `tooltip` component. `base` is the chart's default tooltip.
270
341
  */
271
- declare function buildTooltip(base: AnyObject, { show, formatter, valueFormatter, getLabel }: BuildTooltipOptions): AnyObject | undefined;
342
+ declare function buildTooltip(base: AnyObject, { show, formatter, valueFormatter, getLabel, forecastTag }: BuildTooltipOptions): AnyObject | undefined;
272
343
  /**
273
344
  * Format a value with `tooltipValueFormatter`, for charts whose default
274
345
  * tooltip is custom HTML (where ECharts' `valueFormatter` does not apply)
@@ -673,4 +744,4 @@ declare function downloadFile(content: string, filename: string, mimeType?: stri
673
744
  */
674
745
  declare function downloadCsv(table: ChartDataTable, filename: string): void;
675
746
 
676
- export { type Accessor, type Aggregate, type AutoHeightOptions, AxisConfig, BarChartData, type BinValuesOptions, BoxPlotStats, type BuildDataLabelOptions, type BuildLegendOptions, type BuildTooltipOptions, BulletItem, CandlestickData, CategoryChartData, CategorySeriesData, ChartDataTable, ChartFormatter, type ChartFormatterOptions, type ChartFormatters, ChartLegendConfig, type Delta, type FromRecordsOptions, HeatmapData, HistogramBin, HistogramBucket, LabelFormatterParams, LegendItem, LineChartData, PieSliceData, type PointLabelLookup, RadarChartData, RadarIndicator, ReferenceBand, ReferenceLine, type ReferenceMarkOptions, SankeyData, ScatterChartData, type StackTotalOptions, type ToSliceDataOptions, TooltipFormatterParams, TreemapNode, ValueFormat, type WaterfallBar, WaterfallStep, abbreviateNumber, allSeriesEmpty, anchorCenterGraphic, barCornerRadius, binValues, boxPlotTable, boxplotStats, buildAxis, buildDataLabel, buildDataZoom, buildLegend, buildMarkArea, buildMarkLine, buildStackTotalSeries, buildTooltip, bulletTable, candlestickTable, categoryTable, chartFilename, chartFormatters, computeDelta, createTooltipFormatter, downloadCsv, downloadFile, escapeHtml, formatCurrency, formatDate, formatNumber, formatPercent, formatTableCell, formatTooltipValue, formatValue, fromRecords, generateAreaChartDescription, generateBarChartDescription, generateBoxPlotDescription, generateBulletDescription, generateComboChartDescription, generateDataTable, generateDotPlotDescription, generateHistogramDescription, generateLineChartDescription, generatePieChartDescription, generateScatterChartDescription, generateWaterfallDescription, getLegendItems, getReplaceMerge, gridInset, hasPointLabels, heatmapTable, histogramTable, itemLabelLookup, legendCenter, mergeChartOption, nameLabelLookup, niceStep, pairWithCategories, pointLabel, quantile, radarTable, resolveChartHeight, resolveValueFormatter, sankeyTable, scatterTable, seriesLabelLookup, sliceTable, stackTotals, tableToCsv, toPercentStack, toSliceData, treemapTable, trendLine, valueAxisConfig, valueLabelFormatter, waterfallLayout, waterfallTable, withEndLabelInset, withPointLabels, withReferenceMarks, withZoomInset };
747
+ export { type Accessor, type Aggregate, type AutoHeightOptions, AxisConfig, BarChartData, type BinValuesOptions, BoxPlotStats, type BuildDataLabelOptions, type BuildLegendOptions, type BuildTooltipOptions, BulletItem, CandlestickData, CategoryChartData, CategorySeriesData, ChartDataTable, ChartFormatter, type ChartFormatterOptions, type ChartFormatters, ChartLegendConfig, DEFAULT_FORECAST_LABEL, type Delta, type ForecastLineOptions, type ForecastSource, type FromRecordsOptions, HeatmapData, HistogramBin, HistogramBucket, LabelFormatterParams, LegendItem, LineChartData, PieSliceData, type PointLabelLookup, RadarChartData, RadarIndicator, ReferenceBand, ReferenceLine, type ReferenceMarkOptions, SankeyData, ScatterChartData, type StackTotalOptions, type ToSliceDataOptions, TooltipFormatterParams, TreemapNode, ValueFormat, type WaterfallBar, WaterfallStep, abbreviateNumber, allSeriesEmpty, anchorCenterGraphic, barCornerRadius, binValues, boxPlotTable, boxplotStats, buildAxis, buildDataLabel, buildDataZoom, buildLegend, buildMarkArea, buildMarkLine, buildStackTotalSeries, buildTooltip, bulletTable, candlestickTable, categoryTable, chartFilename, chartFormatters, computeDelta, createTooltipFormatter, describeForecast, downloadCsv, downloadFile, escapeHtml, forecastStart, forecastTagLookup, formatCurrency, formatDate, formatNumber, formatPercent, formatTableCell, formatTooltipValue, formatValue, fromRecords, generateAreaChartDescription, generateBarChartDescription, generateBoxPlotDescription, generateBulletDescription, generateComboChartDescription, generateDataTable, generateDotPlotDescription, generateHistogramDescription, generateLineChartDescription, generatePieChartDescription, generateScatterChartDescription, generateWaterfallDescription, getLegendItems, getReplaceMerge, gridInset, hasForecast, hasPointLabels, heatmapTable, histogramTable, itemLabelLookup, legendCenter, mergeChartOption, nameLabelLookup, niceStep, pairWithCategories, pointLabel, quantile, radarTable, resolveChartHeight, resolveValueFormatter, sankeyTable, scatterTable, seriesLabelLookup, sliceTable, stackTotals, tableToCsv, toPercentStack, toSliceData, treemapTable, trendLine, valueAxisConfig, valueLabelFormatter, waterfallLayout, waterfallTable, withEndLabelInset, withForecastBars, withForecastLines, withForecastPoints, withPointLabels, withReferenceMarks, withZoomInset };
package/dist/index.js CHANGED
@@ -103,6 +103,159 @@ function createTooltipFormatter(options = {}) {
103
103
  };
104
104
  }
105
105
 
106
+ // src/utils/forecast.ts
107
+ var DEFAULT_FORECAST_LABEL = "Forecast";
108
+ function forecastStart(source) {
109
+ const from = source.forecastFrom;
110
+ if (from == null || !Number.isFinite(from)) return void 0;
111
+ const index = Math.max(0, Math.floor(from));
112
+ return index < source.data.length ? index : void 0;
113
+ }
114
+ function hasForecast(series) {
115
+ return !!series?.some((s) => forecastStart(s) !== void 0);
116
+ }
117
+ function forecastTagLookup(series) {
118
+ if (!hasForecast(series)) return void 0;
119
+ return (dataIndex) => {
120
+ for (const s of series) {
121
+ const from = forecastStart(s);
122
+ if (from !== void 0 && dataIndex >= from) return s.forecastLabel || DEFAULT_FORECAST_LABEL;
123
+ }
124
+ return void 0;
125
+ };
126
+ }
127
+ function describeForecast(categories, series) {
128
+ let first;
129
+ for (const s of series) {
130
+ const from = forecastStart(s);
131
+ if (from === void 0 || first && first.index <= from) continue;
132
+ first = { index: from, label: s.forecastLabel || DEFAULT_FORECAST_LABEL };
133
+ }
134
+ if (!first) return void 0;
135
+ const category = categories[first.index] ?? "";
136
+ return `Values from ${category} onward are marked ${first.label.toLowerCase()}`;
137
+ }
138
+ var isPlainObject = (item) => typeof item === "object" && item !== null && !Array.isArray(item);
139
+ function blank(item) {
140
+ if (Array.isArray(item)) return [item[0], null];
141
+ return null;
142
+ }
143
+ function bare(item) {
144
+ return isPlainObject(item) && "value" in item ? item.value : item;
145
+ }
146
+ function withItem(item, extra) {
147
+ if (isPlainObject(item) && "value" in item) {
148
+ return {
149
+ ...item,
150
+ ...extra,
151
+ itemStyle: { ...item.itemStyle, ...extra.itemStyle }
152
+ };
153
+ }
154
+ return { value: item, ...extra };
155
+ }
156
+ function withForecastPoints(data, source, pointSize = 0) {
157
+ const from = forecastStart(source);
158
+ if (from === void 0) return [...data];
159
+ const size = Math.max(pointSize, 6) + 1;
160
+ return data.map(
161
+ (item, index) => index >= from ? withItem(item, { symbol: "emptyCircle", symbolSize: size }) : item
162
+ );
163
+ }
164
+ function withForecastBars(data, source, color) {
165
+ const from = forecastStart(source);
166
+ if (from === void 0) return [...data];
167
+ return data.map(
168
+ (item, index) => index >= from ? withItem(item, {
169
+ itemStyle: {
170
+ opacity: 0.45,
171
+ borderColor: color,
172
+ borderWidth: 1.5,
173
+ borderType: "dashed"
174
+ }
175
+ }) : item
176
+ );
177
+ }
178
+ function withForecastLines(series, sources, { colors }) {
179
+ if (!hasForecast(sources)) return series;
180
+ const stackFrom = /* @__PURE__ */ new Map();
181
+ series.forEach((s, index) => {
182
+ const from = forecastStart(sources[index]);
183
+ if (s.stack == null || from === void 0) return;
184
+ stackFrom.set(s.stack, Math.min(stackFrom.get(s.stack) ?? Infinity, from));
185
+ });
186
+ const mains = [];
187
+ const companions = [];
188
+ series.forEach((s, index) => {
189
+ const from = s.stack != null ? stackFrom.get(s.stack) : forecastStart(sources[index]);
190
+ if (from === void 0) {
191
+ mains.push(s);
192
+ return;
193
+ }
194
+ const color = colors[index];
195
+ const itemStyle = { ...s.itemStyle, color };
196
+ if (from === 0) {
197
+ mains.push({
198
+ ...s,
199
+ itemStyle,
200
+ lineStyle: { ...s.lineStyle, type: "dashed" },
201
+ ...s.areaStyle && { areaStyle: fadedArea(s.areaStyle) }
202
+ });
203
+ return;
204
+ }
205
+ const data = s.data ?? [];
206
+ const common = {
207
+ name: s.name,
208
+ type: "line",
209
+ smooth: s.smooth,
210
+ connectNulls: s.connectNulls,
211
+ showSymbol: false,
212
+ symbol: "none",
213
+ silent: true,
214
+ legendHoverLink: false,
215
+ tooltip: { show: false },
216
+ emphasis: { disabled: true },
217
+ itemStyle,
218
+ z: s.z
219
+ };
220
+ companions.push(
221
+ {
222
+ ...common,
223
+ data: data.map((item, i) => i < from ? bare(item) : blank(item)),
224
+ stack: s.stack != null ? `${s.stack}\0actual` : void 0,
225
+ lineStyle: s.lineStyle,
226
+ areaStyle: s.areaStyle
227
+ },
228
+ {
229
+ ...common,
230
+ data: data.map((item, i) => i >= from - 1 ? bare(item) : blank(item)),
231
+ stack: s.stack != null ? `${s.stack}\0forecast` : void 0,
232
+ lineStyle: { ...s.lineStyle, type: "dashed" },
233
+ areaStyle: s.areaStyle ? fadedArea(s.areaStyle) : void 0
234
+ }
235
+ );
236
+ mains.push({
237
+ ...s,
238
+ itemStyle,
239
+ // Forecast points carry their own size; the rest keep theirs (none
240
+ // when the series hid its points)
241
+ showSymbol: true,
242
+ symbolSize: s.showSymbol === false ? 0 : s.symbolSize ?? 4,
243
+ lineStyle: { ...s.lineStyle, opacity: 0 },
244
+ areaStyle: void 0,
245
+ emphasis: {
246
+ ...s.emphasis,
247
+ focus: "none",
248
+ lineStyle: { ...s.emphasis?.lineStyle, opacity: 0 }
249
+ },
250
+ z: (s.z ?? 2) + 1
251
+ });
252
+ });
253
+ return [...mains, ...companions];
254
+ }
255
+ function fadedArea(areaStyle) {
256
+ return { ...areaStyle, opacity: (areaStyle.opacity ?? 0.7) * 0.5 };
257
+ }
258
+
106
259
  // src/utils/accessibility.ts
107
260
  var isNumber = (value) => typeof value === "number" && Number.isFinite(value);
108
261
  function seriesList(names) {
@@ -128,6 +281,8 @@ function describeTrends(title, kind, data, format) {
128
281
  0
129
282
  );
130
283
  if (missing > 0) description += `. ${missing} values are missing`;
284
+ const forecast = describeForecast(data.categories, data.series);
285
+ if (forecast) description += `. ${forecast}`;
131
286
  return description;
132
287
  }
133
288
  function generateLineChartDescription(title, data, format) {
@@ -163,6 +318,8 @@ function generateBarChartDescription(title, data, format) {
163
318
  if (max && min) {
164
319
  description += `. Highest value is ${formatValue(max.value, format)} for ${max.category}. Lowest value is ${formatValue(min.value, format)} for ${min.category}`;
165
320
  }
321
+ const forecast = describeForecast(data.categories, data.series);
322
+ if (forecast) description += `. ${forecast}`;
166
323
  return description;
167
324
  }
168
325
  function generateDotPlotDescription(title, data, format) {
@@ -252,14 +409,39 @@ function generateDataTable(headers, rows) {
252
409
  }
253
410
 
254
411
  // src/utils/merge.ts
255
- var ALWAYS_REPLACE = ["series", "graphic"];
412
+ var ALWAYS_REPLACE = ["graphic"];
256
413
  function countComponents(value) {
257
414
  if (value == null || value === false) return 0;
258
415
  if (Array.isArray(value)) return value.filter((item) => item != null).length;
259
416
  return 1;
260
417
  }
418
+ function isRecord(value) {
419
+ return value !== null && typeof value === "object" && !Array.isArray(value);
420
+ }
421
+ function sameShape(a, b) {
422
+ if (!isRecord(a) || !isRecord(b)) return !isRecord(a) && !isRecord(b);
423
+ const keys = Object.keys(a).filter((key) => a[key] !== void 0);
424
+ const other = Object.keys(b).filter((key) => b[key] !== void 0);
425
+ if (keys.length !== other.length) return false;
426
+ return keys.every(
427
+ (key) => key in b && b[key] !== void 0 && (key === "type" || key === "id" ? a[key] === b[key] : sameShape(a[key], b[key]))
428
+ );
429
+ }
430
+ function asList(value) {
431
+ if (value == null || value === false) return [];
432
+ return Array.isArray(value) ? value : [value];
433
+ }
434
+ function sameSeriesShape(prev, next) {
435
+ const before = asList(prev);
436
+ const after = asList(next);
437
+ return before.length === after.length && before.every((item, i) => sameShape(item, after[i]));
438
+ }
261
439
  function getReplaceMerge(prev, next, isComponentType) {
262
440
  const types = new Set(ALWAYS_REPLACE);
441
+ const prior = prev;
442
+ if (!prior || !sameSeriesShape(prior.series, next.series)) {
443
+ types.add("series");
444
+ }
263
445
  if (prev) {
264
446
  const before = prev;
265
447
  const after = next;
@@ -271,7 +453,7 @@ function getReplaceMerge(prev, next, isComponentType) {
271
453
  }
272
454
  return [...types].filter((type) => isComponentType(type));
273
455
  }
274
- function isPlainObject(value) {
456
+ function isPlainObject2(value) {
275
457
  if (value === null || typeof value !== "object") return false;
276
458
  const proto = Object.getPrototypeOf(value);
277
459
  return proto === Object.prototype || proto === null;
@@ -284,7 +466,7 @@ function mergeValue(base, override, topLevel = false) {
284
466
  const length = Math.max(from.length, to.length);
285
467
  return Array.from({ length }, (_, index) => mergeValue(from[index], to[index]));
286
468
  }
287
- if (isPlainObject(base) && isPlainObject(override)) {
469
+ if (isPlainObject2(base) && isPlainObject2(override)) {
288
470
  const merged = { ...base };
289
471
  for (const key of Object.keys(override)) {
290
472
  merged[key] = mergeValue(base[key], override[key]);
@@ -692,12 +874,17 @@ function nameLabelLookup(nodes) {
692
874
  function hasPointLabels(series) {
693
875
  return series.some((s) => s.labels?.some((text) => text != null && text !== ""));
694
876
  }
695
- function buildTooltip(base, { show, formatter, valueFormatter, getLabel }) {
877
+ function buildTooltip(base, { show, formatter, valueFormatter, getLabel, forecastTag }) {
696
878
  if (!show) return void 0;
697
- if (formatter === void 0 && valueFormatter === void 0) return base;
879
+ if (formatter === void 0 && valueFormatter === void 0 && !forecastTag) return base;
698
880
  const tooltip = { ...base };
699
- if (valueFormatter) {
700
- tooltip.valueFormatter = (value) => typeof value === "number" ? valueFormatter(value) : String(value ?? "");
881
+ if (valueFormatter || forecastTag) {
882
+ const format = (value) => typeof value === "number" ? valueFormatter ? valueFormatter(value) : value.toLocaleString() : String(value ?? "");
883
+ tooltip.valueFormatter = (value, dataIndex) => {
884
+ const text = format(value);
885
+ const tag = dataIndex === void 0 ? void 0 : forecastTag?.(dataIndex);
886
+ return tag && value != null ? `${text} (${tag})` : text;
887
+ };
701
888
  }
702
889
  if (typeof formatter === "string") {
703
890
  tooltip.formatter = formatter;
@@ -1241,4 +1428,4 @@ function downloadCsv(table, filename) {
1241
1428
  );
1242
1429
  }
1243
1430
 
1244
- export { abbreviateNumber, allSeriesEmpty, anchorCenterGraphic, barCornerRadius, binValues, boxPlotTable, boxplotStats, buildAxis, buildDataLabel, buildDataZoom, buildLegend, buildMarkArea, buildMarkLine, buildStackTotalSeries, buildTooltip, bulletTable, candlestickTable, categoryTable, chartFilename, chartFormatters, computeDelta, createTooltipFormatter, downloadCsv, downloadFile, escapeHtml, formatCurrency, formatDate, formatNumber, formatPercent, formatTableCell, formatTooltipValue, formatValue, fromRecords, generateAreaChartDescription, generateBarChartDescription, generateBoxPlotDescription, generateBulletDescription, generateComboChartDescription, generateDataTable, generateDotPlotDescription, generateHistogramDescription, generateLineChartDescription, generatePieChartDescription, generateScatterChartDescription, generateWaterfallDescription, getLegendItems, getReplaceMerge, gridInset, hasPointLabels, heatmapTable, histogramTable, itemLabelLookup, legendCenter, mergeChartOption, nameLabelLookup, niceStep, pairWithCategories, pointLabel, quantile, radarTable, resolveChartHeight, resolveValueFormatter, sankeyTable, scatterTable, seriesLabelLookup, sliceTable, stackTotals, tableToCsv, toPercentStack, toSliceData, treemapTable, trendLine, valueAxisConfig, valueLabelFormatter, waterfallLayout, waterfallTable, withEndLabelInset, withPointLabels, withReferenceMarks, withZoomInset };
1431
+ export { DEFAULT_FORECAST_LABEL, abbreviateNumber, allSeriesEmpty, anchorCenterGraphic, barCornerRadius, binValues, boxPlotTable, boxplotStats, buildAxis, buildDataLabel, buildDataZoom, buildLegend, buildMarkArea, buildMarkLine, buildStackTotalSeries, buildTooltip, bulletTable, candlestickTable, categoryTable, chartFilename, chartFormatters, computeDelta, createTooltipFormatter, describeForecast, downloadCsv, downloadFile, escapeHtml, forecastStart, forecastTagLookup, formatCurrency, formatDate, formatNumber, formatPercent, formatTableCell, formatTooltipValue, formatValue, fromRecords, generateAreaChartDescription, generateBarChartDescription, generateBoxPlotDescription, generateBulletDescription, generateComboChartDescription, generateDataTable, generateDotPlotDescription, generateHistogramDescription, generateLineChartDescription, generatePieChartDescription, generateScatterChartDescription, generateWaterfallDescription, getLegendItems, getReplaceMerge, gridInset, hasForecast, hasPointLabels, heatmapTable, histogramTable, itemLabelLookup, legendCenter, mergeChartOption, nameLabelLookup, niceStep, pairWithCategories, pointLabel, quantile, radarTable, resolveChartHeight, resolveValueFormatter, sankeyTable, scatterTable, seriesLabelLookup, sliceTable, stackTotals, tableToCsv, toPercentStack, toSliceData, treemapTable, trendLine, valueAxisConfig, valueLabelFormatter, waterfallLayout, waterfallTable, withEndLabelInset, withForecastBars, withForecastLines, withForecastPoints, withPointLabels, withReferenceMarks, withZoomInset };
@@ -24,6 +24,15 @@ interface LineSeriesData {
24
24
  * chart's `stacked` prop for this series.
25
25
  */
26
26
  stack?: string;
27
+ /**
28
+ * Index of the first forecast point: from here to the end of the series the
29
+ * values are projections (or a period still in progress). Lines draw that
30
+ * stretch dashed with hollow points, bars draw those bars faded with a
31
+ * dashed outline, and the tooltip tags those values with `forecastLabel`.
32
+ */
33
+ forecastFrom?: number;
34
+ /** Tooltip tag for forecast values (defaults to 'Forecast') */
35
+ forecastLabel?: string;
27
36
  }
28
37
  interface BarChartData {
29
38
  /** Category labels */
@@ -45,6 +54,15 @@ interface BarSeriesData {
45
54
  * chart's `stacked` prop for this series.
46
55
  */
47
56
  stack?: string;
57
+ /**
58
+ * Index of the first forecast point: from here to the end of the series the
59
+ * values are projections (or a period still in progress). Lines draw that
60
+ * stretch dashed with hollow points, bars draw those bars faded with a
61
+ * dashed outline, and the tooltip tags those values with `forecastLabel`.
62
+ */
63
+ forecastFrom?: number;
64
+ /** Tooltip tag for forecast values (defaults to 'Forecast') */
65
+ forecastLabel?: string;
48
66
  }
49
67
  interface PieSliceData {
50
68
  /** Slice name (shown in legend/tooltip) */
@@ -76,6 +94,15 @@ interface AreaSeriesData {
76
94
  * chart's `stacked` prop for this series.
77
95
  */
78
96
  stack?: string;
97
+ /**
98
+ * Index of the first forecast point: from here to the end of the series the
99
+ * values are projections (or a period still in progress). Lines draw that
100
+ * stretch dashed with hollow points, bars draw those bars faded with a
101
+ * dashed outline, and the tooltip tags those values with `forecastLabel`.
102
+ */
103
+ forecastFrom?: number;
104
+ /** Tooltip tag for forecast values (defaults to 'Forecast') */
105
+ forecastLabel?: string;
79
106
  }
80
107
  interface ScatterChartData {
81
108
  /** Data series */
@@ -216,6 +243,15 @@ interface CategorySeriesData {
216
243
  labels?: (string | null | undefined)[];
217
244
  /** Stack group id */
218
245
  stack?: string;
246
+ /**
247
+ * Index of the first forecast point: from here to the end of the series the
248
+ * values are projections (or a period still in progress). Lines draw that
249
+ * stretch dashed with hollow points, bars draw those bars faded with a
250
+ * dashed outline, and the tooltip tags those values with `forecastLabel`.
251
+ */
252
+ forecastFrom?: number;
253
+ /** Tooltip tag for forecast values (defaults to 'Forecast') */
254
+ forecastLabel?: string;
219
255
  }
220
256
  interface ComboChartData {
221
257
  /** Category labels */
@@ -24,6 +24,15 @@ interface LineSeriesData {
24
24
  * chart's `stacked` prop for this series.
25
25
  */
26
26
  stack?: string;
27
+ /**
28
+ * Index of the first forecast point: from here to the end of the series the
29
+ * values are projections (or a period still in progress). Lines draw that
30
+ * stretch dashed with hollow points, bars draw those bars faded with a
31
+ * dashed outline, and the tooltip tags those values with `forecastLabel`.
32
+ */
33
+ forecastFrom?: number;
34
+ /** Tooltip tag for forecast values (defaults to 'Forecast') */
35
+ forecastLabel?: string;
27
36
  }
28
37
  interface BarChartData {
29
38
  /** Category labels */
@@ -45,6 +54,15 @@ interface BarSeriesData {
45
54
  * chart's `stacked` prop for this series.
46
55
  */
47
56
  stack?: string;
57
+ /**
58
+ * Index of the first forecast point: from here to the end of the series the
59
+ * values are projections (or a period still in progress). Lines draw that
60
+ * stretch dashed with hollow points, bars draw those bars faded with a
61
+ * dashed outline, and the tooltip tags those values with `forecastLabel`.
62
+ */
63
+ forecastFrom?: number;
64
+ /** Tooltip tag for forecast values (defaults to 'Forecast') */
65
+ forecastLabel?: string;
48
66
  }
49
67
  interface PieSliceData {
50
68
  /** Slice name (shown in legend/tooltip) */
@@ -76,6 +94,15 @@ interface AreaSeriesData {
76
94
  * chart's `stacked` prop for this series.
77
95
  */
78
96
  stack?: string;
97
+ /**
98
+ * Index of the first forecast point: from here to the end of the series the
99
+ * values are projections (or a period still in progress). Lines draw that
100
+ * stretch dashed with hollow points, bars draw those bars faded with a
101
+ * dashed outline, and the tooltip tags those values with `forecastLabel`.
102
+ */
103
+ forecastFrom?: number;
104
+ /** Tooltip tag for forecast values (defaults to 'Forecast') */
105
+ forecastLabel?: string;
79
106
  }
80
107
  interface ScatterChartData {
81
108
  /** Data series */
@@ -216,6 +243,15 @@ interface CategorySeriesData {
216
243
  labels?: (string | null | undefined)[];
217
244
  /** Stack group id */
218
245
  stack?: string;
246
+ /**
247
+ * Index of the first forecast point: from here to the end of the series the
248
+ * values are projections (or a period still in progress). Lines draw that
249
+ * stretch dashed with hollow points, bars draw those bars faded with a
250
+ * dashed outline, and the tooltip tags those values with `forecastLabel`.
251
+ */
252
+ forecastFrom?: number;
253
+ /** Tooltip tag for forecast values (defaults to 'Forecast') */
254
+ forecastLabel?: string;
219
255
  }
220
256
  interface ComboChartData {
221
257
  /** Category labels */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classic-homes/charts-core",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Core utilities and theme for Classic Theme charts",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",