Skip to content

Commit a028787

Browse files
authored
Merge pull request #810 from adobe/feat/s2-line-forecast
feat: create s2 LineForecast component
2 parents f1bccaa + 7acb8d8 commit a028787

27 files changed

Lines changed: 786 additions & 15 deletions

File tree

packages/docs/docs/spectrum2/line.md

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,61 @@ The transition between segments is seamless — the line connects solid and dott
192192

193193
---
194194

195+
## Forecast (LineForecast)
196+
197+
The `LineForecast` component is an S2-exclusive child of `Line`. It visually distinguishes a forecast region from historical data: the line transitions from solid to dotted at the forecast boundary, a vertical rule marks the start of the forecast, and an optional label appears above the rule.
198+
199+
```jsx
200+
<Chart data={data}>
201+
<Axis position="bottom" labelFormat="time" ticks baseline />
202+
<Axis position="left" grid />
203+
<Line color="series" metric="value" dimension="datetime" scaleType="time">
204+
<LineForecast metric="forecastValue" start={1725148800000} label="Forecast" />
205+
</Line>
206+
</Chart>
207+
```
208+
209+
The `start` value must be a dimension value that exists in the data (for time axes, epoch milliseconds). Rows at or after `start` are rendered as the forecast segment. Rows before `start` use the `metric` field on `Line`; rows in the forecast region use the `metric` field on `LineForecast`.
210+
211+
When `gradient` is set on the parent `Line`, the gradient is also rendered in the forecast region at a reduced opacity (40% of the historical gradient) to reinforce the uncertainty of the forecast.
212+
213+
### LineForecast props
214+
215+
<table>
216+
<thead>
217+
<tr>
218+
<th>name</th>
219+
<th>type</th>
220+
<th>default</th>
221+
<th>description</th>
222+
</tr>
223+
</thead>
224+
<tbody>
225+
<tr>
226+
<td>metric *</td>
227+
<td>string</td>
228+
<td>–</td>
229+
<td>Key in the data containing the forecast values.</td>
230+
</tr>
231+
<tr>
232+
<td>start *</td>
233+
<td>number | string</td>
234+
<td>–</td>
235+
<td>Dimension value at which the forecast begins. For time axes, provide epoch milliseconds. Rows at or after this value are rendered as the forecast segment.</td>
236+
</tr>
237+
<tr>
238+
<td>label</td>
239+
<td>string</td>
240+
<td>'Forecast'</td>
241+
<td>Text shown above the boundary rule. Hidden automatically when fewer than 80px remain between the boundary and the right edge of the chart.</td>
242+
</tr>
243+
</tbody>
244+
</table>
245+
246+
_* required_
247+
248+
---
249+
195250
## Line props (S2)
196251

197252
:::note Not all base Line props are supported
@@ -210,9 +265,9 @@ The S2 `Line` component does not yet support `onMouseOver`, `onMouseOut`, `Metri
210265
<tbody>
211266
<tr>
212267
<td>children</td>
213-
<td>ChartInspect | ChartPopover | LineDirectLabel</td>
268+
<td>ChartInspect | ChartPopover | LineDirectLabel | LineForecast</td>
214269
<td>–</td>
215-
<td>Optional child components for tooltips, popovers, and inline direct labels.</td>
270+
<td>Optional child components for tooltips, popovers, inline direct labels, and forecast regions.</td>
216271
</tr>
217272
<tr>
218273
<td>color</td>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2026 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
/* eslint-disable @typescript-eslint/no-unused-vars */
14+
import { FC } from 'react';
15+
16+
import { LineForecastProps } from '../../types';
17+
18+
const LineForecast: FC<LineForecastProps> = ({ metric, start, label }: LineForecastProps) => {
19+
return null;
20+
};
21+
22+
LineForecast.displayName = 'LineForecast';
23+
24+
export { LineForecast };
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* Copyright 2026 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
export { LineForecast } from './LineForecast';

packages/react-spectrum-charts-s2/src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export * from './EmptyState';
2020
export * from './Legend';
2121
export * from './Line';
2222
export * from './LineDirectLabel';
23+
export * from './LineForecast';
2324
export * from './LoadingState';
2425
export * from './ReferenceLine';
2526
export * from './Title';

packages/react-spectrum-charts-s2/src/rscToSbAdapter/chartAdapter.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ describe('rscPropsToSpecBuilderOptions()', () => {
255255
chartInspects: [],
256256
color: 'series',
257257
dimension: 'datetime',
258+
forecasts: [],
258259
hasOnClick: false,
259260
hasOnContextMenu: false,
260261
lineDirectLabels: [],

packages/react-spectrum-charts-s2/src/rscToSbAdapter/childrenAdapter.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
ChartPopoverOptions,
1919
DonutSummaryOptions,
2020
LegendOptions,
21+
LineForecastOptions,
2122
LineDirectLabelOptions,
2223
LineOptions,
2324
MarkOptions,
@@ -35,6 +36,7 @@ import { ChartPopover } from '../components/ChartPopover';
3536
import { Legend } from '../components/Legend';
3637
import { Line } from '../components/Line';
3738
import { LineDirectLabel } from '../components/LineDirectLabel';
39+
import { LineForecast } from '../components/LineForecast';
3840
import { ReferenceLine } from '../components/ReferenceLine';
3941
import { Title } from '../components/Title';
4042
import { Donut, DonutSummary, SegmentLabel } from '../rc';
@@ -48,6 +50,7 @@ import {
4850
DonutProps,
4951
DonutSummaryProps,
5052
LegendProps,
53+
LineForecastProps,
5154
LineDirectLabelProps,
5255
LineProps,
5356
ReferenceLineProps,
@@ -73,6 +76,7 @@ export const childrenToOptions = (
7376
chartInspects: ChartInspectOptions[];
7477
chartPopovers: ChartPopoverOptions[];
7578
donutSummaries: DonutSummaryOptions[];
79+
forecasts: LineForecastOptions[];
7680
legends: LegendOptions[];
7781
lineDirectLabels: LineDirectLabelOptions[];
7882
lines: LineOptions[];
@@ -88,6 +92,7 @@ export const childrenToOptions = (
8892
const chartInspects: ChartInspectOptions[] = [];
8993
const chartPopovers: ChartPopoverOptions[] = [];
9094
const donutSummaries: DonutSummaryOptions[] = [];
95+
const forecasts: LineForecastOptions[] = [];
9196
const legends: LegendOptions[] = [];
9297
const lineDirectLabels: LineDirectLabelOptions[] = [];
9398
const lines: LineOptions[] = [];
@@ -138,6 +143,10 @@ export const childrenToOptions = (
138143
legends.push(getLegendOptions(child.props as LegendProps));
139144
break;
140145

146+
case LineForecast.displayName:
147+
forecasts.push(child.props as LineForecastProps);
148+
break;
149+
141150
case LineDirectLabel.displayName:
142151
lineDirectLabels.push(child.props as LineDirectLabelProps);
143152
break;
@@ -172,6 +181,7 @@ export const childrenToOptions = (
172181
chartInspects,
173182
chartPopovers,
174183
donutSummaries,
184+
forecasts,
175185
legends,
176186
lineDirectLabels,
177187
lines,

packages/react-spectrum-charts-s2/src/rscToSbAdapter/lineAdapter.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { DEFAULT_COLOR } from '@spectrum-charts/constants';
1515

1616
import { ChartPopover } from '../components/ChartPopover';
1717
import { ChartInspect } from '../components/ChartInspect';
18+
import { LineForecast } from '../components/LineForecast';
1819
import { getLineOptions } from './lineAdapter';
1920

2021
describe('getLineOptions()', () => {
@@ -41,6 +42,18 @@ describe('getLineOptions()', () => {
4142
expect(getLineOptions({ onContextMenu: () => {} }).hasOnContextMenu).toBe(true);
4243
expect(getLineOptions({ onContextMenu: undefined }).hasOnContextMenu).toBe(false);
4344
});
45+
it('should convert LineForecast children to forecasts array', () => {
46+
const options = getLineOptions({
47+
children: [createElement(LineForecast, { metric: 'forecastValue', start: 1725148800000 })],
48+
});
49+
expect(options.forecasts).toHaveLength(1);
50+
expect(options.forecasts?.[0]).toHaveProperty('metric', 'forecastValue');
51+
expect(options.forecasts?.[0]).toHaveProperty('start', 1725148800000);
52+
});
53+
it('should return empty forecasts array when no LineForecast children', () => {
54+
const options = getLineOptions({});
55+
expect(options.forecasts).toHaveLength(0);
56+
});
4457
it('should pass through included props', () => {
4558
const options = getLineOptions({ color: DEFAULT_COLOR });
4659
expect(options).toHaveProperty('color', DEFAULT_COLOR);

packages/react-spectrum-charts-s2/src/rscToSbAdapter/lineAdapter.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ import { LineProps } from '../types';
1515
import { childrenToOptions } from './childrenAdapter';
1616

1717
export const getLineOptions = ({ children, onClick, onContextMenu, contextMenuMode: _contextMenuMode, ...lineProps }: LineProps): LineOptions => {
18-
const { chartInspects, chartPopovers, lineDirectLabels } = childrenToOptions(children);
18+
const { chartInspects, chartPopovers, forecasts, lineDirectLabels } = childrenToOptions(children);
1919
return {
2020
...lineProps,
2121
chartInspects,
2222
chartPopovers,
23+
forecasts,
2324
hasOnClick: Boolean(onClick),
2425
hasOnContextMenu: Boolean(onContextMenu),
2526
lineDirectLabels,

0 commit comments

Comments
 (0)