Skip to content

Commit b34b7ee

Browse files
authored
Refactor vector symbology menus: drive logic with data structure (#752)
* Refactor vector symbology menu to be data-structure-driven * Add descriptions for each render type
1 parent de818af commit b34b7ee

File tree

12 files changed

+454
-375
lines changed

12 files changed

+454
-375
lines changed

packages/base/src/dialogs/symbology/hooks/useGetProperties.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ export const useGetProperties = ({
2020
layerId,
2121
model,
2222
}: IUseGetPropertiesProps): IUseGetPropertiesResult => {
23-
const [featureProperties, setFeatureProperties] = useState<any>({});
24-
const [isLoading, setIsLoading] = useState(true);
25-
const [error, setError] = useState<Error | undefined>(undefined);
23+
const [featureProperties, setFeatureProperties] = useState<
24+
Record<string, Set<any>>
25+
>({});
26+
const [isLoading, setIsLoading] = useState<boolean>(true);
27+
const [error, setError] = useState<Error | undefined>();
2628

2729
const getProperties = async () => {
2830
if (!layerId) {

packages/base/src/dialogs/symbology/symbologyDialog.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,18 @@ export interface ISymbologyDialogProps {
1717
layerId?: string;
1818
}
1919

20+
export interface ISymbologyDialogWithAttributesProps
21+
extends ISymbologyDialogProps {
22+
selectableAttributesAndValues: Record<string, Set<any>>;
23+
}
24+
2025
export interface ISymbologyTabbedDialogProps extends ISymbologyDialogProps {
2126
symbologyTab: SymbologyTab;
2227
}
2328

29+
export type ISymbologyTabbedDialogWithAttributesProps =
30+
ISymbologyDialogWithAttributesProps & ISymbologyTabbedDialogProps;
31+
2432
export interface ISymbologyWidgetOptions {
2533
model: IJupyterGISModel;
2634
state: IStateDB;

packages/base/src/dialogs/symbology/vector_layer/VectorRendering.tsx

Lines changed: 144 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,118 @@
1+
import { LayerType, IJGISLayer } from '@jupytergis/schema';
12
import React, { useEffect, useState } from 'react';
23

34
import { useGetProperties } from '@/src/dialogs/symbology/hooks/useGetProperties';
45
import { ISymbologyDialogProps } from '@/src/dialogs/symbology/symbologyDialog';
56
import {
67
getColorCodeFeatureAttributes,
78
getNumericFeatureAttributes,
9+
objectEntries,
810
} from '@/src/tools';
9-
import { SymbologyTab } from '@/src/types';
11+
import { SymbologyTab, VectorRenderType } from '@/src/types';
1012
import Canonical from './types/Canonical';
1113
import Categorized from './types/Categorized';
1214
import Graduated from './types/Graduated';
1315
import Heatmap from './types/Heatmap';
1416
import SimpleSymbol from './types/SimpleSymbol';
1517

18+
interface IRenderTypeProps {
19+
component: any;
20+
attributeChecker?: (...args: any[]) => any;
21+
supportedLayerTypes: string[];
22+
isTabbed: boolean;
23+
}
24+
type RenderTypeOptions = {
25+
[key in VectorRenderType]: IRenderTypeProps;
26+
};
27+
28+
interface ISelectableRenderTypeProps extends IRenderTypeProps {
29+
selectableAttributesAndValues?: Record<string, Set<any>>;
30+
layerTypeSupported: boolean;
31+
}
32+
type SelectableRenderTypes = {
33+
[key in VectorRenderType]: ISelectableRenderTypeProps;
34+
};
35+
36+
const RENDER_TYPE_OPTIONS: RenderTypeOptions = {
37+
'Single Symbol': {
38+
component: SimpleSymbol,
39+
supportedLayerTypes: ['VectorLayer', 'VectorTileLayer', 'HeatmapLayer'],
40+
isTabbed: true,
41+
},
42+
Canonical: {
43+
component: Canonical,
44+
attributeChecker: getColorCodeFeatureAttributes,
45+
supportedLayerTypes: ['VectorLayer', 'HeatmapLayer'],
46+
isTabbed: false,
47+
},
48+
Graduated: {
49+
component: Graduated,
50+
attributeChecker: getNumericFeatureAttributes,
51+
supportedLayerTypes: ['VectorLayer', 'HeatmapLayer'],
52+
isTabbed: true,
53+
},
54+
Categorized: {
55+
component: Categorized,
56+
attributeChecker: getNumericFeatureAttributes,
57+
supportedLayerTypes: ['VectorLayer', 'HeatmapLayer'],
58+
isTabbed: true,
59+
},
60+
Heatmap: {
61+
component: Heatmap,
62+
supportedLayerTypes: ['VectorLayer', 'HeatmapLayer'],
63+
isTabbed: false,
64+
},
65+
} as const;
66+
67+
const getSelectableRenderTypes = (
68+
featureProperties: Record<string, Set<any>>,
69+
layerType: LayerType,
70+
): SelectableRenderTypes => {
71+
const entries = objectEntries(RENDER_TYPE_OPTIONS).map(
72+
([renderType, renderTypeProps]) => [
73+
renderType,
74+
{
75+
...renderTypeProps,
76+
...(renderTypeProps.attributeChecker
77+
? {
78+
selectableAttributesAndValues:
79+
renderTypeProps.attributeChecker(featureProperties),
80+
}
81+
: {}),
82+
layerTypeSupported:
83+
renderTypeProps.supportedLayerTypes.includes(layerType),
84+
},
85+
],
86+
);
87+
return Object.fromEntries(entries);
88+
};
89+
90+
const useLayerRenderType = (
91+
layer: IJGISLayer,
92+
setSelectedRenderType: React.Dispatch<
93+
React.SetStateAction<VectorRenderType | undefined>
94+
>,
95+
) =>
96+
useEffect(() => {
97+
let renderType = layer.parameters?.symbologyState?.renderType;
98+
if (!renderType) {
99+
renderType = layer.type === 'HeatmapLayer' ? 'Heatmap' : 'Single Symbol';
100+
}
101+
setSelectedRenderType(renderType);
102+
}, []);
103+
16104
const VectorRendering = ({
17105
model,
18106
state,
19107
okSignalPromise,
20108
cancel,
21109
layerId,
22110
}: ISymbologyDialogProps) => {
23-
const [selectedRenderType, setSelectedRenderType] = useState('');
24-
const [componentToRender, setComponentToRender] = useState<any>(null);
25-
const [renderTypeOptions, setRenderTypeOptions] = useState<string[]>([
26-
'Single Symbol',
27-
]);
111+
const [selectedRenderType, setSelectedRenderType] = useState<
112+
VectorRenderType | undefined
113+
>();
28114
const [symbologyTab, setSymbologyTab] = useState<SymbologyTab>('color');
29115

30-
let RenderComponent;
31-
32116
if (!layerId) {
33117
return;
34118
}
@@ -37,110 +121,31 @@ const VectorRendering = ({
37121
return;
38122
}
39123

40-
const { featureProperties } = useGetProperties({
124+
const { featureProperties, isLoading: featuresLoading } = useGetProperties({
41125
layerId,
42126
model: model,
43127
});
44128

45-
useEffect(() => {
46-
let renderType = layer.parameters?.symbologyState?.renderType;
47-
if (!renderType) {
48-
renderType = layer.type === 'HeatmapLayer' ? 'Heatmap' : 'Single Symbol';
49-
}
50-
setSelectedRenderType(renderType);
129+
useLayerRenderType(layer, setSelectedRenderType);
51130

52-
const vectorLayerOptions = ['Single Symbol', 'Heatmap'];
53-
54-
if (
55-
Object.keys(getColorCodeFeatureAttributes(featureProperties)).length > 0
56-
) {
57-
vectorLayerOptions.push('Canonical');
58-
}
59-
if (
60-
Object.keys(getNumericFeatureAttributes(featureProperties)).length > 0
61-
) {
62-
vectorLayerOptions.push('Graduated', 'Categorized');
63-
}
131+
if (featuresLoading) {
132+
return <p>Loading...</p>;
133+
}
64134

65-
const options: Record<string, string[]> = {
66-
VectorLayer: vectorLayerOptions,
67-
VectorTileLayer: ['Single Symbol'],
68-
HeatmapLayer: ['Single Symbol', 'Graduated', 'Categorized', 'Heatmap'],
69-
};
70-
setRenderTypeOptions(options[layer.type]);
71-
}, [featureProperties]);
135+
if (selectedRenderType === undefined) {
136+
// typeguard
137+
return;
138+
}
72139

73-
useEffect(() => {
74-
switch (selectedRenderType) {
75-
case 'Single Symbol':
76-
RenderComponent = (
77-
<SimpleSymbol
78-
model={model}
79-
state={state}
80-
okSignalPromise={okSignalPromise}
81-
cancel={cancel}
82-
layerId={layerId}
83-
symbologyTab={symbologyTab}
84-
/>
85-
);
86-
break;
87-
case 'Graduated':
88-
RenderComponent = (
89-
<Graduated
90-
model={model}
91-
state={state}
92-
okSignalPromise={okSignalPromise}
93-
cancel={cancel}
94-
layerId={layerId}
95-
symbologyTab={symbologyTab}
96-
/>
97-
);
98-
break;
99-
case 'Categorized':
100-
RenderComponent = (
101-
<Categorized
102-
model={model}
103-
state={state}
104-
okSignalPromise={okSignalPromise}
105-
cancel={cancel}
106-
layerId={layerId}
107-
symbologyTab={symbologyTab}
108-
/>
109-
);
110-
break;
111-
case 'Canonical':
112-
RenderComponent = (
113-
<Canonical
114-
model={model}
115-
state={state}
116-
okSignalPromise={okSignalPromise}
117-
cancel={cancel}
118-
layerId={layerId}
119-
/>
120-
);
121-
break;
122-
case 'Heatmap':
123-
RenderComponent = (
124-
<Heatmap
125-
model={model}
126-
state={state}
127-
okSignalPromise={okSignalPromise}
128-
cancel={cancel}
129-
layerId={layerId}
130-
/>
131-
);
132-
break;
133-
default:
134-
RenderComponent = <div>Select a render type</div>;
135-
}
136-
setComponentToRender(RenderComponent);
137-
}, [selectedRenderType, symbologyTab]);
140+
const selectableRenderTypes = getSelectableRenderTypes(
141+
featureProperties,
142+
layer.type,
143+
);
144+
const selectedRenderTypeProps = selectableRenderTypes[selectedRenderType];
138145

139146
return (
140147
<>
141-
{['Single Symbol', 'Graduated', 'Categorized'].includes(
142-
selectedRenderType,
143-
) && (
148+
{selectedRenderTypeProps.isTabbed && (
144149
<div className="jp-gis-symbology-tabs">
145150
{(['color', 'radius'] as const).map(tab => (
146151
<button
@@ -155,27 +160,45 @@ const VectorRendering = ({
155160
)}
156161
<div className="jp-gis-symbology-row">
157162
<label htmlFor="render-type-select">Render Type:</label>
158-
<select
159-
name="render-type-select"
160-
id="render-type-select"
161-
value={selectedRenderType}
162-
onChange={event => {
163-
setSelectedRenderType(event.target.value);
164-
}}
165-
>
166-
{renderTypeOptions
167-
.filter(
168-
option => !(symbologyTab === 'radius' && option === 'Heatmap'),
169-
)
170-
.map(option => (
171-
<option key={option} value={option}>
172-
{option}
173-
</option>
174-
))}
175-
</select>
163+
<div className="jp-select-wrapper">
164+
<select
165+
name="render-type-select"
166+
id="render-type-select"
167+
className="jp-mod-styled"
168+
value={selectedRenderType}
169+
onChange={event => {
170+
setSelectedRenderType(event.target.value as VectorRenderType);
171+
}}
172+
>
173+
{objectEntries(selectableRenderTypes)
174+
.filter(
175+
([renderType, renderTypeProps]) =>
176+
renderTypeProps.layerTypeSupported &&
177+
!(renderType === 'Heatmap' && symbologyTab === 'radius'),
178+
)
179+
.map(([renderType, _]) => (
180+
<option key={renderType} value={renderType}>
181+
{renderType}
182+
</option>
183+
))}
184+
</select>
185+
</div>
176186
</div>
177187

178-
{componentToRender}
188+
<selectedRenderTypeProps.component
189+
model={model}
190+
state={state}
191+
okSignalPromise={okSignalPromise}
192+
cancel={cancel}
193+
layerId={layerId}
194+
{...(selectedRenderTypeProps.isTabbed ? { symbologyTab } : {})}
195+
{...(selectedRenderTypeProps.selectableAttributesAndValues
196+
? {
197+
selectableAttributesAndValues:
198+
selectedRenderTypeProps.selectableAttributesAndValues,
199+
}
200+
: {})}
201+
/>
179202
</>
180203
);
181204
};

0 commit comments

Comments
 (0)