@classic-homes/charts-core 0.4.2 → 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) {
@@ -617,7 +774,7 @@ function getReplaceMerge(prev, next, isComponentType) {
617
774
  }
618
775
  return [...types].filter((type) => isComponentType(type));
619
776
  }
620
- function isPlainObject(value) {
777
+ function isPlainObject2(value) {
621
778
  if (value === null || typeof value !== "object") return false;
622
779
  const proto = Object.getPrototypeOf(value);
623
780
  return proto === Object.prototype || proto === null;
@@ -630,7 +787,7 @@ function mergeValue(base, override, topLevel = false) {
630
787
  const length = Math.max(from.length, to.length);
631
788
  return Array.from({ length }, (_, index) => mergeValue(from[index], to[index]));
632
789
  }
633
- if (isPlainObject(base) && isPlainObject(override)) {
790
+ if (isPlainObject2(base) && isPlainObject2(override)) {
634
791
  const merged = { ...base };
635
792
  for (const key of Object.keys(override)) {
636
793
  merged[key] = mergeValue(base[key], override[key]);
@@ -1038,12 +1195,17 @@ function nameLabelLookup(nodes) {
1038
1195
  function hasPointLabels(series) {
1039
1196
  return series.some((s) => s.labels?.some((text) => text != null && text !== ""));
1040
1197
  }
1041
- function buildTooltip(base, { show, formatter, valueFormatter, getLabel }) {
1198
+ function buildTooltip(base, { show, formatter, valueFormatter, getLabel, forecastTag }) {
1042
1199
  if (!show) return void 0;
1043
- if (formatter === void 0 && valueFormatter === void 0) return base;
1200
+ if (formatter === void 0 && valueFormatter === void 0 && !forecastTag) return base;
1044
1201
  const tooltip = { ...base };
1045
- if (valueFormatter) {
1046
- 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
+ };
1047
1209
  }
1048
1210
  if (typeof formatter === "string") {
1049
1211
  tooltip.formatter = formatter;
@@ -1587,6 +1749,7 @@ function downloadCsv(table, filename) {
1587
1749
  );
1588
1750
  }
1589
1751
 
1752
+ exports.DEFAULT_FORECAST_LABEL = DEFAULT_FORECAST_LABEL;
1590
1753
  exports.THEME_DARK = THEME_DARK;
1591
1754
  exports.THEME_LIGHT = THEME_LIGHT;
1592
1755
  exports.abbreviateNumber = abbreviateNumber;
@@ -1614,9 +1777,12 @@ exports.computeDelta = computeDelta;
1614
1777
  exports.createTooltipFormatter = createTooltipFormatter;
1615
1778
  exports.darkChartColors = darkChartColors;
1616
1779
  exports.darkTheme = darkTheme;
1780
+ exports.describeForecast = describeForecast;
1617
1781
  exports.downloadCsv = downloadCsv;
1618
1782
  exports.downloadFile = downloadFile;
1619
1783
  exports.escapeHtml = escapeHtml;
1784
+ exports.forecastStart = forecastStart;
1785
+ exports.forecastTagLookup = forecastTagLookup;
1620
1786
  exports.formatCurrency = formatCurrency;
1621
1787
  exports.formatDate = formatDate;
1622
1788
  exports.formatNumber = formatNumber;
@@ -1643,6 +1809,7 @@ exports.getChartColors = getChartColors;
1643
1809
  exports.getLegendItems = getLegendItems;
1644
1810
  exports.getReplaceMerge = getReplaceMerge;
1645
1811
  exports.gridInset = gridInset;
1812
+ exports.hasForecast = hasForecast;
1646
1813
  exports.hasPointLabels = hasPointLabels;
1647
1814
  exports.heatmapTable = heatmapTable;
1648
1815
  exports.histogramTable = histogramTable;
@@ -1674,6 +1841,9 @@ exports.valueLabelFormatter = valueLabelFormatter;
1674
1841
  exports.waterfallLayout = waterfallLayout;
1675
1842
  exports.waterfallTable = waterfallTable;
1676
1843
  exports.withEndLabelInset = withEndLabelInset;
1844
+ exports.withForecastBars = withForecastBars;
1845
+ exports.withForecastLines = withForecastLines;
1846
+ exports.withForecastPoints = withForecastPoints;
1677
1847
  exports.withPointLabels = withPointLabels;
1678
1848
  exports.withReferenceMarks = withReferenceMarks;
1679
1849
  exports.withZoomInset = withZoomInset;
package/dist/index.d.cts CHANGED
@@ -151,6 +151,71 @@ declare function getReplaceMerge(prev: object | null | undefined, next: object,
151
151
  */
152
152
  declare function mergeChartOption<T extends object>(generated: T, override: object | undefined): T;
153
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
+
154
219
  /**
155
220
  * Builders for the label, axis, tooltip and legend parts of a chart option.
156
221
  *
@@ -268,11 +333,13 @@ interface BuildTooltipOptions {
268
333
  valueFormatter?: (value: number) => string;
269
334
  /** Per-point label lookup, exposed to formatter functions as `label` */
270
335
  getLabel?: PointLabelLookup;
336
+ /** Tag for forecast values by data index (see `forecastTagLookup`) */
337
+ forecastTag?: (dataIndex: number) => string | undefined;
271
338
  }
272
339
  /**
273
340
  * Build the `tooltip` component. `base` is the chart's default tooltip.
274
341
  */
275
- 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;
276
343
  /**
277
344
  * Format a value with `tooltipValueFormatter`, for charts whose default
278
345
  * tooltip is custom HTML (where ECharts' `valueFormatter` does not apply)
@@ -677,4 +744,4 @@ declare function downloadFile(content: string, filename: string, mimeType?: stri
677
744
  */
678
745
  declare function downloadCsv(table: ChartDataTable, filename: string): void;
679
746
 
680
- 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
@@ -151,6 +151,71 @@ declare function getReplaceMerge(prev: object | null | undefined, next: object,
151
151
  */
152
152
  declare function mergeChartOption<T extends object>(generated: T, override: object | undefined): T;
153
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
+
154
219
  /**
155
220
  * Builders for the label, axis, tooltip and legend parts of a chart option.
156
221
  *
@@ -268,11 +333,13 @@ interface BuildTooltipOptions {
268
333
  valueFormatter?: (value: number) => string;
269
334
  /** Per-point label lookup, exposed to formatter functions as `label` */
270
335
  getLabel?: PointLabelLookup;
336
+ /** Tag for forecast values by data index (see `forecastTagLookup`) */
337
+ forecastTag?: (dataIndex: number) => string | undefined;
271
338
  }
272
339
  /**
273
340
  * Build the `tooltip` component. `base` is the chart's default tooltip.
274
341
  */
275
- 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;
276
343
  /**
277
344
  * Format a value with `tooltipValueFormatter`, for charts whose default
278
345
  * tooltip is custom HTML (where ECharts' `valueFormatter` does not apply)
@@ -677,4 +744,4 @@ declare function downloadFile(content: string, filename: string, mimeType?: stri
677
744
  */
678
745
  declare function downloadCsv(table: ChartDataTable, filename: string): void;
679
746
 
680
- 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) {
@@ -296,7 +453,7 @@ function getReplaceMerge(prev, next, isComponentType) {
296
453
  }
297
454
  return [...types].filter((type) => isComponentType(type));
298
455
  }
299
- function isPlainObject(value) {
456
+ function isPlainObject2(value) {
300
457
  if (value === null || typeof value !== "object") return false;
301
458
  const proto = Object.getPrototypeOf(value);
302
459
  return proto === Object.prototype || proto === null;
@@ -309,7 +466,7 @@ function mergeValue(base, override, topLevel = false) {
309
466
  const length = Math.max(from.length, to.length);
310
467
  return Array.from({ length }, (_, index) => mergeValue(from[index], to[index]));
311
468
  }
312
- if (isPlainObject(base) && isPlainObject(override)) {
469
+ if (isPlainObject2(base) && isPlainObject2(override)) {
313
470
  const merged = { ...base };
314
471
  for (const key of Object.keys(override)) {
315
472
  merged[key] = mergeValue(base[key], override[key]);
@@ -717,12 +874,17 @@ function nameLabelLookup(nodes) {
717
874
  function hasPointLabels(series) {
718
875
  return series.some((s) => s.labels?.some((text) => text != null && text !== ""));
719
876
  }
720
- function buildTooltip(base, { show, formatter, valueFormatter, getLabel }) {
877
+ function buildTooltip(base, { show, formatter, valueFormatter, getLabel, forecastTag }) {
721
878
  if (!show) return void 0;
722
- if (formatter === void 0 && valueFormatter === void 0) return base;
879
+ if (formatter === void 0 && valueFormatter === void 0 && !forecastTag) return base;
723
880
  const tooltip = { ...base };
724
- if (valueFormatter) {
725
- 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
+ };
726
888
  }
727
889
  if (typeof formatter === "string") {
728
890
  tooltip.formatter = formatter;
@@ -1266,4 +1428,4 @@ function downloadCsv(table, filename) {
1266
1428
  );
1267
1429
  }
1268
1430
 
1269
- 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.2",
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",