Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[charts] Add baseline property to the LineChart series #14153

Merged
merged 14 commits into from
Aug 12, 2024
19 changes: 19 additions & 0 deletions docs/data/charts/lines/AreaBaseline.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as React from 'react';
import { LineChart } from '@mui/x-charts/LineChart';

export default function AreaBaseline() {
return (
<LineChart
xAxis={[{ data: [1, 2, 3, 5, 8, 10] }]}
series={[
{
data: [2, -5.5, 2, -7.5, 1.5, 6],
area: true,
baseline: -10,
},
]}
width={500}
height={300}
/>
);
}
19 changes: 19 additions & 0 deletions docs/data/charts/lines/AreaBaseline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as React from 'react';
import { LineChart } from '@mui/x-charts/LineChart';

export default function AreaBaseline() {
return (
<LineChart
xAxis={[{ data: [1, 2, 3, 5, 8, 10] }]}
series={[
{
data: [2, -5.5, 2, -7.5, 1.5, 6],
area: true,
baseline: -10,
JCQuintas marked this conversation as resolved.
Show resolved Hide resolved
},
]}
width={500}
height={300}
/>
);
}
12 changes: 12 additions & 0 deletions docs/data/charts/lines/AreaBaseline.tsx.preview
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<LineChart
xAxis={[{ data: [1, 2, 3, 5, 8, 10] }]}
series={[
{
data: [2, -5.5, 2, -7.5, 1.5, 6],
area: true,
baseline: -10,
},
]}
width={500}
height={300}
/>
10 changes: 10 additions & 0 deletions docs/data/charts/lines/lines.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,16 @@ Different series could even have different interpolations.

{{"demo": "InterpolationDemoNoSnap.js", "hideToolbar": true}}

### Baseline

The area chart draws a `baseline` on the Y axis `0`.
This is useful as a base value, but customized visualizations may require a different baseline.

To change it, simply provide the wanted `baseline` number of a series.
Different series can have different `baselines`.
JCQuintas marked this conversation as resolved.
Show resolved Hide resolved

{{"demo": "AreaBaseline.js"}}

### Optimization

To show mark elements, use `showMark` series property.
Expand Down
1 change: 1 addition & 0 deletions docs/pages/x/api/charts/line-series-type.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"properties": {
"type": { "type": { "description": "'line'" }, "required": true },
"area": { "type": { "description": "boolean" } },
"baseline": { "type": { "description": "number" }, "default": "0" },
"color": { "type": { "description": "string" } },
"connectNulls": { "type": { "description": "boolean" }, "default": "false" },
"curve": { "type": { "description": "CurveType" } },
Expand Down
1 change: 1 addition & 0 deletions docs/translations/api-docs/charts/line-series-type.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"propertiesDescriptions": {
"type": { "description": "" },
"area": { "description": "" },
"baseline": { "description": "The value of the line at the base of the series area." },
"color": { "description": "" },
"connectNulls": {
"description": "If <code>true</code>, line and area connect points separated by <code>null</code> values."
Expand Down
5 changes: 5 additions & 0 deletions packages/x-charts/src/LineChart/AreaPlot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const useAggregatedData = () => {
stackedData,
data,
connectNulls,
baseline,
} = series[seriesId];

const xAxisId = xAxisIdProp ?? xAxisKey;
Expand Down Expand Up @@ -97,6 +98,10 @@ const useAggregatedData = () => {
.x((d) => xScale(d.x))
.defined((_, i) => connectNulls || data[i] != null)
.y0((d) => {
if (typeof baseline === 'number') {
return yScale(baseline)!;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only impacts the plotting. Here is an example with values fluctuating between 200-300, and with baseline set to 1000.

Notice that:

  • The basline has no impact on the max value of the y-axis
  • The yèaxis always start at 0 because it's the assumed baseline

image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential solution

  1. When computing the extremum values, you replace when needed the base (d[0]) by the user defined baseline

  2. Instead of providing a number, user can provide 'top' | 'bottom'. Then you just need to ignore the d[0] by doing

- isArea && axis.scaleType !== 'log' ? ...
+ isArea && axis.scaleType !== 'log' && series[seriesId].baseline === undefined ? ...

and in the commented line return

- return yScale(baseline)!;
- return baseline==='top' ? yScale.range[0] : yScale.range[1];

const { area, stackedData } = series[seriesId];
const isArea = area !== undefined;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It behaves a bit weirdly when baseline changes the extremums.

This happens because the extremum getter value is then passed into the nice() function, it can have unintended consequences, which means I would also have to check the baseline where that is calculated.

But we already have the axis min/max values for that. So I believe we should use the min/max values when the axis extremums need to be changed.

screencapture-localhost-3001-playground-area-chart-baseline-2024-08-12-11_20_10

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It behaves a bit weirdly when baseline changes the extremums.

TLTR: I don't think it's an issue


Technically speaking, we have two behavior to define:

  • where does the area set the baseline
  • should the basline impact the extremums.

Should the baseline impact the extremums

For stacked data, I can not think of a usecase where the answer would be no. Otherwise you would compare some area wherase the base one would have missing part.

image

For a single series, where the area is more aesthetic, seems it can be ignored. Here foe example the NASDAQ evolution during the day. Nobody care of the space between 0 and 16.500. Only the relative evolution matter. Otherwise you would see nothing.

image

Where does the area set the baseline

I only see two reasons to modify the default 0:

  • aesthetic reasons. But in that case, only min/max makes sense
  • There is a specific value to compare to. For example the average over time. But in that case, I don't see a reason to pick a value that is always on top or below the line

So basically the weird case you define should not exist

image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, but those are just working as expected. The issue that appears when we have the baseline affect the extremums is that the extremums will get niced after. So if we have a non-round number like below, it will not work properly. Then this starts to get more complex and would clash with a feature that already exists yAxis.min/max

Screenshot 2024-08-12 at 13 03 52

const value = d.y && yScale(d.y[0])!;
if (Number.isNaN(value)) {
return yScale.range()[0];
Expand Down
5 changes: 5 additions & 0 deletions packages/x-charts/src/models/seriesType/line.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ export interface LineSeriesType
* @default 'none'
*/
stackOffset?: StackOffsetType;
/**
* The value of the line at the base of the series area.
* @default 0
*/
baseline?: number;
}

/**
Expand Down