Skip to content

Commit f16364c

Browse files
committed
move zoom params/state to its dedicated pluggin
1 parent a355c77 commit f16364c

10 files changed

Lines changed: 214 additions & 294 deletions

File tree

docs/data/charts/map/ZoomMap.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import * as React from 'react';
2+
import Box from '@mui/material/Box';
3+
import Stack from '@mui/material/Stack';
4+
import Typography from '@mui/material/Typography';
5+
import { feature as topojsonFeature } from 'topojson-client';
6+
import countriesTopology from 'visionscarto-world-atlas/world/110m.json';
7+
import { Unstable_ChartsGeoDataProviderPremium as ChartsGeoDataProviderPremium } from '@mui/x-charts-premium/ChartsGeoDataProviderPremium';
8+
import { MapShapePlot } from '@mui/x-charts-premium/Map';
9+
import { ChartsSurface } from '@mui/x-charts/ChartsSurface';
10+
import { ChartsTooltip } from '@mui/x-charts-premium/ChartsTooltip';
11+
import { ContinuousColorLegend } from '@mui/x-charts-premium/ChartsLegend';
12+
13+
import { internetUsageByCountry } from '../dataset/internetUsageByCountry';
14+
import { withCountryCodeAsName, countryData } from '../dataset/countryData';
15+
16+
const countries = withCountryCodeAsName(
17+
topojsonFeature(countriesTopology, 'countries'),
18+
);
19+
20+
const data = Object.keys(countryData).map((code) => ({
21+
name: code,
22+
label: countryData[code].country,
23+
colorValue: internetUsageByCountry[code],
24+
}));
25+
26+
export default function ZoomMap() {
27+
return (
28+
<Stack spacing={2} sx={{ width: '100%', maxWidth: 800 }}>
29+
<Typography variant="body2" component="h6" sx={{ textAlign: 'end' }}>
30+
Share of the population using the Internet in 2020
31+
</Typography>
32+
<Box sx={{ width: '100%' }}>
33+
<ChartsGeoDataProviderPremium
34+
geoData={countries}
35+
projection="naturalEarth1"
36+
height={360}
37+
series={[
38+
{
39+
type: 'mapShape',
40+
label: 'Internet usage',
41+
data,
42+
valueFormatter: (point) =>
43+
point.colorValue == null
44+
? 'No data'
45+
: `${point.colorValue.toFixed(1)}%`,
46+
},
47+
]}
48+
zAxis={[
49+
{
50+
id: 'internet-usage',
51+
colorMap: {
52+
type: 'continuous',
53+
min: 0,
54+
max: 100,
55+
color: ['#e3f2fd', '#0d47a1'],
56+
unownedColor: '#f5f5f5',
57+
},
58+
},
59+
]}
60+
>
61+
<ChartsSurface>
62+
<MapShapePlot stroke="#fff" strokeWidth={0.3} />
63+
</ChartsSurface>
64+
<ChartsTooltip trigger="item" />
65+
<ContinuousColorLegend axisDirection="z" sx={{ maxWidth: 150 }} />
66+
</ChartsGeoDataProviderPremium>
67+
</Box>
68+
</Stack>
69+
);
70+
}

docs/data/charts/map/ZoomMap.tsx

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import * as React from 'react';
2+
import Box from '@mui/material/Box';
3+
import Stack from '@mui/material/Stack';
4+
import Typography from '@mui/material/Typography';
5+
import { feature as topojsonFeature } from 'topojson-client';
6+
import countriesTopology from 'visionscarto-world-atlas/world/110m.json';
7+
import { Unstable_ChartsGeoDataProviderPremium as ChartsGeoDataProviderPremium } from '@mui/x-charts-premium/ChartsGeoDataProviderPremium';
8+
import { MapShapePlot } from '@mui/x-charts-premium/Map';
9+
import { ChartsSurface } from '@mui/x-charts/ChartsSurface';
10+
import { ChartsTooltip } from '@mui/x-charts-premium/ChartsTooltip';
11+
import { ContinuousColorLegend } from '@mui/x-charts-premium/ChartsLegend';
12+
import { type ExtendedFeatureCollection } from '@mui/x-charts-vendor/d3-geo';
13+
import { internetUsageByCountry } from '../dataset/internetUsageByCountry';
14+
import { withCountryCodeAsName, countryData } from '../dataset/countryData';
15+
16+
const countries = withCountryCodeAsName(
17+
topojsonFeature(
18+
countriesTopology as any,
19+
'countries',
20+
) as unknown as ExtendedFeatureCollection,
21+
);
22+
23+
const data = Object.keys(countryData).map((code) => ({
24+
name: code,
25+
label: countryData[code].country,
26+
colorValue: internetUsageByCountry[code],
27+
}));
28+
29+
export default function ZoomMap() {
30+
return (
31+
<Stack spacing={2} sx={{ width: '100%', maxWidth: 800 }}>
32+
<Typography variant="body2" component="h6" sx={{ textAlign: 'end' }}>
33+
Share of the population using the Internet in 2020
34+
</Typography>
35+
<Box sx={{ width: '100%' }}>
36+
<ChartsGeoDataProviderPremium
37+
geoData={countries}
38+
projection="naturalEarth1"
39+
height={360}
40+
zoom
41+
series={[
42+
{
43+
type: 'mapShape',
44+
label: 'Internet usage',
45+
data,
46+
valueFormatter: (point) =>
47+
point.colorValue == null
48+
? 'No data'
49+
: `${point.colorValue.toFixed(1)}%`,
50+
},
51+
]}
52+
zAxis={[
53+
{
54+
id: 'internet-usage',
55+
colorMap: {
56+
type: 'continuous',
57+
min: 0,
58+
max: 100,
59+
color: ['#e3f2fd', '#0d47a1'],
60+
unownedColor: '#f5f5f5',
61+
},
62+
},
63+
]}
64+
>
65+
<ChartsSurface>
66+
<MapShapePlot stroke="#fff" strokeWidth={0.3} />
67+
</ChartsSurface>
68+
<ChartsTooltip trigger="item" />
69+
70+
<ContinuousColorLegend axisDirection="z" sx={{ maxWidth: 150 }} />
71+
</ChartsGeoDataProviderPremium>
72+
</Box>
73+
</Stack>
74+
);
75+
}

docs/data/charts/map/map.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ The map provider `ChartsGeoDataProviderPremium` uses three main props:
1818

1919
- `geoData`: an array of geographical objects defining the map (countries, cities, road, ...)
2020
- `projection`: a string that defines how the objects should be projected on the SVG.
21-
- `series`: the data associated to the geographical objects.
22-
23-
The series can be of type `'mapShape'`, `'mapPoint'`, or `'mapLink'`.
21+
- `series`: the data associated to the geographical objects of type `'mapShape'`.
2422

2523
{{"demo": "BasicGeoDataPlot.js"}}
2624

@@ -164,6 +162,10 @@ the `fade` option decides which items are dimmed
164162

165163
{{"demo": "HighlightedMapShape.js"}}
166164

165+
## Zoom
166+
167+
{{"demo": "ZoomMap.js"}}
168+
167169
## Managing visibility from the legend
168170

169171
When `toggleVisibilityOnClick` is set on the `ChartsLegend`, clicking on a series toggles

packages/x-charts-premium/src/internals/plugins/useGeoProjection/useGeoProjection.selectors.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ import type {
2828
UseGeoProjectionSignature,
2929
UseGeoProjectionState,
3030
} from './useGeoProjection.types';
31+
import type { UseGeoProjectionZoomSignature } from '../useGeoProjectionZoom/useGeoProjectionZoom.types';
32+
33+
/**
34+
* The projection lives in `useGeoProjection`, while its zoom state (`zoomLevel`/`center`) lives in
35+
* the optional `useGeoProjectionZoom` plugin. The projection selector reads both, so its state is
36+
* typed with the zoom signature as an optional dependency.
37+
*/
38+
type GeoChartState = ChartState<[], [UseGeoProjectionSignature, UseGeoProjectionZoomSignature]>;
3139

3240
const PROJECTION_FACTORIES: Record<D3NamedProjection, (() => GeoProjection) | undefined> = {
3341
// Azimuthal projections (https://d3js.org/d3-geo/azimuthal)
@@ -56,9 +64,11 @@ const isConicProjection = (projection: GeoProjection): projection is GeoConicPro
5664
return 'parallels' in projection && typeof projection.parallels === 'function';
5765
};
5866
export const selectorChartGeoProjectionState = (
59-
state: ChartState<[], [UseGeoProjectionSignature]>,
67+
state: GeoChartState,
6068
): UseGeoProjectionState['geoProjection'] | undefined => state.geoProjection;
6169

70+
const selectorChartGeoProjectionZoomState = (state: GeoChartState) => state.geoProjectionZoom;
71+
6272
export const selectorChartRawGeoData: (
6373
state: ChartState<[], [UseGeoProjectionSignature]>,
6474
) => ExtendedFeatureCollection | null = createSelector(
@@ -72,8 +82,8 @@ export const selectorChartRawProjection = createSelector(
7282
);
7383

7484
export const selectorChartZoomLevel = createSelector(
75-
selectorChartGeoProjectionState,
76-
(geoProjection): number | null => geoProjection?.zoomLevel ?? null,
85+
selectorChartGeoProjectionZoomState,
86+
(geoProjectionZoom): number | null => geoProjectionZoom?.zoomLevel ?? 1,
7787
);
7888

7989
const selectorChartRotate = createSelectorMemoized(
@@ -82,8 +92,8 @@ const selectorChartRotate = createSelectorMemoized(
8292
);
8393

8494
const selectorChartCenter = createSelectorMemoized(
85-
selectorChartGeoProjectionState,
86-
(geoProjection): [number, number] | null => geoProjection?.center ?? null,
95+
selectorChartGeoProjectionZoomState,
96+
(geoProjectionZoom): [number, number] | null => geoProjectionZoom?.center ?? [0, 0],
8797
);
8898

8999
const selectorChartParallels = createSelectorMemoized(

packages/x-charts-premium/src/internals/plugins/useGeoProjection/useGeoProjection.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { type ChartPlugin } from '@mui/x-charts/internals';
44
import { type UseGeoProjectionSignature } from './useGeoProjection.types';
55

66
export const useGeoProjection: ChartPlugin<UseGeoProjectionSignature> = ({ params, store }) => {
7-
const { geoData, projection, rotate, zoomLevel, center } = params;
7+
const { geoData, projection, rotate } = params;
88

99
const isFirstRender = React.useRef(true);
1010
React.useEffect(() => {
@@ -13,25 +13,20 @@ export const useGeoProjection: ChartPlugin<UseGeoProjectionSignature> = ({ param
1313
return;
1414
}
1515

16-
// Preserve the zoom state (`zoomLevel`/`center`) owned by `useGeoProjectionZoom`.
1716
store.set('geoProjection', {
1817
...store.state.geoProjection,
1918
geoData: geoData ?? null,
2019
projection: projection ?? null,
2120
rotate: rotate ?? null,
22-
zoomLevel: zoomLevel ?? 1,
23-
center: center ?? [0, 0],
2421
});
25-
}, [geoData, projection, rotate, zoomLevel, center, store]);
22+
}, [geoData, projection, rotate, store]);
2623

2724
return {};
2825
};
2926

3027
useGeoProjection.params = {
3128
geoData: true,
3229
projection: true,
33-
zoomLevel: true,
34-
center: true,
3530
rotate: true,
3631
};
3732

@@ -42,7 +37,5 @@ useGeoProjection.getInitialState = (params) => ({
4237
geoData: params.geoData ?? null,
4338
projection: params.projection ?? null,
4439
rotate: params.rotate ?? null,
45-
zoomLevel: null,
46-
center: null,
4740
},
4841
});

packages/x-charts-premium/src/internals/plugins/useGeoProjection/useGeoProjection.types.ts

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { type ChartPluginSignature } from '@mui/x-charts/internals';
22
import { type GeoProjection, type ExtendedFeatureCollection } from '@mui/x-charts-vendor/d3-geo';
3+
import { type UseGeoProjectionZoomSignature } from '../useGeoProjectionZoom/useGeoProjectionZoom.types';
34

45
export type D3NamedProjection =
56
| 'azimuthalEqualArea'
@@ -41,17 +42,6 @@ export interface UseGeoProjectionParameters {
4142
* The rotation of the projection, specified as a `[longitude, latitude]` pair in degrees.
4243
*/
4344
rotate?: [number, number];
44-
/**
45-
* The level of zoom on the map.
46-
* 1 being the map data fit in the drawing area.
47-
* @default 1
48-
*/
49-
zoomLevel?: number | null;
50-
/**
51-
* The geographic coordinate `[longitude, latitude]` displayed at the center of the drawing area.
52-
* @default [0, 0]
53-
*/
54-
center?: [number, number] | null;
5545
}
5646

5747
export type UseGeoProjectionDefaultizedParameters = UseGeoProjectionParameters;
@@ -61,17 +51,6 @@ export interface UseGeoProjectionState {
6151
geoData: ExtendedFeatureCollection | null;
6252
projection: GeoProjectionInput | null;
6353
rotate: [number, number] | null;
64-
/**
65-
* The zoom level, as a multiple of the scale that fits the data in the drawing area.
66-
* `null` (the default) and `1` both mean fit-to-data. The absolute projection scale is
67-
* derived as `fitScale * zoomLevel`, so this stays valid across resizes.
68-
*/
69-
zoomLevel: number | null;
70-
/**
71-
* The geographic coordinate `[longitude, latitude]` displayed at the center of the drawing area.
72-
* `null` keeps the data centered (the fit center).
73-
*/
74-
center: [number, number] | null;
7554
/**
7655
* The two standard parallels used by conic projections, if applicable.
7756
* Used for projection 'conicConformal', 'conicEqualArea', 'conicEquidistant'.
@@ -84,4 +63,8 @@ export type UseGeoProjectionSignature = ChartPluginSignature<{
8463
params: UseGeoProjectionParameters;
8564
defaultizedParams: UseGeoProjectionDefaultizedParameters;
8665
state: UseGeoProjectionState;
66+
// The zoom state (`zoomLevel`/`center`) is owned by `useGeoProjectionZoom`, but the projection
67+
// selector reads it to derive the projection scale/translation. Optional so the projection still
68+
// works when the zoom plugin is not registered.
69+
optionalDependencies: [UseGeoProjectionZoomSignature];
8770
}>;

0 commit comments

Comments
 (0)