Skip to content

Commit a0304e8

Browse files
committed
support conic projection
1 parent 85ce864 commit a0304e8

7 files changed

Lines changed: 91 additions & 78 deletions

File tree

docs/data/charts/map/MapZoomControl.js

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ import { ChartsSurface } from '@mui/x-charts/ChartsSurface';
1818

1919
const countries = topojsonFeature(countriesTopology, 'countries');
2020

21+
const countriesWithoutAntarctica = {
22+
...countries,
23+
features: countries.features.filter(
24+
(feature) => feature.properties?.name !== 'Antarctica',
25+
),
26+
};
27+
2128
const USAStates = topojsonFeature(USATopology, 'states');
2229

2330
const projectionGroups = [
@@ -37,10 +44,10 @@ const projectionGroups = [
3744
label: 'Conic',
3845
projections: [
3946
// For now commented because those are more difficult to handle
40-
// 'conicConformal',
41-
// 'conicEqualArea',
42-
// 'conicEquidistant',
43-
// 'albers',
47+
'conicConformal',
48+
'conicEqualArea',
49+
'conicEquidistant',
50+
'albers',
4451
'albersUsa', // Special composition for the USA with an edge case for Alaska and Hawaii.
4552
],
4653
},
@@ -64,16 +71,6 @@ const cities = [
6471
{ name: 'Rio', coordinates: [-43.1729, -22.9068] },
6572
];
6673

67-
function isConicProjection(projection) {
68-
return (
69-
projection === 'conicConformal' ||
70-
projection === 'conicEqualArea' ||
71-
projection === 'conicEquidistant' ||
72-
projection === 'albers' ||
73-
projection === 'albersUsa'
74-
);
75-
}
76-
7774
export default function MapZoomControl() {
7875
const [projection, setProjection] = React.useState('naturalEarth1');
7976
const apiRef = useChartPremiumApiRef();
@@ -92,7 +89,9 @@ export default function MapZoomControl() {
9289
>
9390
<Box sx={{ flexGrow: 1, maxWidth: 800 }}>
9491
<ChartsGeoDataProviderPremium
95-
geoData={isConicProjection(projection) ? USAStates : countries}
92+
geoData={
93+
projection === 'albersUsa' ? USAStates : countriesWithoutAntarctica
94+
}
9695
projection={projection}
9796
apiRef={apiRef}
9897
zoom

docs/data/charts/map/MapZoomControl.tsx

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ const countries = topojsonFeature(
2929
'countries',
3030
) as unknown as ExtendedFeatureCollection;
3131

32+
const countriesWithoutAntarctica = {
33+
...countries,
34+
features: countries.features.filter(
35+
(feature) => feature.properties?.name !== 'Antarctica',
36+
),
37+
};
38+
3239
const USAStates = topojsonFeature(
3340
USATopology as any,
3441
'states',
@@ -51,10 +58,10 @@ const projectionGroups: { label: string; projections: D3NamedProjection[] }[] =
5158
label: 'Conic',
5259
projections: [
5360
// For now commented because those are more difficult to handle
54-
// 'conicConformal',
55-
// 'conicEqualArea',
56-
// 'conicEquidistant',
57-
// 'albers',
61+
'conicConformal',
62+
'conicEqualArea',
63+
'conicEquidistant',
64+
'albers',
5865
'albersUsa', // Special composition for the USA with an edge case for Alaska and Hawaii.
5966
],
6067
},
@@ -78,16 +85,6 @@ const cities = [
7885
{ name: 'Rio', coordinates: [-43.1729, -22.9068] },
7986
];
8087

81-
function isConicProjection(projection: D3NamedProjection) {
82-
return (
83-
projection === 'conicConformal' ||
84-
projection === 'conicEqualArea' ||
85-
projection === 'conicEquidistant' ||
86-
projection === 'albers' ||
87-
projection === 'albersUsa'
88-
);
89-
}
90-
9188
export default function MapZoomControl() {
9289
const [projection, setProjection] =
9390
React.useState<D3NamedProjection>('naturalEarth1');
@@ -107,7 +104,9 @@ export default function MapZoomControl() {
107104
>
108105
<Box sx={{ flexGrow: 1, maxWidth: 800 }}>
109106
<ChartsGeoDataProviderPremium
110-
geoData={isConicProjection(projection) ? USAStates : countries}
107+
geoData={
108+
projection === 'albersUsa' ? USAStates : countriesWithoutAntarctica
109+
}
111110
projection={projection}
112111
apiRef={apiRef}
113112
zoom

docs/data/charts/map/MapZoomOptions.js

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ import { ChartsSurface } from '@mui/x-charts/ChartsSurface';
1818

1919
const countries = topojsonFeature(countriesTopology, 'countries');
2020

21+
const countriesWithoutAntarctica = {
22+
...countries,
23+
features: countries.features.filter(
24+
(feature) => feature.properties?.name !== 'Antarctica',
25+
),
26+
};
27+
2128
const USAStates = topojsonFeature(USATopology, 'states');
2229

2330
const projectionGroups = [
@@ -37,10 +44,10 @@ const projectionGroups = [
3744
label: 'Conic',
3845
projections: [
3946
// For now commented because those are more difficult to handle
40-
// 'conicConformal',
41-
// 'conicEqualArea',
42-
// 'conicEquidistant',
43-
// 'albers',
47+
'conicConformal',
48+
'conicEqualArea',
49+
'conicEquidistant',
50+
'albers',
4451
'albersUsa', // Special composition for the USA with an edge case for Alaska and Hawaii.
4552
],
4653
},
@@ -93,7 +100,9 @@ export default function MapZoomOptions() {
93100
>
94101
<Box sx={{ flexGrow: 1, maxWidth: 800 }}>
95102
<ChartsGeoDataProviderPremium
96-
geoData={isConicProjection(projection) ? USAStates : countries}
103+
geoData={
104+
projection === 'albersUsa' ? USAStates : countriesWithoutAntarctica
105+
}
97106
projection={projection}
98107
apiRef={apiRef}
99108
zoom={{ rotationAllowed, translationAllowed, maxEmptySpace }}
@@ -111,12 +120,13 @@ export default function MapZoomOptions() {
111120
label="projection"
112121
value={projection}
113122
onChange={(event) => {
114-
setProjection(event.target.value);
123+
const value = event.target.value;
124+
setProjection(value);
115125
apiRef.current?.resetZoom();
116-
if (
117-
isConicProjection(event.target.value) ||
118-
isCylindricalProjection(event.target.value)
119-
) {
126+
if (value === 'albersUsa') {
127+
setRotationAllowed('none');
128+
setTranslationAllowed('both');
129+
} else if (isConicProjection(value) || isCylindricalProjection(value)) {
120130
setRotationAllowed('long');
121131
setTranslationAllowed('y');
122132
} else {

docs/data/charts/map/MapZoomOptions.tsx

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ const countries = topojsonFeature(
2727
'countries',
2828
) as unknown as ExtendedFeatureCollection;
2929

30+
const countriesWithoutAntarctica = {
31+
...countries,
32+
features: countries.features.filter(
33+
(feature) => feature.properties?.name !== 'Antarctica',
34+
),
35+
};
36+
3037
const USAStates = topojsonFeature(
3138
USATopology as any,
3239
'states',
@@ -49,10 +56,10 @@ const projectionGroups: { label: string; projections: D3NamedProjection[] }[] =
4956
label: 'Conic',
5057
projections: [
5158
// For now commented because those are more difficult to handle
52-
// 'conicConformal',
53-
// 'conicEqualArea',
54-
// 'conicEquidistant',
55-
// 'albers',
59+
'conicConformal',
60+
'conicEqualArea',
61+
'conicEquidistant',
62+
'albers',
5663
'albersUsa', // Special composition for the USA with an edge case for Alaska and Hawaii.
5764
],
5865
},
@@ -110,7 +117,9 @@ export default function MapZoomOptions() {
110117
>
111118
<Box sx={{ flexGrow: 1, maxWidth: 800 }}>
112119
<ChartsGeoDataProviderPremium
113-
geoData={isConicProjection(projection) ? USAStates : countries}
120+
geoData={
121+
projection === 'albersUsa' ? USAStates : countriesWithoutAntarctica
122+
}
114123
projection={projection}
115124
apiRef={apiRef}
116125
zoom={{ rotationAllowed, translationAllowed, maxEmptySpace }}
@@ -128,12 +137,13 @@ export default function MapZoomOptions() {
128137
label="projection"
129138
value={projection}
130139
onChange={(event) => {
131-
setProjection(event.target.value as D3NamedProjection);
140+
const value = event.target.value as D3NamedProjection;
141+
setProjection(value);
132142
apiRef.current?.resetZoom();
133-
if (
134-
isConicProjection(event.target.value as D3NamedProjection) ||
135-
isCylindricalProjection(event.target.value as D3NamedProjection)
136-
) {
143+
if (value === 'albersUsa') {
144+
setRotationAllowed('none');
145+
setTranslationAllowed('both');
146+
} else if (isConicProjection(value) || isCylindricalProjection(value)) {
137147
setRotationAllowed('long');
138148
setTranslationAllowed('y');
139149
} else {

packages/x-charts-premium/src/internals/plugins/useGeoProjectionZoom/mapZoom.utils.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type { MapRotationAxis, MapTranslationAxis } from './useGeoProjectionZoom
88
const DEG = Math.PI / 180;
99
const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));
1010

11-
type ProjectionFamily = 'azimuthal' | 'conic' | 'cylindrical';
11+
type ProjectionFamily = 'azimuthal' | 'conic' | 'cylindrical' | 'albersUsa';
1212

1313
const PROJECTION_FAMILY: Record<useGeoProjectionTypes.D3NamedProjection, ProjectionFamily> = {
1414
azimuthalEqualArea: 'azimuthal',
@@ -20,7 +20,7 @@ const PROJECTION_FAMILY: Record<useGeoProjectionTypes.D3NamedProjection, Project
2020
conicEqualArea: 'conic',
2121
conicEquidistant: 'conic',
2222
albers: 'conic',
23-
albersUsa: 'conic',
23+
albersUsa: 'albersUsa',
2424
equirectangular: 'cylindrical',
2525
mercator: 'cylindrical',
2626
transverseMercator: 'cylindrical',
@@ -37,8 +37,9 @@ const FAMILY_INTERACTION: Record<
3737
{ rotationAllowed: MapRotationAxis; translationAllowed: MapTranslationAxis }
3838
> = {
3939
azimuthal: { rotationAllowed: 'both', translationAllowed: 'none' },
40-
conic: { rotationAllowed: 'both', translationAllowed: 'none' },
41-
cylindrical: { rotationAllowed: 'long', translationAllowed: 'both' },
40+
conic: { rotationAllowed: 'long', translationAllowed: 'y' },
41+
cylindrical: { rotationAllowed: 'long', translationAllowed: 'y' },
42+
albersUsa: { rotationAllowed: 'none', translationAllowed: 'both' },
4243
};
4344

4445
export function getProjectionFamily(
@@ -104,19 +105,19 @@ export function getRotation(
104105
zoomFactor: number = 1,
105106
rotationAllowed: MapRotationAxis = 'both',
106107
): [number, number] | null {
107-
if (!projection.invert) {
108+
if (!projection.invert || !projection.rotate) {
108109
return null;
109110
}
110111

111-
const rotate = projection.rotate();
112+
const rotate = projection.rotate?.();
112113
const scale = projection.scale();
113114
projection.scale(scale * zoomFactor);
114-
projection.rotate([0, 0]);
115+
projection.rotate?.([0, 0]);
115116

116117
const q = projection.invert(to);
117118

118119
// Reset projection modifications to avoid side effects.
119-
projection.rotate(rotate);
120+
projection.rotate?.(rotate);
120121
projection.scale(scale);
121122

122123
if (!geoPoint || !q) {

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

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,9 @@ export const useGeoProjectionZoom: ChartPlugin<UseGeoProjectionZoomSignature> =
134134
rotationAllowed,
135135
);
136136

137-
const rotate = projection.rotate();
137+
const rotate = projection.rotate?.();
138138
if (center) {
139-
projection.rotate([-center[0], -center[1]]);
139+
projection.rotate?.([-center[0], -center[1]]);
140140
}
141141
const translation = getTranslation(
142142
store,
@@ -147,12 +147,12 @@ export const useGeoProjectionZoom: ChartPlugin<UseGeoProjectionZoomSignature> =
147147
maxEmptySpace,
148148
);
149149

150-
projection.rotate(rotate);
150+
projection.rotate?.(rotate);
151151

152-
if (center) {
152+
if (center || translation) {
153153
applyView({
154154
zoomLevel: store.state.geoProjectionZoom.zoomLevel ?? 1,
155-
center,
155+
center: center ?? store.state.geoProjectionZoom.center ?? [0, 0],
156156
translation: translation ?? store.state.geoProjectionZoom.translation ?? [0, 0],
157157
});
158158
}
@@ -182,11 +182,11 @@ export const useGeoProjectionZoom: ChartPlugin<UseGeoProjectionZoomSignature> =
182182
return;
183183
}
184184

185-
const rotate = projection.rotate();
185+
const rotate = projection.rotate?.();
186186
const scale = projection.scale();
187187
const center = getRotation(projection, geoPoint, [point.x, point.y], factor, rotationAllowed);
188188
if (center) {
189-
projection.rotate([-center[0], -center[1]]);
189+
projection.rotate?.([-center[0], -center[1]]);
190190
}
191191
projection.scale(scale * factor);
192192
const translation = getTranslation(
@@ -197,13 +197,13 @@ export const useGeoProjectionZoom: ChartPlugin<UseGeoProjectionZoomSignature> =
197197
translationAllowed,
198198
maxEmptySpace,
199199
);
200-
projection.rotate(rotate);
200+
projection.rotate?.(rotate);
201201
projection.scale(scale);
202202

203-
if (center) {
203+
if (center || translation) {
204204
applyView({
205205
zoomLevel: nextZoom,
206-
center,
206+
center: center ?? store.state.geoProjectionZoom.center ?? [0, 0],
207207
translation: translation ?? store.state.geoProjectionZoom.translation ?? [0, 0],
208208
});
209209
}
@@ -232,9 +232,9 @@ export const useGeoProjectionZoom: ChartPlugin<UseGeoProjectionZoomSignature> =
232232
}
233233
const center = getRotation(projection, geoPoint, [point.x, point.y], factor, rotationAllowed);
234234
const scale = projection.scale();
235-
const rotate = projection.rotate();
235+
const rotate = projection.rotate?.();
236236
if (center) {
237-
projection.rotate([-center![0], -center![1]]);
237+
projection.rotate?.([-center![0], -center![1]]);
238238
}
239239
projection.scale(scale * factor);
240240
const translation = getTranslation(
@@ -245,7 +245,7 @@ export const useGeoProjectionZoom: ChartPlugin<UseGeoProjectionZoomSignature> =
245245
translationAllowed,
246246
maxEmptySpace,
247247
);
248-
projection.rotate(rotate);
248+
projection.rotate?.(rotate);
249249
projection.scale(scale);
250250

251251
if (center || translation) {

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { selectorChartDrawingArea } from '../../corePlugins/useChartDimensions/u
1515
import type { UseGeoProjectionZoomSignature } from '../useGeoProjectionZoom/useGeoProjectionZoom.types';
1616
import type { GeoTooltipPosition } from '../../corePlugins/useChartSeriesConfig';
1717
import type { ChartState } from '../../models/chart';
18-
import { getParallels, isConicProjection, resolveProjectionInstance } from './projection.utils';
18+
import { getParallels, resolveProjectionInstance } from './projection.utils';
1919

2020
const ZERO_COORDINATES: [number, number] = [0, 0];
2121

@@ -205,12 +205,6 @@ export const selectorChartProjection = createSelectorMemoized(
205205
// `fitScale` is the `zoomLevel === 1` reference scale, computed independently in
206206
// `selectorFitScale` so it stays stable across pan/zoom transforms.
207207
projection.scale(zoomLevel != null && zoomLevel !== 1 ? fitScale * zoomLevel : fitScale);
208-
209-
// Conic projections are positioned via `rotate`; `center` panning is not applied.
210-
if (isConicProjection(projection)) {
211-
return projection;
212-
}
213-
214208
projection.translate([
215209
drawingArea.left + drawingArea.width / 2 + (translation?.[0] ?? 0) * drawingArea.width,
216210
drawingArea.top + drawingArea.height / 2 + (translation?.[1] ?? 0) * drawingArea.height,

0 commit comments

Comments
 (0)