Skip to content

Commit 022cb9b

Browse files
[charts-pro] Add WebGL renderer to heatmap
1 parent 8afeb1d commit 022cb9b

File tree

26 files changed

+10129
-10
lines changed

26 files changed

+10129
-10
lines changed

docs/data/charts/dataset/nyc-yellow-taxi-2024-trip-count.json

Lines changed: 8783 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import Stack from '@mui/material/Stack';
2+
import Typography from '@mui/material/Typography';
3+
import { interpolateOrRd } from 'd3-scale-chromatic';
4+
import { HeatmapPremium } from '@mui/x-charts-premium/HeatmapPremium';
5+
import data from '../dataset/nyc-yellow-taxi-2024-trip-count.json';
6+
7+
const seriesData = data;
8+
const max = Math.max(...seriesData.map(([, , value]) => value));
9+
const xData = Array.from({ length: 366 }, (_, i) => {
10+
const date = new Date(2024, 0, 1);
11+
12+
date.setDate(i + 1);
13+
14+
return date;
15+
});
16+
const yData = Array.from({ length: 24 }, (_, i) => i);
17+
18+
const settings = {
19+
xAxis: [
20+
{
21+
data: xData,
22+
ordinalTimeTicks: ['months', 'biweekly', 'weeks', 'days'],
23+
valueFormatter: (date) =>
24+
date.toLocaleString('en-US', { month: 'short', day: 'numeric' }),
25+
zoom: { minSpan: 3 },
26+
},
27+
],
28+
yAxis: [{ data: yData, valueFormatter: (hour) => `${hour}:00` }],
29+
zAxis: [
30+
{
31+
min: 0,
32+
max,
33+
colorMap: {
34+
type: 'continuous',
35+
color: interpolateOrRd,
36+
max,
37+
},
38+
},
39+
],
40+
series: [{ data: seriesData }],
41+
height: 450,
42+
};
43+
44+
export default function WebGLHeatmap() {
45+
return (
46+
<Stack width="100%">
47+
<Typography variant="h6" sx={{ alignSelf: 'center', textAlign: 'center' }}>
48+
Yellow Taxi Trip Count - 2024
49+
</Typography>
50+
<HeatmapPremium renderer="webgl" {...settings} />
51+
<Typography variant="caption">Source: NYC.gov</Typography>
52+
</Stack>
53+
);
54+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import Stack from '@mui/material/Stack';
2+
import Typography from '@mui/material/Typography';
3+
import { interpolateOrRd } from 'd3-scale-chromatic';
4+
import {
5+
HeatmapPremium,
6+
HeatmapPremiumProps,
7+
} from '@mui/x-charts-premium/HeatmapPremium';
8+
import data from '../dataset/nyc-yellow-taxi-2024-trip-count.json';
9+
10+
const seriesData = data as [number, number, number][];
11+
const max = Math.max(...seriesData.map(([, , value]) => value));
12+
const xData = Array.from({ length: 366 }, (_, i) => {
13+
const date = new Date(2024, 0, 1);
14+
15+
date.setDate(i + 1);
16+
17+
return date;
18+
});
19+
const yData = Array.from({ length: 24 }, (_, i) => i);
20+
21+
const settings: HeatmapPremiumProps = {
22+
xAxis: [
23+
{
24+
data: xData,
25+
ordinalTimeTicks: ['months', 'biweekly', 'weeks', 'days'],
26+
valueFormatter: (date: Date) =>
27+
date.toLocaleString('en-US', { month: 'short', day: 'numeric' }),
28+
zoom: { minSpan: 3 },
29+
},
30+
],
31+
yAxis: [{ data: yData, valueFormatter: (hour: number) => `${hour}:00` }],
32+
zAxis: [
33+
{
34+
min: 0,
35+
max,
36+
colorMap: {
37+
type: 'continuous',
38+
color: interpolateOrRd,
39+
max,
40+
},
41+
},
42+
],
43+
series: [{ data: seriesData }],
44+
height: 450,
45+
};
46+
47+
export default function WebGLHeatmap() {
48+
return (
49+
<Stack width="100%">
50+
<Typography variant="h6" sx={{ alignSelf: 'center', textAlign: 'center' }}>
51+
Yellow Taxi Trip Count - 2024
52+
</Typography>
53+
<HeatmapPremium renderer="webgl" {...settings} />
54+
<Typography variant="caption">Source: NYC.gov</Typography>
55+
</Stack>
56+
);
57+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<Typography variant="h6" sx={{ alignSelf: 'center', textAlign: 'center' }}>
2+
Yellow Taxi Trip Count - 2024
3+
</Typography>
4+
<HeatmapPremium renderer="webgl" {...settings} />
5+
<Typography variant="caption">Source: NYC.gov</Typography>

docs/data/charts/heatmap/heatmap.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: React Heatmap chart
33
productId: x-charts
4-
components: Heatmap, HeatmapPlot, HeatmapTooltip, HeatmapTooltipContent, FocusedHeatmapCell
4+
components: Heatmap, HeatmapPlot, HeatmapTooltip, HeatmapTooltipContent, FocusedHeatmapCell, HeatmapPremium
55
---
66

77
# Charts - Heatmap [<span class="plan-pro"></span>](/x/introduction/licensing/#pro-plan 'Pro plan')
@@ -103,3 +103,17 @@ You can modify it with `slots.legend` and `slotProps.legend`.
103103
## Custom item
104104

105105
{{"demo": "CustomItem.js"}}
106+
107+
## WebGL Renderer [<span class="plan-premium"></span>](/x/introduction/licensing/#premium-plan 'Premium plan')
108+
109+
Heatmaps can contain a large number of cells.
110+
To improve performance when rendering many cells, you can use the WebGL renderer by setting the `renderer` prop to `'webgl'`.
111+
112+
The WebGL renderer has some limitations compared to the SVG renderer:
113+
114+
- The `cell` slot is not supported;
115+
- The heatmap cell cannot be customized using CSS;
116+
117+
The following example showcases a heatmap with approximately 8800 cells rendered using WebGL.
118+
119+
{{"demo": "WebGLHeatmap.js"}}

docs/data/chartsApiPages.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,11 @@ const chartsApiPages: MuiPage[] = [
225225
title: 'HeatmapPlot',
226226
plan: 'pro',
227227
},
228+
{
229+
pathname: '/x/api/charts/heatmap-premium',
230+
title: 'HeatmapPremium',
231+
plan: 'premium',
232+
},
228233
{
229234
pathname: '/x/api/charts/heatmap-tooltip',
230235
title: 'HeatmapTooltip',
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import * as React from 'react';
2+
import ApiPage from 'docs/src/modules/components/ApiPage';
3+
import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
4+
import jsonPageContent from './heatmap-premium.json';
5+
6+
export default function Page(props) {
7+
const { descriptions } = props;
8+
return <ApiPage descriptions={descriptions} pageContent={jsonPageContent} />;
9+
}
10+
11+
export async function getStaticProps() {
12+
const req = require.context(
13+
'docsx/translations/api-docs/charts/heatmap-premium',
14+
false,
15+
/\.\/heatmap-premium.*\.json$/,
16+
);
17+
const descriptions = mapApiPageTranslations(req);
18+
19+
return { props: { descriptions } };
20+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
{
2+
"props": {
3+
"series": {
4+
"type": { "name": "arrayOf", "description": "Array&lt;object&gt;" },
5+
"required": true
6+
},
7+
"xAxis": {
8+
"type": {
9+
"name": "arrayOf",
10+
"description": "Array&lt;{ axis?: 'x', barGapRatio?: number, categoryGapRatio?: number, classes?: object, colorMap?: { colors: Array&lt;string&gt;, type: 'ordinal', unknownColor?: string, values?: Array&lt;Date<br>&#124;&nbsp;number<br>&#124;&nbsp;string&gt; }<br>&#124;&nbsp;{ color: Array&lt;string&gt;<br>&#124;&nbsp;func, max?: Date<br>&#124;&nbsp;number, min?: Date<br>&#124;&nbsp;number, type: 'continuous' }<br>&#124;&nbsp;{ colors: Array&lt;string&gt;, thresholds: Array&lt;Date<br>&#124;&nbsp;number&gt;, type: 'piecewise' }, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, domainLimit?: 'nice'<br>&#124;&nbsp;'strict'<br>&#124;&nbsp;func, groups?: Array&lt;{ getValue: func, tickLabelStyle?: object, tickSize?: number }&gt;, height?: number, hideTooltip?: bool, id?: number<br>&#124;&nbsp;string, ignoreTooltip?: bool, label?: string, labelStyle?: object, offset?: number, ordinalTimeTicks?: Array&lt;'biweekly'<br>&#124;&nbsp;'days'<br>&#124;&nbsp;'hours'<br>&#124;&nbsp;'months'<br>&#124;&nbsp;'quarterly'<br>&#124;&nbsp;'weeks'<br>&#124;&nbsp;'years'<br>&#124;&nbsp;{ format: func, getTickNumber: func, isTick: func }&gt;, position?: 'bottom'<br>&#124;&nbsp;'none'<br>&#124;&nbsp;'top', reverse?: bool, scaleType?: 'band', slotProps?: object, slots?: object, sx?: Array&lt;func<br>&#124;&nbsp;object<br>&#124;&nbsp;bool&gt;<br>&#124;&nbsp;func<br>&#124;&nbsp;object, tickInterval?: 'auto'<br>&#124;&nbsp;array<br>&#124;&nbsp;func, tickLabelInterval?: 'auto'<br>&#124;&nbsp;func, tickLabelMinGap?: number, tickLabelPlacement?: 'middle'<br>&#124;&nbsp;'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'<br>&#124;&nbsp;'extremities'<br>&#124;&nbsp;'middle'<br>&#124;&nbsp;'start', tickSize?: number, tickSpacing?: number, valueFormatter?: func, zoom?: { filterMode?: 'discard'<br>&#124;&nbsp;'keep', maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, slider?: { enabled?: bool, preview?: bool, showTooltip?: 'always'<br>&#124;&nbsp;'hover'<br>&#124;&nbsp;'never', size?: number }, step?: number }<br>&#124;&nbsp;bool }&gt;"
11+
},
12+
"required": true
13+
},
14+
"yAxis": {
15+
"type": {
16+
"name": "arrayOf",
17+
"description": "Array&lt;{ axis?: 'y', barGapRatio?: number, categoryGapRatio?: number, classes?: object, colorMap?: { colors: Array&lt;string&gt;, type: 'ordinal', unknownColor?: string, values?: Array&lt;Date<br>&#124;&nbsp;number<br>&#124;&nbsp;string&gt; }<br>&#124;&nbsp;{ color: Array&lt;string&gt;<br>&#124;&nbsp;func, max?: Date<br>&#124;&nbsp;number, min?: Date<br>&#124;&nbsp;number, type: 'continuous' }<br>&#124;&nbsp;{ colors: Array&lt;string&gt;, thresholds: Array&lt;Date<br>&#124;&nbsp;number&gt;, type: 'piecewise' }, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, domainLimit?: 'nice'<br>&#124;&nbsp;'strict'<br>&#124;&nbsp;func, groups?: Array&lt;{ getValue: func, tickLabelStyle?: object, tickSize?: number }&gt;, hideTooltip?: bool, id?: number<br>&#124;&nbsp;string, ignoreTooltip?: bool, label?: string, labelStyle?: object, offset?: number, ordinalTimeTicks?: Array&lt;'biweekly'<br>&#124;&nbsp;'days'<br>&#124;&nbsp;'hours'<br>&#124;&nbsp;'months'<br>&#124;&nbsp;'quarterly'<br>&#124;&nbsp;'weeks'<br>&#124;&nbsp;'years'<br>&#124;&nbsp;{ format: func, getTickNumber: func, isTick: func }&gt;, position?: 'left'<br>&#124;&nbsp;'none'<br>&#124;&nbsp;'right', reverse?: bool, scaleType?: 'band', slotProps?: object, slots?: object, sx?: Array&lt;func<br>&#124;&nbsp;object<br>&#124;&nbsp;bool&gt;<br>&#124;&nbsp;func<br>&#124;&nbsp;object, tickInterval?: 'auto'<br>&#124;&nbsp;array<br>&#124;&nbsp;func, tickLabelInterval?: 'auto'<br>&#124;&nbsp;func, tickLabelPlacement?: 'middle'<br>&#124;&nbsp;'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'<br>&#124;&nbsp;'extremities'<br>&#124;&nbsp;'middle'<br>&#124;&nbsp;'start', tickSize?: number, tickSpacing?: number, valueFormatter?: func, width?: number, zoom?: { filterMode?: 'discard'<br>&#124;&nbsp;'keep', maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, slider?: { enabled?: bool, preview?: bool, showTooltip?: 'always'<br>&#124;&nbsp;'hover'<br>&#124;&nbsp;'never', size?: number }, step?: number }<br>&#124;&nbsp;bool }&gt;"
18+
},
19+
"required": true
20+
},
21+
"borderRadius": { "type": { "name": "number" } },
22+
"brushConfig": {
23+
"type": {
24+
"name": "shape",
25+
"description": "{ enabled?: bool, preventHighlight?: bool, preventTooltip?: bool }"
26+
}
27+
},
28+
"colors": {
29+
"type": { "name": "union", "description": "Array&lt;string&gt;<br>&#124;&nbsp;func" },
30+
"default": "rainbowSurgePalette"
31+
},
32+
"dataset": { "type": { "name": "arrayOf", "description": "Array&lt;object&gt;" } },
33+
"disableAxisListener": { "type": { "name": "bool" }, "default": "false" },
34+
"height": { "type": { "name": "number" } },
35+
"hideLegend": { "type": { "name": "bool" } },
36+
"highlightedItem": {
37+
"type": {
38+
"name": "shape",
39+
"description": "{ dataIndex?: number, seriesId: number<br>&#124;&nbsp;string }"
40+
}
41+
},
42+
"id": { "type": { "name": "string" } },
43+
"initialZoom": {
44+
"type": {
45+
"name": "arrayOf",
46+
"description": "Array&lt;{ axisId: number<br>&#124;&nbsp;string, end: number, start: number }&gt;"
47+
}
48+
},
49+
"loading": { "type": { "name": "bool" }, "default": "false" },
50+
"localeText": { "type": { "name": "object" } },
51+
"margin": {
52+
"type": {
53+
"name": "union",
54+
"description": "number<br>&#124;&nbsp;{ bottom?: number, left?: number, right?: number, top?: number }"
55+
}
56+
},
57+
"onAxisClick": {
58+
"type": { "name": "func" },
59+
"deprecated": true,
60+
"deprecationInfo": "Use <code>onItemClick</code> instead to get access to both x- and y-axis values.",
61+
"signature": {
62+
"type": "function(event: MouseEvent, data: null | ChartsAxisData) => void",
63+
"describedArgs": ["event", "data"]
64+
}
65+
},
66+
"onHighlightChange": {
67+
"type": { "name": "func" },
68+
"signature": {
69+
"type": "function(highlightedItem: HighlightItemData | null) => void",
70+
"describedArgs": ["highlightedItem"]
71+
}
72+
},
73+
"onItemClick": {
74+
"type": { "name": "func" },
75+
"signature": {
76+
"type": "function(event: React.MouseEvent<SVGSVGElement, MouseEvent>, item: SeriesItemIdentifier<SeriesType>) => void",
77+
"describedArgs": ["event", "item"]
78+
}
79+
},
80+
"onTooltipItemChange": {
81+
"type": { "name": "func" },
82+
"signature": {
83+
"type": "function(tooltipItem: SeriesItemIdentifier<TSeries> | null) => void",
84+
"describedArgs": ["tooltipItem"]
85+
}
86+
},
87+
"onZoomChange": {
88+
"type": { "name": "func" },
89+
"signature": {
90+
"type": "function(zoomData: Array<ZoomData>) => void",
91+
"describedArgs": ["zoomData"]
92+
}
93+
},
94+
"renderer": {
95+
"type": { "name": "enum", "description": "'svg-single'<br>&#124;&nbsp;'webgl'" }
96+
},
97+
"showToolbar": { "type": { "name": "bool" }, "default": "false" },
98+
"slotProps": { "type": { "name": "object" }, "default": "{}" },
99+
"slots": {
100+
"type": { "name": "object" },
101+
"default": "{}",
102+
"additionalInfo": { "slotsApi": true }
103+
},
104+
"tooltip": {
105+
"type": { "name": "object" },
106+
"seeMoreLink": { "url": "https://mui.com/x/react-charts/tooltip/", "text": "tooltip docs" }
107+
},
108+
"tooltipItem": {
109+
"type": {
110+
"name": "shape",
111+
"description": "{ dataIndex?: number, seriesId: number<br>&#124;&nbsp;string, type: 'heatmap', xIndex?: number, yIndex?: number }"
112+
}
113+
},
114+
"width": { "type": { "name": "number" } },
115+
"zAxis": {
116+
"type": {
117+
"name": "arrayOf",
118+
"description": "Array&lt;{ colorMap?: { colors: Array&lt;string&gt;, type: 'ordinal', unknownColor?: string, values?: Array&lt;Date<br>&#124;&nbsp;number<br>&#124;&nbsp;string&gt; }<br>&#124;&nbsp;{ color: Array&lt;string&gt;<br>&#124;&nbsp;func, max?: Date<br>&#124;&nbsp;number, min?: Date<br>&#124;&nbsp;number, type: 'continuous' }<br>&#124;&nbsp;{ colors: Array&lt;string&gt;, thresholds: Array&lt;Date<br>&#124;&nbsp;number&gt;, type: 'piecewise' }, data?: array, dataKey?: string, id?: string, max?: number, min?: number }&gt;"
119+
}
120+
},
121+
"zoomData": {
122+
"type": {
123+
"name": "arrayOf",
124+
"description": "Array&lt;{ axisId: number<br>&#124;&nbsp;string, end: number, start: number }&gt;"
125+
}
126+
},
127+
"zoomInteractionConfig": {
128+
"type": {
129+
"name": "shape",
130+
"description": "{ pan?: Array&lt;'drag'<br>&#124;&nbsp;'pressAndDrag'<br>&#124;&nbsp;'wheel'<br>&#124;&nbsp;{ pointerMode?: 'mouse'<br>&#124;&nbsp;'touch', requiredKeys?: Array&lt;string&gt;, type: 'drag' }<br>&#124;&nbsp;{ pointerMode?: 'mouse'<br>&#124;&nbsp;'touch', requiredKeys?: Array&lt;string&gt;, type: 'pressAndDrag' }<br>&#124;&nbsp;{ allowedDirection?: 'x'<br>&#124;&nbsp;'xy'<br>&#124;&nbsp;'y', pointerMode?: any, requiredKeys?: Array&lt;string&gt;, type: 'wheel' }&gt;, zoom?: Array&lt;'brush'<br>&#124;&nbsp;'doubleTapReset'<br>&#124;&nbsp;'pinch'<br>&#124;&nbsp;'tapAndDrag'<br>&#124;&nbsp;'wheel'<br>&#124;&nbsp;{ pointerMode?: any, requiredKeys?: Array&lt;string&gt;, type: 'wheel' }<br>&#124;&nbsp;{ pointerMode?: any, requiredKeys?: array, type: 'pinch' }<br>&#124;&nbsp;{ pointerMode?: 'mouse'<br>&#124;&nbsp;'touch', requiredKeys?: Array&lt;string&gt;, type: 'tapAndDrag' }<br>&#124;&nbsp;{ pointerMode?: 'mouse'<br>&#124;&nbsp;'touch', requiredKeys?: Array&lt;string&gt;, type: 'doubleTapReset' }<br>&#124;&nbsp;{ pointerMode?: any, requiredKeys?: array, type: 'brush' }&gt; }"
131+
}
132+
}
133+
},
134+
"name": "HeatmapPremium",
135+
"imports": [
136+
"import { HeatmapPremium } from '@mui/x-charts-premium/HeatmapPremium';",
137+
"import { HeatmapPremium } from '@mui/x-charts-premium';"
138+
],
139+
"classes": [],
140+
"muiName": "MuiHeatmapPremium",
141+
"filename": "/packages/x-charts-premium/src/HeatmapPremium/HeatmapPremium.tsx",
142+
"inheritance": null,
143+
"demos": "<ul><li><a href=\"/x/react-charts/heatmap/\">Charts - Heatmap <a href=\"/x/introduction/licensing/#pro-plan\" title=\"Pro plan\"><span class=\"plan-pro\"></span></a></a></li></ul>",
144+
"cssComponent": false
145+
}

0 commit comments

Comments
 (0)