@jupytergis/base 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commands/BaseCommandIDs.d.ts +1 -1
- package/lib/commands/BaseCommandIDs.js +1 -1
- package/lib/commands/index.js +28 -34
- package/lib/constants.js +1 -0
- package/lib/dialogs/symbology/classificationModes.js +12 -16
- package/lib/dialogs/symbology/colorRampUtils.d.ts +47 -3
- package/lib/dialogs/symbology/colorRampUtils.js +112 -13
- package/lib/dialogs/symbology/components/color_ramp/ColorRampSelector.js +6 -14
- package/lib/dialogs/symbology/components/color_ramp/ColorRampSelectorEntry.d.ts +2 -2
- package/lib/dialogs/symbology/components/color_ramp/ColorRampSelectorEntry.js +3 -11
- package/lib/dialogs/symbology/components/color_ramp/RgbaColorPicker.d.ts +13 -0
- package/lib/dialogs/symbology/components/color_ramp/RgbaColorPicker.js +98 -0
- package/lib/dialogs/symbology/components/color_stops/StopContainer.js +3 -1
- package/lib/dialogs/symbology/components/color_stops/StopRow.d.ts +1 -1
- package/lib/dialogs/symbology/components/color_stops/StopRow.js +12 -7
- package/lib/dialogs/symbology/symbologyDialog.d.ts +2 -1
- package/lib/dialogs/symbology/symbologyUtils.d.ts +2 -2
- package/lib/dialogs/symbology/symbologyUtils.js +58 -40
- package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +14 -2
- package/lib/dialogs/symbology/vector_layer/VectorRendering.js +6 -5
- package/lib/dialogs/symbology/vector_layer/components/ValueSelect.js +3 -1
- package/lib/dialogs/symbology/vector_layer/types/Canonical.js +70 -5
- package/lib/dialogs/symbology/vector_layer/types/Categorized.js +81 -34
- package/lib/dialogs/symbology/vector_layer/types/Graduated.js +155 -43
- package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.js +31 -16
- package/lib/formbuilder/formselectors.js +4 -1
- package/lib/formbuilder/objectform/components/WmsTileSourceUrlInput.d.ts +3 -0
- package/lib/formbuilder/objectform/components/WmsTileSourceUrlInput.js +84 -0
- package/lib/formbuilder/objectform/source/index.d.ts +1 -0
- package/lib/formbuilder/objectform/source/index.js +1 -0
- package/lib/formbuilder/objectform/source/wmsTileSource.d.ts +4 -0
- package/lib/formbuilder/objectform/source/wmsTileSource.js +78 -0
- package/lib/formbuilder/objectform/useSchemaFormState.d.ts +1 -1
- package/lib/mainview/mainView.d.ts +3 -1
- package/lib/mainview/mainView.js +170 -23
- package/lib/menus.js +4 -0
- package/lib/panelview/components/layers.js +19 -2
- package/lib/panelview/components/legendItem.js +14 -4
- package/lib/panelview/filter-panel/Filter.d.ts +3 -0
- package/lib/panelview/filter-panel/Filter.js +9 -9
- package/lib/panelview/leftpanel.js +0 -7
- package/lib/panelview/story-maps/SpectaPanel.js +2 -2
- package/lib/panelview/story-maps/StoryViewerPanel.d.ts +1 -2
- package/lib/panelview/story-maps/StoryViewerPanel.js +1 -1
- package/lib/panelview/story-maps/components/SpectaDesktopView.d.ts +2 -1
- package/lib/panelview/story-maps/components/SpectaDesktopView.js +4 -4
- package/lib/panelview/story-maps/hooks/useStoryMap.d.ts +1 -0
- package/lib/panelview/story-maps/hooks/useStoryMap.js +3 -0
- package/lib/stacBrowser/components/filter-extension/QueryableComboBox.js +61 -20
- package/lib/stacBrowser/hooks/useStacFilterExtension.d.ts +1 -1
- package/lib/stacBrowser/hooks/useStacFilterExtension.js +195 -111
- package/lib/stacBrowser/hooks/useStacSearch.d.ts +1 -0
- package/lib/stacBrowser/hooks/useStacSearch.js +18 -10
- package/lib/tools.d.ts +1 -1
- package/lib/tools.js +3 -3
- package/lib/types.d.ts +7 -1
- package/package.json +5 -2
- package/style/shared/button.css +2 -5
- package/style/shared/input.css +2 -2
- package/style/shared/tabs.css +2 -2
- package/style/storyPanel.css +7 -0
- package/style/symbologyDialog.css +45 -1
|
@@ -3,7 +3,7 @@ import { IStopRow } from "../../symbologyDialog";
|
|
|
3
3
|
import { SymbologyValue } from "../../../../types";
|
|
4
4
|
declare const StopRow: React.FC<{
|
|
5
5
|
index: number;
|
|
6
|
-
dataValue: number;
|
|
6
|
+
dataValue: number | string;
|
|
7
7
|
symbologyValue: SymbologyValue;
|
|
8
8
|
stopRows: IStopRow[];
|
|
9
9
|
setStopRows: (stopRows: IStopRow[]) => void;
|
|
@@ -2,7 +2,8 @@ import { faTrash } from '@fortawesome/free-solid-svg-icons';
|
|
|
2
2
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
3
3
|
import { Button } from '@jupyterlab/ui-components';
|
|
4
4
|
import React, { useEffect, useRef } from 'react';
|
|
5
|
-
import {
|
|
5
|
+
import { colorToRgba } from "../../colorRampUtils";
|
|
6
|
+
import RgbaColorPicker from "../color_ramp/RgbaColorPicker";
|
|
6
7
|
const StopRow = ({ index, dataValue, symbologyValue, stopRows, setStopRows, deleteRow, useNumber, }) => {
|
|
7
8
|
const inputRef = useRef(null);
|
|
8
9
|
useEffect(() => {
|
|
@@ -13,7 +14,8 @@ const StopRow = ({ index, dataValue, symbologyValue, stopRows, setStopRows, dele
|
|
|
13
14
|
}, [stopRows]);
|
|
14
15
|
const handleStopChange = (event) => {
|
|
15
16
|
const newRows = [...stopRows];
|
|
16
|
-
|
|
17
|
+
const value = event.target.value;
|
|
18
|
+
newRows[index].stop = useNumber ? +value : value;
|
|
17
19
|
setStopRows(newRows);
|
|
18
20
|
};
|
|
19
21
|
const handleBlur = () => {
|
|
@@ -31,14 +33,17 @@ const StopRow = ({ index, dataValue, symbologyValue, stopRows, setStopRows, dele
|
|
|
31
33
|
};
|
|
32
34
|
const handleOutputChange = (event) => {
|
|
33
35
|
const newRows = [...stopRows];
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
newRows[index].output = +event.target.value;
|
|
37
|
+
setStopRows(newRows);
|
|
38
|
+
};
|
|
39
|
+
const handleColorOutputChange = (color) => {
|
|
40
|
+
const newRows = [...stopRows];
|
|
41
|
+
newRows[index].output = color;
|
|
37
42
|
setStopRows(newRows);
|
|
38
43
|
};
|
|
39
44
|
return (React.createElement("div", { className: "jp-gis-color-row" },
|
|
40
|
-
React.createElement("input", { id: `jp-gis-color-value-${index}`, type:
|
|
41
|
-
useNumber ? (React.createElement("input", { type: "number", ref: inputRef, value: symbologyValue, onChange: handleOutputChange, className: "jp-mod-styled jp-gis-color-row-output-input" })) : (React.createElement(
|
|
45
|
+
React.createElement("input", { id: `jp-gis-color-value-${index}`, type: useNumber ? 'number' : 'text', value: dataValue, onChange: handleStopChange, onBlur: handleBlur, className: "jp-mod-styled jp-gis-color-row-value-input" }),
|
|
46
|
+
useNumber ? (React.createElement("input", { type: "number", ref: inputRef, value: symbologyValue, onChange: handleOutputChange, className: "jp-mod-styled jp-gis-color-row-output-input" })) : (React.createElement(RgbaColorPicker, { color: colorToRgba(symbologyValue), onChange: handleColorOutputChange })),
|
|
42
47
|
React.createElement(Button, { id: `jp-gis-remove-color-${index}`, className: "jp-Button jp-gis-filter-icon" },
|
|
43
48
|
React.createElement(FontAwesomeIcon, { icon: faTrash, onClick: deleteRow }))));
|
|
44
49
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IJGISLayer, IJupyterGISModel, IVectorLayer, IWebGlLayer } from '@jupytergis/schema';
|
|
2
|
-
import {
|
|
2
|
+
import { IColorMap } from './colorRampUtils';
|
|
3
3
|
import { IStopRow } from './symbologyDialog';
|
|
4
4
|
/** Payload when saving symbology; shape matches vector or WebGl layer params. */
|
|
5
5
|
export interface ISymbologyPayload {
|
|
@@ -29,5 +29,5 @@ export declare namespace VectorUtils {
|
|
|
29
29
|
const buildRadiusInfo: (layer: IJGISLayer) => IStopRow[];
|
|
30
30
|
}
|
|
31
31
|
export declare namespace Utils {
|
|
32
|
-
const getValueColorPairs: (stops: number[],
|
|
32
|
+
const getValueColorPairs: (stops: number[], colorRamp: IColorMap, nClasses: number, reverse?: boolean) => IStopRow[];
|
|
33
33
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { UUID } from '@lumino/coreutils';
|
|
1
2
|
import colormap from 'colormap';
|
|
3
|
+
import { findExprNode } from './colorRampUtils';
|
|
2
4
|
const COLOR_EXPR_STOPS_START = 3;
|
|
3
5
|
/**
|
|
4
6
|
* Resolve the effective symbology params for this dialog: either the layer's
|
|
@@ -90,35 +92,38 @@ export var VectorUtils;
|
|
|
90
92
|
if (!color[key]) {
|
|
91
93
|
continue;
|
|
92
94
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
});
|
|
106
|
-
seenPairs.add(pairKey);
|
|
107
|
-
}
|
|
95
|
+
const interpolate = findExprNode(color[key], 'interpolate');
|
|
96
|
+
if (interpolate) {
|
|
97
|
+
// Graduated: value:color pairs starting at index 3
|
|
98
|
+
for (let i = COLOR_EXPR_STOPS_START; i < interpolate.length; i += 2) {
|
|
99
|
+
const pairKey = `${interpolate[i]}-${interpolate[i + 1]}`;
|
|
100
|
+
if (!seenPairs.has(pairKey)) {
|
|
101
|
+
valueColorPairs.push({
|
|
102
|
+
id: UUID.uuid4(),
|
|
103
|
+
stop: interpolate[i],
|
|
104
|
+
output: interpolate[i + 1],
|
|
105
|
+
});
|
|
106
|
+
seenPairs.add(pairKey);
|
|
108
107
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
const caseExpr = findExprNode(color[key], 'case');
|
|
112
|
+
if (caseExpr) {
|
|
113
|
+
// Categorized: alternating [condition, color] pairs, last element is fallback
|
|
114
|
+
for (let i = 1; i < caseExpr.length - 1; i += 2) {
|
|
115
|
+
const condition = caseExpr[i];
|
|
116
|
+
const pairKey = `${condition[2]}-${caseExpr[i + 1]}`;
|
|
113
117
|
if (!seenPairs.has(pairKey)) {
|
|
114
118
|
valueColorPairs.push({
|
|
115
|
-
|
|
116
|
-
|
|
119
|
+
id: UUID.uuid4(),
|
|
120
|
+
stop: condition[2],
|
|
121
|
+
output: caseExpr[i + 1],
|
|
117
122
|
});
|
|
118
123
|
seenPairs.add(pairKey);
|
|
119
124
|
}
|
|
120
125
|
}
|
|
121
|
-
|
|
126
|
+
}
|
|
122
127
|
}
|
|
123
128
|
}
|
|
124
129
|
return valueColorPairs;
|
|
@@ -141,6 +146,7 @@ export var VectorUtils;
|
|
|
141
146
|
}
|
|
142
147
|
for (let i = COLOR_EXPR_STOPS_START; i < circleRadius.length; i += 2) {
|
|
143
148
|
const obj = {
|
|
149
|
+
id: UUID.uuid4(),
|
|
144
150
|
stop: circleRadius[i],
|
|
145
151
|
output: circleRadius[i + 1],
|
|
146
152
|
};
|
|
@@ -151,29 +157,41 @@ export var VectorUtils;
|
|
|
151
157
|
})(VectorUtils || (VectorUtils = {}));
|
|
152
158
|
export var Utils;
|
|
153
159
|
(function (Utils) {
|
|
154
|
-
Utils.getValueColorPairs = (stops,
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
+
Utils.getValueColorPairs = (stops, colorRamp, nClasses, reverse = false) => {
|
|
161
|
+
const isCategorical = colorRamp.type === 'categorical';
|
|
162
|
+
let colorMap;
|
|
163
|
+
if (isCategorical) {
|
|
164
|
+
colorMap = [...colorRamp.colors];
|
|
165
|
+
if (colorMap.length < nClasses) {
|
|
166
|
+
colorMap = Array.from({ length: nClasses }, (_, i) => {
|
|
167
|
+
return colorMap[i % colorMap.length];
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
colorMap = colorMap.slice(0, nClasses);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
const nShades = Math.max(nClasses, 9);
|
|
176
|
+
colorMap = colormap({
|
|
177
|
+
colormap: colorRamp.name,
|
|
178
|
+
nshades: nShades,
|
|
179
|
+
format: 'rgba',
|
|
180
|
+
});
|
|
181
|
+
}
|
|
160
182
|
if (reverse) {
|
|
161
183
|
colorMap = [...colorMap].reverse();
|
|
162
184
|
}
|
|
163
185
|
const valueColorPairs = [];
|
|
164
|
-
// colormap requires 9 classes to generate the ramp
|
|
165
|
-
// so we do some tomfoolery to make it work with less than 9 stops
|
|
166
|
-
if (nClasses < 9) {
|
|
167
|
-
const midIndex = Math.floor(nClasses / 2);
|
|
168
|
-
// Get the first n/2 elements from the second array
|
|
169
|
-
const firstPart = colorMap.slice(0, midIndex);
|
|
170
|
-
// Get the last n/2 elements from the second array
|
|
171
|
-
const secondPart = colorMap.slice(colorMap.length - (stops.length - firstPart.length));
|
|
172
|
-
// Create the new array by combining the first and last parts
|
|
173
|
-
colorMap = firstPart.concat(secondPart);
|
|
174
|
-
}
|
|
175
186
|
for (let i = 0; i < nClasses; i++) {
|
|
176
|
-
|
|
187
|
+
const colorIndex = isCategorical
|
|
188
|
+
? i
|
|
189
|
+
: Math.round((i / (nClasses - 1)) * (colorMap.length - 1));
|
|
190
|
+
valueColorPairs.push({
|
|
191
|
+
id: UUID.uuid4(),
|
|
192
|
+
stop: stops[i],
|
|
193
|
+
output: colorMap[colorIndex],
|
|
194
|
+
});
|
|
177
195
|
}
|
|
178
196
|
return valueColorPairs;
|
|
179
197
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Button } from '@jupyterlab/ui-components';
|
|
2
|
+
import { UUID } from '@lumino/coreutils';
|
|
2
3
|
import React, { useEffect, useState } from 'react';
|
|
3
4
|
import { GeoTiffClassifications } from "../../classificationModes";
|
|
4
5
|
import ColorRampControls from "../../components/color_ramp/ColorRampControls";
|
|
@@ -10,6 +11,7 @@ import BandRow from "../components/BandRow";
|
|
|
10
11
|
import { LoadingOverlay } from "../../../../shared/components/loading";
|
|
11
12
|
import { useLatest } from "../../../../shared/hooks/useLatest";
|
|
12
13
|
import { GlobalStateDbManager } from "../../../../store";
|
|
14
|
+
import { getColorMapList } from '../../colorRampUtils';
|
|
13
15
|
import { useEffectiveSymbologyParams } from '../../hooks/useEffectiveSymbologyParams';
|
|
14
16
|
const SingleBandPseudoColor = ({ model, okSignalPromise, layerId, isStorySegmentOverride, segmentId, }) => {
|
|
15
17
|
if (!layerId) {
|
|
@@ -83,6 +85,7 @@ const SingleBandPseudoColor = ({ model, okSignalPromise, layerId, isStorySegment
|
|
|
83
85
|
// Sixth and on is value:color pairs
|
|
84
86
|
for (let i = 5; i < color.length; i += 2) {
|
|
85
87
|
const obj = {
|
|
88
|
+
id: UUID.uuid4(),
|
|
86
89
|
stop: scaleValue(Number(color[i]), isQuantile),
|
|
87
90
|
output: color[i + 1],
|
|
88
91
|
};
|
|
@@ -103,6 +106,7 @@ const SingleBandPseudoColor = ({ model, okSignalPromise, layerId, isStorySegment
|
|
|
103
106
|
? color[i][2]
|
|
104
107
|
: color[i]);
|
|
105
108
|
const obj = {
|
|
109
|
+
id: UUID.uuid4(),
|
|
106
110
|
stop: scaleValue(stopVal, isQuantile),
|
|
107
111
|
output: color[i + 1],
|
|
108
112
|
};
|
|
@@ -211,6 +215,7 @@ const SingleBandPseudoColor = ({ model, okSignalPromise, layerId, isStorySegment
|
|
|
211
215
|
const addStopRow = () => {
|
|
212
216
|
setStopRows([
|
|
213
217
|
{
|
|
218
|
+
id: UUID.uuid4(),
|
|
214
219
|
stop: 0,
|
|
215
220
|
output: [0, 0, 0, 1],
|
|
216
221
|
},
|
|
@@ -252,7 +257,11 @@ const SingleBandPseudoColor = ({ model, okSignalPromise, layerId, isStorySegment
|
|
|
252
257
|
return;
|
|
253
258
|
}
|
|
254
259
|
setIsLoading(false);
|
|
255
|
-
const
|
|
260
|
+
const colorRamp = getColorMapList().find(c => c.name === selectedRamp);
|
|
261
|
+
if (!colorRamp) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const valueColorPairs = Utils.getValueColorPairs(stops, colorRamp, nClasses, reverseRamp);
|
|
256
265
|
setStopRows(valueColorPairs);
|
|
257
266
|
};
|
|
258
267
|
const scaleValue = (bandValue, isQuantile) => {
|
|
@@ -265,6 +274,9 @@ const SingleBandPseudoColor = ({ model, okSignalPromise, layerId, isStorySegment
|
|
|
265
274
|
return (bandValue * (max - min)) / (1 - 0) + min;
|
|
266
275
|
};
|
|
267
276
|
const unscaleValue = (value, isQuantile) => {
|
|
277
|
+
if (typeof value !== 'number') {
|
|
278
|
+
throw new Error('unscaleValue expects a number');
|
|
279
|
+
}
|
|
268
280
|
const currentBand = bandRowsRef.current[selectedBand - 1];
|
|
269
281
|
const min = isQuantile ? 1 : currentBand.stats.minimum;
|
|
270
282
|
const max = isQuantile ? 65535 : currentBand.stats.maximum;
|
|
@@ -294,7 +306,7 @@ const SingleBandPseudoColor = ({ model, okSignalPromise, layerId, isStorySegment
|
|
|
294
306
|
? '='
|
|
295
307
|
: ''),
|
|
296
308
|
React.createElement("span", null, "Output Value")),
|
|
297
|
-
stopRows.map((stop, index) => (React.createElement(StopRow, { key:
|
|
309
|
+
stopRows.map((stop, index) => (React.createElement(StopRow, { key: stop.id, index: index, dataValue: stop.stop, symbologyValue: stop.output, stopRows: stopRows, setStopRows: setStopRows, deleteRow: () => deleteStopRow(index) })))),
|
|
298
310
|
React.createElement("div", { className: "jp-gis-symbology-button-container" },
|
|
299
311
|
React.createElement(Button, { className: "jp-Dialog-button jp-mod-accept jp-mod-styled", onClick: addStopRow }, "Add Stop"))));
|
|
300
312
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react';
|
|
2
2
|
import { useGetProperties } from "../hooks/useGetProperties";
|
|
3
|
+
import FilterComponent from "../../../panelview/filter-panel/Filter";
|
|
3
4
|
import { getColorCodeFeatureAttributes, getFeatureAttributes, getNumericFeatureAttributes, objectEntries, } from "../../../tools";
|
|
4
5
|
import Canonical from './types/Canonical';
|
|
5
6
|
import Categorized from './types/Categorized';
|
|
@@ -92,8 +93,8 @@ const VectorRendering = ({ model, okSignalPromise, layerId, isStorySegmentOverri
|
|
|
92
93
|
const selectableRenderTypes = getSelectableRenderTypes(featureProperties, layer.type);
|
|
93
94
|
const selectedRenderTypeProps = selectableRenderTypes[selectedRenderType];
|
|
94
95
|
return (React.createElement(React.Fragment, null,
|
|
95
|
-
selectedRenderTypeProps.isTabbed && (React.createElement("div", { className: "jp-gis-symbology-tabs" }, ['color', 'radius'].map(tab => (React.createElement("button", { key: tab, className: `jp-gis-tab ${symbologyTab === tab ? 'active' : ''}`, onClick: () => setSymbologyTab(tab) }, tab.charAt(0).toUpperCase() + tab.slice(1)))))),
|
|
96
|
-
React.createElement("div", { className: "jp-gis-symbology-row" },
|
|
96
|
+
selectedRenderTypeProps.isTabbed && (React.createElement("div", { className: "jp-gis-symbology-tabs" }, ['color', 'radius', 'filters'].map(tab => (React.createElement("button", { key: tab, className: `jp-gis-tab ${symbologyTab === tab ? 'active' : ''}`, onClick: () => setSymbologyTab(tab) }, tab.charAt(0).toUpperCase() + tab.slice(1)))))),
|
|
97
|
+
symbologyTab !== 'filters' && (React.createElement("div", { className: "jp-gis-symbology-row" },
|
|
97
98
|
React.createElement("label", { htmlFor: "render-type-select" }, "Render Type:"),
|
|
98
99
|
React.createElement("div", { className: "jp-select-wrapper" },
|
|
99
100
|
React.createElement("select", { name: "render-type-select", id: "render-type-select", className: "jp-mod-styled", value: selectedRenderType, onChange: event => {
|
|
@@ -101,11 +102,11 @@ const VectorRendering = ({ model, okSignalPromise, layerId, isStorySegmentOverri
|
|
|
101
102
|
} }, objectEntries(selectableRenderTypes)
|
|
102
103
|
.filter(([renderType, renderTypeProps]) => renderTypeProps.layerTypeSupported &&
|
|
103
104
|
!(renderType === 'Heatmap' && symbologyTab === 'radius'))
|
|
104
|
-
.map(([renderType, _]) => (React.createElement("option", { key: renderType, value: renderType }, renderType)))))),
|
|
105
|
-
React.createElement(selectedRenderTypeProps.component, Object.assign({ model: model, okSignalPromise: okSignalPromise, layerId: layerId, isStorySegmentOverride: isStorySegmentOverride, segmentId: segmentId }, (selectedRenderTypeProps.isTabbed ? { symbologyTab } : {}), (selectedRenderTypeProps.selectableAttributesAndValues
|
|
105
|
+
.map(([renderType, _]) => (React.createElement("option", { key: renderType, value: renderType }, renderType))))))),
|
|
106
|
+
symbologyTab === 'filters' ? (React.createElement(FilterComponent, { model: model, okSignalPromise: okSignalPromise })) : (React.createElement(selectedRenderTypeProps.component, Object.assign({ model: model, okSignalPromise: okSignalPromise, layerId: layerId, isStorySegmentOverride: isStorySegmentOverride, segmentId: segmentId }, (selectedRenderTypeProps.isTabbed ? { symbologyTab } : {}), (selectedRenderTypeProps.selectableAttributesAndValues
|
|
106
107
|
? {
|
|
107
108
|
selectableAttributesAndValues: selectedRenderTypeProps.selectableAttributesAndValues,
|
|
108
109
|
}
|
|
109
|
-
: {})))));
|
|
110
|
+
: {}))))));
|
|
110
111
|
};
|
|
111
112
|
export default VectorRendering;
|
|
@@ -2,6 +2,8 @@ import React from 'react';
|
|
|
2
2
|
const ValueSelect = ({ featureProperties, selectedValue, setSelectedValue, }) => {
|
|
3
3
|
return (React.createElement("div", { className: "jp-gis-symbology-row" },
|
|
4
4
|
React.createElement("label", { htmlFor: 'vector-value-select' }, "Value:"),
|
|
5
|
-
React.createElement("select", { name: 'vector-value-select', onChange: event => setSelectedValue(event.target.value), className: "jp-mod-styled" }, Object.keys(featureProperties)
|
|
5
|
+
React.createElement("select", { name: 'vector-value-select', onChange: event => setSelectedValue(event.target.value), className: "jp-mod-styled" }, Object.keys(featureProperties)
|
|
6
|
+
.sort((a, b) => a.localeCompare(b))
|
|
7
|
+
.map((feature, index) => (React.createElement("option", { key: index, value: feature, selected: feature === selectedValue, className: "jp-mod-styled" }, feature))))));
|
|
6
8
|
};
|
|
7
9
|
export default ValueSelect;
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { colorToRgba, DEFAULT_COLOR, DEFAULT_STROKE_WIDTH, isColor, } from "../../colorRampUtils";
|
|
3
|
+
import RgbaColorPicker from "../../components/color_ramp/RgbaColorPicker";
|
|
2
4
|
import { useOkSignal } from "../../hooks/useOkSignal";
|
|
3
5
|
import { saveSymbology } from "../../symbologyUtils";
|
|
4
6
|
import ValueSelect from "../components/ValueSelect";
|
|
5
7
|
import { useLatest } from "../../../../shared/hooks/useLatest";
|
|
8
|
+
const TRANSPARENT = [0, 0, 0, 0];
|
|
6
9
|
const Canonical = ({ model, okSignalPromise, layerId, selectableAttributesAndValues, isStorySegmentOverride, segmentId, }) => {
|
|
7
10
|
const [selectedValue, setSelectedValue] = useState('');
|
|
11
|
+
const [fallbackColor, setFallbackColor] = useState(TRANSPARENT);
|
|
12
|
+
const [strokeFollowsFill, setStrokeFollowsFill] = useState(true);
|
|
13
|
+
const [strokeColor, setStrokeColor] = useState(DEFAULT_COLOR);
|
|
14
|
+
const [strokeWidth, setStrokeWidth] = useState(String(DEFAULT_STROKE_WIDTH));
|
|
8
15
|
const selectedValueRef = useLatest(selectedValue);
|
|
16
|
+
const fallbackColorRef = useLatest(fallbackColor);
|
|
17
|
+
const strokeFollowsFillRef = useLatest(strokeFollowsFill);
|
|
18
|
+
const strokeColorRef = useLatest(strokeColor);
|
|
19
|
+
const strokeWidthRef = useLatest(strokeWidth);
|
|
9
20
|
if (!layerId) {
|
|
10
21
|
return;
|
|
11
22
|
}
|
|
@@ -14,23 +25,49 @@ const Canonical = ({ model, okSignalPromise, layerId, selectableAttributesAndVal
|
|
|
14
25
|
return;
|
|
15
26
|
}
|
|
16
27
|
useEffect(() => {
|
|
17
|
-
var _a, _b;
|
|
28
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
18
29
|
const layerParams = layer.parameters;
|
|
19
|
-
const
|
|
30
|
+
const savedValue = (_a = layerParams.symbologyState) === null || _a === void 0 ? void 0 : _a.value;
|
|
31
|
+
const value = savedValue && savedValue in selectableAttributesAndValues
|
|
32
|
+
? savedValue
|
|
33
|
+
: Object.keys(selectableAttributesAndValues)[0];
|
|
20
34
|
setSelectedValue(value);
|
|
35
|
+
setFallbackColor(colorToRgba((_c = (_b = layerParams.symbologyState) === null || _b === void 0 ? void 0 : _b.fallbackColor) !== null && _c !== void 0 ? _c : TRANSPARENT));
|
|
36
|
+
setStrokeFollowsFill((_e = (_d = layerParams.symbologyState) === null || _d === void 0 ? void 0 : _d.strokeFollowsFill) !== null && _e !== void 0 ? _e : true);
|
|
37
|
+
const savedStroke = (_f = layerParams.color) === null || _f === void 0 ? void 0 : _f['stroke-color'];
|
|
38
|
+
setStrokeColor(isColor(savedStroke) ? colorToRgba(savedStroke) : DEFAULT_COLOR);
|
|
39
|
+
setStrokeWidth(String((_h = (_g = layerParams.color) === null || _g === void 0 ? void 0 : _g['stroke-width']) !== null && _h !== void 0 ? _h : DEFAULT_STROKE_WIDTH));
|
|
21
40
|
}, [selectableAttributesAndValues]);
|
|
22
41
|
const handleOk = () => {
|
|
23
42
|
if (!layer.parameters) {
|
|
24
43
|
return;
|
|
25
44
|
}
|
|
26
|
-
|
|
45
|
+
// Use coalesce so that features missing the color property (e.g. boundary
|
|
46
|
+
// or line features in a multi-layer MVT) fall back to the user-chosen color
|
|
47
|
+
// instead of returning undefined, which would cause OL to throw at render time.
|
|
48
|
+
const colorExpr = [
|
|
49
|
+
'coalesce',
|
|
50
|
+
['get', selectedValueRef.current],
|
|
51
|
+
fallbackColorRef.current,
|
|
52
|
+
];
|
|
27
53
|
const newStyle = Object.assign({}, layer.parameters.color);
|
|
28
54
|
newStyle['fill-color'] = colorExpr;
|
|
29
|
-
newStyle['stroke-color'] = colorExpr;
|
|
30
55
|
newStyle['circle-fill-color'] = colorExpr;
|
|
56
|
+
if (strokeFollowsFillRef.current) {
|
|
57
|
+
newStyle['stroke-color'] = colorExpr;
|
|
58
|
+
newStyle['circle-stroke-color'] = colorExpr;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
newStyle['stroke-color'] = strokeColorRef.current;
|
|
62
|
+
newStyle['circle-stroke-color'] = strokeColorRef.current;
|
|
63
|
+
newStyle['stroke-width'] = Math.max(0, parseFloat(strokeWidthRef.current));
|
|
64
|
+
newStyle['circle-stroke-width'] = Math.max(0, parseFloat(strokeWidthRef.current));
|
|
65
|
+
}
|
|
31
66
|
const symbologyState = {
|
|
32
67
|
renderType: 'Canonical',
|
|
33
68
|
value: selectedValueRef.current,
|
|
69
|
+
fallbackColor: fallbackColorRef.current,
|
|
70
|
+
strokeFollowsFill: strokeFollowsFillRef.current,
|
|
34
71
|
};
|
|
35
72
|
saveSymbology({
|
|
36
73
|
model,
|
|
@@ -60,6 +97,34 @@ const Canonical = ({ model, okSignalPromise, layerId, selectableAttributesAndVal
|
|
|
60
97
|
})();
|
|
61
98
|
return (React.createElement("div", { className: "jp-gis-layer-symbology-container" },
|
|
62
99
|
React.createElement("p", null, "Color features based on an attribute containing a hex color code."),
|
|
63
|
-
body
|
|
100
|
+
body,
|
|
101
|
+
React.createElement("div", { className: "jp-gis-symbology-row" },
|
|
102
|
+
React.createElement("label", null, "Stroke Color:"),
|
|
103
|
+
React.createElement("div", { style: {
|
|
104
|
+
display: 'flex',
|
|
105
|
+
alignItems: 'center',
|
|
106
|
+
gap: 8,
|
|
107
|
+
flex: '1 0 50%',
|
|
108
|
+
maxWidth: '50%',
|
|
109
|
+
} },
|
|
110
|
+
React.createElement("div", { style: {
|
|
111
|
+
opacity: strokeFollowsFill ? 0.3 : 1,
|
|
112
|
+
pointerEvents: strokeFollowsFill ? 'none' : 'auto',
|
|
113
|
+
} },
|
|
114
|
+
React.createElement(RgbaColorPicker, { color: strokeColor, onChange: setStrokeColor })),
|
|
115
|
+
React.createElement("label", { style: {
|
|
116
|
+
display: 'flex',
|
|
117
|
+
alignItems: 'center',
|
|
118
|
+
gap: 4,
|
|
119
|
+
whiteSpace: 'nowrap',
|
|
120
|
+
} },
|
|
121
|
+
React.createElement("input", { type: "checkbox", checked: strokeFollowsFill, onChange: e => setStrokeFollowsFill(e.target.checked) }),
|
|
122
|
+
"match fill"))),
|
|
123
|
+
React.createElement("div", { className: "jp-gis-symbology-row" },
|
|
124
|
+
React.createElement("label", null, "Stroke Width:"),
|
|
125
|
+
React.createElement("input", { type: "text", className: "jp-mod-styled", value: strokeWidth, onChange: e => setStrokeWidth(e.target.value) })),
|
|
126
|
+
React.createElement("div", { className: "jp-gis-symbology-row" },
|
|
127
|
+
React.createElement("label", null, "Fallback Color:"),
|
|
128
|
+
React.createElement(RgbaColorPicker, { color: fallbackColor, onChange: setFallbackColor }))));
|
|
64
129
|
};
|
|
65
130
|
export default Canonical;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { colorToRgba, DEFAULT_COLOR, DEFAULT_STROKE_WIDTH, getColorMapList, isColor, } from "../../colorRampUtils";
|
|
2
3
|
import ColorRampControls from "../../components/color_ramp/ColorRampControls";
|
|
4
|
+
import RgbaColorPicker from "../../components/color_ramp/RgbaColorPicker";
|
|
3
5
|
import StopContainer from "../../components/color_stops/StopContainer";
|
|
4
6
|
import { useOkSignal } from "../../hooks/useOkSignal";
|
|
5
7
|
import { Utils, VectorUtils, saveSymbology, } from "../../symbologyUtils";
|
|
@@ -10,10 +12,14 @@ const Categorized = ({ model, okSignalPromise, layerId, symbologyTab, selectable
|
|
|
10
12
|
const [selectedAttribute, setSelectedAttribute] = useState('');
|
|
11
13
|
const [stopRows, setStopRows] = useState([]);
|
|
12
14
|
const [colorRampOptions, setColorRampOptions] = useState();
|
|
15
|
+
const [fallbackColor, setFallbackColor] = useState([0, 0, 0, 0]);
|
|
16
|
+
const [strokeFollowsFill, setStrokeFollowsFill] = useState(false);
|
|
17
|
+
const fallbackColorRef = useLatest(fallbackColor);
|
|
18
|
+
const strokeFollowsFillRef = useLatest(strokeFollowsFill);
|
|
13
19
|
const [manualStyle, setManualStyle] = useState({
|
|
14
|
-
fillColor:
|
|
15
|
-
strokeColor:
|
|
16
|
-
strokeWidth:
|
|
20
|
+
fillColor: DEFAULT_COLOR,
|
|
21
|
+
strokeColor: DEFAULT_COLOR,
|
|
22
|
+
strokeWidth: String(DEFAULT_STROKE_WIDTH),
|
|
17
23
|
radius: 5,
|
|
18
24
|
});
|
|
19
25
|
const manualStyleRef = useLatest(manualStyle);
|
|
@@ -39,35 +45,40 @@ const Categorized = ({ model, okSignalPromise, layerId, symbologyTab, selectable
|
|
|
39
45
|
setStopRows(valueColorPairs);
|
|
40
46
|
}, []);
|
|
41
47
|
useEffect(() => {
|
|
48
|
+
var _a, _b, _c, _d;
|
|
42
49
|
if (params.color) {
|
|
43
50
|
const fillColor = params.color['fill-color'];
|
|
44
51
|
const circleFillColor = params.color['circle-fill-color'];
|
|
45
52
|
const strokeColor = params.color['stroke-color'];
|
|
46
53
|
const circleStrokeColor = params.color['circle-stroke-color'];
|
|
47
|
-
const
|
|
54
|
+
const effectiveFill = isColor(fillColor)
|
|
55
|
+
? fillColor
|
|
56
|
+
: isColor(circleFillColor)
|
|
57
|
+
? circleFillColor
|
|
58
|
+
: DEFAULT_COLOR;
|
|
59
|
+
const effectiveStroke = isColor(strokeColor)
|
|
60
|
+
? strokeColor
|
|
61
|
+
: isColor(circleStrokeColor)
|
|
62
|
+
? circleStrokeColor
|
|
63
|
+
: DEFAULT_COLOR;
|
|
48
64
|
setManualStyle({
|
|
49
|
-
fillColor:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
? circleFillColor
|
|
53
|
-
: '#3399CC',
|
|
54
|
-
strokeColor: isSimpleColor(strokeColor)
|
|
55
|
-
? strokeColor
|
|
56
|
-
: isSimpleColor(circleStrokeColor)
|
|
57
|
-
? circleStrokeColor
|
|
58
|
-
: '#3399CC',
|
|
59
|
-
strokeWidth: params.color['stroke-width'] ||
|
|
65
|
+
fillColor: colorToRgba(effectiveFill),
|
|
66
|
+
strokeColor: colorToRgba(effectiveStroke),
|
|
67
|
+
strokeWidth: String(params.color['stroke-width'] ||
|
|
60
68
|
params.color['circle-stroke-width'] ||
|
|
61
|
-
|
|
69
|
+
DEFAULT_STROKE_WIDTH),
|
|
62
70
|
radius: params.color['circle-radius'] || 5,
|
|
63
71
|
});
|
|
64
72
|
}
|
|
73
|
+
setFallbackColor(colorToRgba((_b = (_a = params.symbologyState) === null || _a === void 0 ? void 0 : _a.fallbackColor) !== null && _b !== void 0 ? _b : [0, 0, 0, 0]));
|
|
74
|
+
setStrokeFollowsFill((_d = (_c = params.symbologyState) === null || _c === void 0 ? void 0 : _c.strokeFollowsFill) !== null && _d !== void 0 ? _d : false);
|
|
65
75
|
}, [layerId]);
|
|
66
76
|
useEffect(() => {
|
|
67
77
|
var _a;
|
|
68
|
-
|
|
69
|
-
const attribute =
|
|
70
|
-
|
|
78
|
+
const savedValue = (_a = params.symbologyState) === null || _a === void 0 ? void 0 : _a.value;
|
|
79
|
+
const attribute = savedValue && savedValue in selectableAttributesAndValues
|
|
80
|
+
? savedValue
|
|
81
|
+
: Object.keys(selectableAttributesAndValues)[0];
|
|
71
82
|
setSelectedAttribute(attribute);
|
|
72
83
|
}, [selectableAttributesAndValues]);
|
|
73
84
|
const buildColorInfoFromClassification = (selectedMode, numberOfShades, selectedRamp, reverseRamp, setIsLoading) => {
|
|
@@ -78,8 +89,15 @@ const Categorized = ({ model, okSignalPromise, layerId, symbologyTab, selectable
|
|
|
78
89
|
selectedMode,
|
|
79
90
|
reverseRamp,
|
|
80
91
|
});
|
|
92
|
+
if (!selectableAttributesAndValues[selectedAttribute]) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
81
95
|
const stops = Array.from(selectableAttributesAndValues[selectedAttribute]).sort((a, b) => a - b);
|
|
82
|
-
const
|
|
96
|
+
const colorRamp = getColorMapList().find(c => c.name === selectedRamp);
|
|
97
|
+
if (!colorRamp) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const valueColorPairs = Utils.getValueColorPairs(stops, colorRamp, stops.length, reverseRamp);
|
|
83
101
|
setStopRows(valueColorPairs);
|
|
84
102
|
};
|
|
85
103
|
const handleOk = () => {
|
|
@@ -93,27 +111,36 @@ const Categorized = ({ model, okSignalPromise, layerId, symbologyTab, selectable
|
|
|
93
111
|
expr.push(stop.output);
|
|
94
112
|
});
|
|
95
113
|
if (symbologyTab === 'color') {
|
|
96
|
-
expr.push(
|
|
114
|
+
expr.push(fallbackColorRef.current);
|
|
97
115
|
newStyle['fill-color'] = expr;
|
|
98
116
|
newStyle['circle-fill-color'] = expr;
|
|
99
|
-
|
|
100
|
-
|
|
117
|
+
if (strokeFollowsFillRef.current) {
|
|
118
|
+
newStyle['stroke-color'] = expr;
|
|
119
|
+
newStyle['circle-stroke-color'] = expr;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
newStyle['stroke-color'] = manualStyleRef.current.strokeColor;
|
|
123
|
+
newStyle['circle-stroke-color'] = manualStyleRef.current.strokeColor;
|
|
124
|
+
}
|
|
101
125
|
}
|
|
102
126
|
}
|
|
103
127
|
else {
|
|
104
128
|
newStyle['fill-color'] = manualStyleRef.current.fillColor;
|
|
105
129
|
newStyle['circle-fill-color'] = manualStyleRef.current.fillColor;
|
|
130
|
+
newStyle['stroke-color'] = manualStyleRef.current.strokeColor;
|
|
131
|
+
newStyle['circle-stroke-color'] = manualStyleRef.current.strokeColor;
|
|
106
132
|
}
|
|
107
|
-
newStyle['stroke-width'] = manualStyleRef.current.strokeWidth;
|
|
108
|
-
newStyle['circle-stroke-width'] = manualStyleRef.current.strokeWidth;
|
|
133
|
+
newStyle['stroke-width'] = Math.max(0, parseFloat(manualStyleRef.current.strokeWidth));
|
|
134
|
+
newStyle['circle-stroke-width'] = Math.max(0, parseFloat(manualStyleRef.current.strokeWidth));
|
|
109
135
|
newStyle['circle-radius'] = manualStyleRef.current.radius;
|
|
110
|
-
newStyle['circle-stroke-color'] = manualStyleRef.current.strokeColor;
|
|
111
136
|
const symbologyState = {
|
|
112
137
|
renderType: 'Categorized',
|
|
113
138
|
value: selectedAttributeRef.current,
|
|
114
139
|
colorRamp: (_a = colorRampOptionsRef.current) === null || _a === void 0 ? void 0 : _a.selectedRamp,
|
|
115
140
|
method: symbologyTab,
|
|
116
141
|
reverseRamp: (_b = colorRampOptionsRef.current) === null || _b === void 0 ? void 0 : _b.reverseRamp,
|
|
142
|
+
fallbackColor: fallbackColorRef.current,
|
|
143
|
+
strokeFollowsFill: strokeFollowsFillRef.current,
|
|
117
144
|
};
|
|
118
145
|
saveSymbology({
|
|
119
146
|
model,
|
|
@@ -165,20 +192,40 @@ const Categorized = ({ model, okSignalPromise, layerId, symbologyTab, selectable
|
|
|
165
192
|
symbologyTab === 'color' && (React.createElement(React.Fragment, null,
|
|
166
193
|
React.createElement("div", { className: "jp-gis-symbology-row" },
|
|
167
194
|
React.createElement("label", null, "Fill Color:"),
|
|
168
|
-
React.createElement(
|
|
195
|
+
React.createElement(RgbaColorPicker, { color: manualStyle.fillColor, onChange: color => {
|
|
169
196
|
handleReset('color');
|
|
170
|
-
setManualStyle(prev => (Object.assign(Object.assign({}, prev), { fillColor:
|
|
197
|
+
setManualStyle(prev => (Object.assign(Object.assign({}, prev), { fillColor: color })));
|
|
171
198
|
} })),
|
|
172
199
|
React.createElement("div", { className: "jp-gis-symbology-row" },
|
|
173
200
|
React.createElement("label", null, "Stroke Color:"),
|
|
174
|
-
React.createElement("
|
|
175
|
-
|
|
176
|
-
|
|
201
|
+
React.createElement("div", { style: {
|
|
202
|
+
display: 'flex',
|
|
203
|
+
alignItems: 'center',
|
|
204
|
+
gap: 8,
|
|
205
|
+
flex: '1 0 50%',
|
|
206
|
+
maxWidth: '50%',
|
|
207
|
+
} },
|
|
208
|
+
React.createElement("div", { style: {
|
|
209
|
+
opacity: strokeFollowsFill ? 0.3 : 1,
|
|
210
|
+
pointerEvents: strokeFollowsFill ? 'none' : 'auto',
|
|
211
|
+
} },
|
|
212
|
+
React.createElement(RgbaColorPicker, { color: manualStyle.strokeColor, onChange: color => setManualStyle(prev => (Object.assign(Object.assign({}, prev), { strokeColor: color }))) })),
|
|
213
|
+
React.createElement("label", { style: {
|
|
214
|
+
display: 'flex',
|
|
215
|
+
alignItems: 'center',
|
|
216
|
+
gap: 4,
|
|
217
|
+
whiteSpace: 'nowrap',
|
|
218
|
+
} },
|
|
219
|
+
React.createElement("input", { type: "checkbox", checked: strokeFollowsFill, onChange: e => setStrokeFollowsFill(e.target.checked) }),
|
|
220
|
+
"match fill"))),
|
|
177
221
|
React.createElement("div", { className: "jp-gis-symbology-row" },
|
|
178
222
|
React.createElement("label", null, "Stroke Width:"),
|
|
179
|
-
React.createElement("input", { type: "
|
|
180
|
-
setManualStyle(prev => (Object.assign(Object.assign({}, prev), { strokeWidth:
|
|
181
|
-
} }))
|
|
223
|
+
React.createElement("input", { type: "text", className: "jp-mod-styled", value: manualStyle.strokeWidth, onChange: e => {
|
|
224
|
+
setManualStyle(prev => (Object.assign(Object.assign({}, prev), { strokeWidth: e.target.value })));
|
|
225
|
+
} })),
|
|
226
|
+
React.createElement("div", { className: "jp-gis-symbology-row" },
|
|
227
|
+
React.createElement("label", null, "Fallback Color:"),
|
|
228
|
+
React.createElement(RgbaColorPicker, { color: fallbackColor, onChange: setFallbackColor })))),
|
|
182
229
|
symbologyTab === 'radius' && (React.createElement("div", { className: "jp-gis-symbology-row" },
|
|
183
230
|
React.createElement("label", null, "Circle Radius:"),
|
|
184
231
|
React.createElement("input", { type: "number", className: "jp-mod-styled", value: manualStyle.radius, onChange: e => {
|