Skip to content

[charts] Introduce the radar chart #16406

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

Merged
merged 34 commits into from
Mar 5, 2025
Merged

Conversation

alexfauquette
Copy link
Member

@alexfauquette alexfauquette commented Jan 30, 2025

This PR is a rough first version of the Radar chart #7925. Lots of features are missing.

The idea is to validate the data structure, and it's mapping to a rotation/radius scale system. If ok, we can merge this one and add features in distinct PR such that we keep them small enough to be relevant (I don't expect someone to read 200 files with 10 different features and spot any bug or room for improvement)

Main idea

The polar plugin introduce the support of rotatio/radius axes that work similarly to the x/y axes

The radar don't expose directly those axies. It uses an intermediate metrics to make sure the radar has only one rotation axis with scale point, and one radius axis per metric.

Then we have one component per item rendered (grid, area, and marks)

Features

  • Polar coordinate
  • Map series config to rendering

Future features

Preview: https://deploy-preview-16406--material-ui-x.netlify.app/x/react-charts/radar/

Changelog

  • 🎁 The first iteration of the radar chart is available. Features and refinements will be added in the coming weeks

@alexfauquette alexfauquette added type: new feature Introduces a new piece of functionality or capability. scope: charts Changes or issues related to the charts product labels Jan 30, 2025
@mui-bot
Copy link

mui-bot commented Jan 30, 2025

Deploy preview: https://deploy-preview-16406--material-ui-x.netlify.app/

Updated pages:

Generated by 🚫 dangerJS against 3be61ca

@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Jan 31, 2025
Copy link

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Jan 31, 2025
@alexfauquette alexfauquette marked this pull request as ready for review January 31, 2025 11:11
Copy link

codspeed-hq bot commented Jan 31, 2025

CodSpeed Performance Report

Merging #16406 will not alter performance

Comparing alexfauquette:radar-chart (3be61ca) with master (03f0c05)

Summary

✅ 7 untouched benchmarks

@alexfauquette alexfauquette marked this pull request as draft January 31, 2025 15:28
@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Feb 4, 2025
Copy link

github-actions bot commented Feb 4, 2025

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Feb 4, 2025
@alexfauquette alexfauquette marked this pull request as ready for review February 4, 2025 10:59
Copy link
Member

@bernardobelchior bernardobelchior left a comment

Choose a reason for hiding this comment

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

Good job! Left some comments because I don't get a lot of stuff 😛

seriesProp: RadarSeriesType;
itemIdentifier: RadarItemIdentifier;
valueType: number;
polar: true;
Copy link
Member

Choose a reason for hiding this comment

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

More of a nitpick, and we don't need to solve it now since we're just repeating the established pattern of using cartesian: true, but would it make sense to have a type: 'cartesian' or type: 'polar' instead?

params: UseChartPolarAxisParameters;
defaultizedParams: UseChartPolarAxisDefaultizedParameters;
state: UseChartPolarAxisState;
// instance: UseChartPolarAxisInstance;
Copy link
Member

Choose a reason for hiding this comment

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

Bump to ensure this comment isn't forgotten 😄

margin={{ top: 20 }}
series={[
{
// label: 'Lisa',
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// label: 'Lisa',


export default function CompositionExample() {
return (
<RadarDataProvider
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if it makes sense to have this RadarDataProvider.

From my understanding, we already have three tiers of decreasing abstraction: LineChart, ChartContainer and ChartDataProvider. With RadarDataProvider, it seems we'll now have 4 tiers: RadarChart, ChartContainer, RadarDataProvider and ChartDataProvider.

Does it make sense to have the distinction between RadarDataProvider and ChartDataProvider?

I'm questioning because either we'll have to add docs on how to use ChartDataProvider with a radar chart, or we'll probably need to update the generic composition page to mention that some of that information doesn't apply to radar charts.

What are we trying to accomplish with RadarDataProvider that can't be done with ChartDataProvider?

My point of view might not make sense since I don't have much experience with this, but I'm a bit confused so I assumed that users might also face the same problem. Curious to hear your opinion!

Copy link
Member

Choose a reason for hiding this comment

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

Yeah I have some concerns as well with the DataProvider, currently you can't use zoom if you use composition. And if we add a different data provider for each of the charts it might get messy 😓

Copy link
Member Author

Choose a reason for hiding this comment

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

What are we trying to accomplish with RadarDataProvider that can't be done with ChartDataProvider?

The mapping from the radar props to polar axes.

The idea is to provide a simpler API such that users can not shoot in their feet, and allowing us to make assumption about the links between the axes.

Technically they could use the DataProvider if they respect the relation between the radial axes, and the rotation one.

I have some concerns as well with the DataProvider, currently you can't use zoom if you use composition.

That's an issue about plugins propagation. The DataProviderProps should defaultize its plugins with the default MIT plugins plus the zoom one


For me the strategy is the following

  1. A general composition is ChartContainer[Pro] = ChartDataProvider[Pro] + ChartSurface[Pro]
  2. Custom Data provider for charts with dedicated API

The idea being that the ChartDataProvider is a technical description of the charts, and the custom data provider is a simplification because not all the combinations of axes and data make sense. For example, the funnel charts does not really need flexible axes. They have some combination that are useful and could be extracted in a simplified API.

The messy aspect comes from the fact that we currently have only Cartesian charts that can be mixed together + the pie chart that is poorly documented. The composition docs could effectively be more explicit about the two approach about composition.

axisIndex: number,
formattedSeries: ProcessedSeries<TSeriesType>,
) => {
const charTypes = Object.keys(seriesConfig).filter(isPolarSeriesType);
Copy link
Member

Choose a reason for hiding this comment

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

Nit: did you mean chartTypes I guess something like polarSeries would maybe make more sense?

Suggested change
const charTypes = Object.keys(seriesConfig).filter(isPolarSeriesType);
const polarSeries = Object.keys(seriesConfig).filter(isPolarSeriesType);

* The number of divisions in the radar grid.
* @default 5
*/
divisionNumber?: number;
Copy link
Member

Choose a reason for hiding this comment

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

Nit: would it make sense to call this divisions or slices?

Copy link
Member

Choose a reason for hiding this comment

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

Slices would be the V shapes. Like a slice of pizza. I found divisionNumber a bit confusing too. divisions or internalDivisions, though the first is probably better.

*/
name: string;
/**
* The minimal value of the domain.
Copy link
Member

Choose a reason for hiding this comment

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

Do we mean "minimum"?

Suggested change
* The minimal value of the domain.
* The minimum value of the domain.

metrics: string[] | MetricConfig[];
/**
* The default max value for axis.
* If will be override is `metrics` contains a `max` property.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* If will be override is `metrics` contains a `max` property.
* It will be overridden if `metrics` contains a `max` property.


export interface RadarConfig {
/**
* The different metrics shown by radar.
Copy link
Member

Choose a reason for hiding this comment

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

This is more of a nit, but I'm not sure "different" is adding much here.

Suggested change
* The different metrics shown by radar.
* The metrics shown by radar.

*/
max?: number;
/**
* The angle of the first axis (in deg)
Copy link
Member

Choose a reason for hiding this comment

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

Would it make sense to say something like "The starting angle of the rotation axis (in degrees)"? It might not be clear that the first axis is the rotation axis, or is it?

@JCQuintas
Copy link
Member

Should we fix darkmode in this PR?

Comment on lines 33 to 36
/**
* The number of steps in the radar grid.
*/
divisionNumber?: number;
Copy link
Member

Choose a reason for hiding this comment

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

This prop does nothing. I guess it was moved into the root props but forgotten here?

Comment on lines +11 to +14
/**
* If `true` show marks at value position.
*/
showMark?: boolean;
Copy link
Member

Choose a reason for hiding this comment

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

showMark and valueFormatter doesn't seem to be working.

import ChartsUsageDemo from 'docsx/src/modules/components/ChartsUsageDemo';
import { Unstable_RadarChart as RadarChart } from '@mui/x-charts/RadarChart';

export default function DemoRadarNoSnap() {
Copy link
Member

Choose a reason for hiding this comment

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

We can remove the NoSnap from this file. This demo seems useful to snap 👍

Copy link

github-actions bot commented Mar 3, 2025

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Mar 3, 2025
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Mar 5, 2025
@@ -6,7 +6,7 @@ import { useItemHighlightedGetter } from '../../hooks/useItemHighlightedGetter';
import { SeriesId } from '../../models/seriesType/common';

/**
*
* THis hook provides all the data needed to display radar series.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* THis hook provides all the data needed to display radar series.
* This hook provides all the data needed to display radar series.

@alexfauquette alexfauquette merged commit bc48b31 into mui:master Mar 5, 2025
19 checks passed
Copy link
Member

@bernardobelchior bernardobelchior left a comment

Choose a reason for hiding this comment

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

Code looks good! Minor comments regarding the docs

*/
min?: number;
/**
* The maximal value of the domain.
Copy link
Member

Choose a reason for hiding this comment

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

Did you mean maximum?

Suggested change
* The maximal value of the domain.
* The maximum value of the domain.

metrics: string[] | MetricConfig[];
/**
* The default max value for radius axes.
* It will be override if `metrics` contains a `max` property.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* It will be override if `metrics` contains a `max` property.
* It will be overridden if `metrics` contains a `max` property.

/**
* The metrics shown by radar.
*/
metrics: string[] | MetricConfig[];
Copy link
Member

Choose a reason for hiding this comment

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

From the code, it seems we actually support Array<string | MetricConfig> because we're handling each metric separately:

radar.metrics.map((m) => {
        const { name, min = 0, max = radar.max } = typeof m === 'string' ? { name: m } : m;

        return {
          id: name,
          label: name,
          scaleType: 'linear' as const,
          min,
          max,
        };

Not sure if there's any benefit in allowing that, but just wondering if we should use Array<string | MetricConfig> here.

});
});

// eslint-disable-next-line mocha/max-top-level-suites
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we should remove this rule altogether? Do you know why we have it enabled?

Copy link
Member

Choose a reason for hiding this comment

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

It will be removed once we stop depending on mocha. Theoretically mocha doesn't support multiple describes. Technically it doesn't have a problem with that...

Right now it is part of the defaults, so I just opted to leave it be and change it later when we are in position to do so globally

Comment on lines +23 to +27
* The rotation-axes IDs sorted by order they got provided.
*/
rotationAxisIds: AxisId[];
/**
* The radius-axes IDs sorted by order they got provided.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* The rotation-axes IDs sorted by order they got provided.
*/
rotationAxisIds: AxisId[];
/**
* The radius-axes IDs sorted by order they got provided.
* The rotation-axes IDs sorted by order they were provided.
*/
rotationAxisIds: AxisId[];
/**
* The radius-axes IDs sorted by order they were provided.


## Basics

A radar chart is defined by two main props.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
A radar chart is defined by two main props.
A radar chart is defined by two main props:


The radar chart displays a grid behind the series that can be configured with

- `startAngle` The rotation angle of the entire chart in degree
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
- `startAngle` The rotation angle of the entire chart in degree
- `startAngle` The rotation angle of the entire chart in degrees

The radar chart displays a grid behind the series that can be configured with

- `startAngle` The rotation angle of the entire chart in degree
- `divisions` The number of division of the grid
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
- `divisions` The number of division of the grid
- `divisions` The number of divisions of the grid

Comment on lines +71 to +73
In this example, we uses `RadarSeriesArea` and `RadarSeriesMarks` to modify the order of the elements:
all the marks are on top of all the path.
And we apply different properties based on the series id.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
In this example, we uses `RadarSeriesArea` and `RadarSeriesMarks` to modify the order of the elements:
all the marks are on top of all the path.
And we apply different properties based on the series id.
In this example, we use `RadarSeriesArea` and `RadarSeriesMarks` to modify the order of the elements so that all the marks render above the paths.
Additionally, we apply different properties based on the series ID.

Comment on lines +5 to +6
* The id of the series To display.
* If undefined all series are display.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* The id of the series To display.
* If undefined all series are display.
* The id of the series to display.
* If undefined all series are displayed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
scope: charts Changes or issues related to the charts product type: new feature Introduces a new piece of functionality or capability.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants