@jupytergis/base 0.9.2 → 0.10.1

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.
Files changed (69) hide show
  1. package/lib/annotations/components/Annotation.js +1 -1
  2. package/lib/commands/BaseCommandIDs.d.ts +1 -0
  3. package/lib/commands/BaseCommandIDs.js +2 -0
  4. package/lib/commands/index.js +31 -73
  5. package/lib/constants.js +2 -1
  6. package/lib/dialogs/ProcessingFormDialog.js +2 -2
  7. package/lib/dialogs/symbology/colorRampUtils.d.ts +2 -1
  8. package/lib/dialogs/symbology/colorRampUtils.js +10 -4
  9. package/lib/dialogs/symbology/components/color_ramp/ColorRampControls.d.ts +36 -0
  10. package/lib/dialogs/symbology/components/color_ramp/ColorRampControls.js +82 -0
  11. package/lib/dialogs/symbology/components/color_ramp/ColorRampSelector.d.ts +20 -0
  12. package/lib/dialogs/symbology/components/color_ramp/{CanvasSelectComponent.js → ColorRampSelector.js} +25 -7
  13. package/lib/dialogs/symbology/components/color_ramp/ColorRampSelectorEntry.d.ts +20 -0
  14. package/lib/dialogs/symbology/components/color_ramp/{ColorRampEntry.js → ColorRampSelectorEntry.js} +17 -3
  15. package/lib/dialogs/symbology/components/color_ramp/ModeSelectRow.d.ts +6 -5
  16. package/lib/dialogs/symbology/components/color_ramp/ModeSelectRow.js +7 -2
  17. package/lib/dialogs/symbology/hooks/useGetProperties.js +12 -6
  18. package/lib/dialogs/symbology/symbologyUtils.d.ts +2 -1
  19. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +8 -4
  20. package/lib/dialogs/symbology/vector_layer/VectorRendering.js +2 -2
  21. package/lib/dialogs/symbology/vector_layer/types/Categorized.js +4 -5
  22. package/lib/dialogs/symbology/vector_layer/types/Graduated.js +16 -28
  23. package/lib/dialogs/symbology/vector_layer/types/Heatmap.js +3 -3
  24. package/lib/formbuilder/editform.js +4 -3
  25. package/lib/formbuilder/objectform/layer/heatmapLayerForm.d.ts +2 -1
  26. package/lib/formbuilder/objectform/layer/heatmapLayerForm.js +4 -0
  27. package/lib/formbuilder/objectform/layer/vectorlayerform.d.ts +2 -1
  28. package/lib/formbuilder/objectform/layer/vectorlayerform.js +4 -0
  29. package/lib/formbuilder/objectform/layer/webGlLayerForm.d.ts +2 -0
  30. package/lib/formbuilder/objectform/layer/webGlLayerForm.js +4 -0
  31. package/lib/icons.d.ts +1 -0
  32. package/lib/icons.js +5 -0
  33. package/lib/index.d.ts +0 -1
  34. package/lib/index.js +0 -1
  35. package/lib/mainview/mainView.d.ts +1 -0
  36. package/lib/mainview/mainView.js +62 -12
  37. package/lib/panelview/annotationPanel.js +1 -1
  38. package/lib/panelview/components/filter-panel/Filter.js +1 -1
  39. package/lib/panelview/components/layers.js +152 -51
  40. package/lib/panelview/components/legendItem.js +36 -1
  41. package/lib/panelview/leftpanel.d.ts +0 -1
  42. package/lib/panelview/leftpanel.js +4 -4
  43. package/lib/panelview/rightpanel.js +4 -4
  44. package/lib/processing/processingFormToParam.js +3 -0
  45. package/lib/shared/components/ToggleGroup.d.ts +2 -2
  46. package/lib/stacBrowser/hooks/useStacSearch.js +2 -2
  47. package/lib/toolbar/widget.js +19 -28
  48. package/lib/tools.d.ts +1 -0
  49. package/lib/tools.js +2 -2
  50. package/lib/types.d.ts +2 -0
  51. package/lib/types.js +8 -0
  52. package/package.json +2 -2
  53. package/style/base.css +2 -0
  54. package/style/icons/book_open.svg +1 -1
  55. package/style/icons/clock-solid.svg +1 -1
  56. package/style/icons/geolocation.svg +1 -1
  57. package/style/icons/info-solid.svg +1 -1
  58. package/style/icons/logo_mini.svg +1 -1
  59. package/style/icons/marker.svg +5 -0
  60. package/style/icons/target_without_center.svg +1 -1
  61. package/style/icons/vector_square.svg +1 -1
  62. package/style/shared/tabs.css +1 -0
  63. package/style/symbologyDialog.css +12 -4
  64. package/lib/classificationModes.d.ts +0 -13
  65. package/lib/classificationModes.js +0 -326
  66. package/lib/dialogs/symbology/components/color_ramp/CanvasSelectComponent.d.ts +0 -11
  67. package/lib/dialogs/symbology/components/color_ramp/ColorRamp.d.ts +0 -16
  68. package/lib/dialogs/symbology/components/color_ramp/ColorRamp.js +0 -34
  69. package/lib/dialogs/symbology/components/color_ramp/ColorRampEntry.d.ts +0 -9
@@ -38,7 +38,7 @@ const Annotation = ({ itemId, annotationModel, jgisModel, children, }) => {
38
38
  return (React.createElement(Message, { user: content.user, message: content.value, self: ((_a = annotationModel.user) === null || _a === void 0 ? void 0 : _a.username) === ((_b = content.user) === null || _b === void 0 ? void 0 : _b.username) }));
39
39
  })),
40
40
  React.createElement("div", { className: "jGIS-Annotation-Message" },
41
- React.createElement("textarea", { rows: 3, placeholder: 'Ctrl+Enter to submit', value: messageContent, onChange: e => setMessageContent(e.currentTarget.value), onKeyDown: e => {
41
+ React.createElement("textarea", { "data-id": "annotation-textarea", rows: 3, placeholder: 'Ctrl+Enter to submit', value: messageContent, onChange: e => setMessageContent(e.currentTarget.value), onKeyDown: e => {
42
42
  if (e.ctrlKey && e.key === 'Enter') {
43
43
  handleSubmit();
44
44
  }
@@ -4,6 +4,7 @@ export declare const undo = "jupytergis:undo";
4
4
  export declare const symbology = "jupytergis:symbology";
5
5
  export declare const identify = "jupytergis:identify";
6
6
  export declare const temporalController = "jupytergis:temporalController";
7
+ export declare const addMarker = "jupytergis:addMarker";
7
8
  export declare const getGeolocation = "jupytergis:getGeolocation";
8
9
  export declare const openLayerBrowser = "jupytergis:openLayerBrowser";
9
10
  export declare const newRasterEntry = "jupytergis:newRasterEntry";
@@ -2,12 +2,14 @@
2
2
  *
3
3
  * See the documentation for more details.
4
4
  */
5
+ // Toolbar
5
6
  export const createNew = 'jupytergis:create-new-jGIS-file';
6
7
  export const redo = 'jupytergis:redo';
7
8
  export const undo = 'jupytergis:undo';
8
9
  export const symbology = 'jupytergis:symbology';
9
10
  export const identify = 'jupytergis:identify';
10
11
  export const temporalController = 'jupytergis:temporalController';
12
+ export const addMarker = 'jupytergis:addMarker';
11
13
  // geolocation
12
14
  export const getGeolocation = 'jupytergis:getGeolocation';
13
15
  // Layers and sources creation commands
@@ -11,6 +11,7 @@ import { getSingleSelectedLayer } from '../processing/index';
11
11
  import { addProcessingCommands } from '../processing/processingCommands';
12
12
  import { getGeoJSONDataFromLayerSource, downloadFile } from '../tools';
13
13
  import { JupyterGISDocumentWidget } from '../widget';
14
+ const POINT_SELECTION_TOOL_CLASS = 'jGIS-point-selection-tool';
14
15
  function loadKeybindings(commands, keybindings) {
15
16
  keybindings.forEach(binding => {
16
17
  commands.addKeyBinding({
@@ -89,7 +90,7 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
89
90
  ].includes(selectedLayer.type);
90
91
  if (current.model.currentMode === 'identifying' && !canIdentify) {
91
92
  current.model.currentMode = 'panning';
92
- current.node.classList.remove('jGIS-identify-tool');
93
+ current.node.classList.remove(POINT_SELECTION_TOOL_CLASS);
93
94
  return false;
94
95
  }
95
96
  return current.model.currentMode === 'identifying';
@@ -118,13 +119,13 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
118
119
  const keysPressed = luminoEvent.keys;
119
120
  if (keysPressed === null || keysPressed === void 0 ? void 0 : keysPressed.includes('Escape')) {
120
121
  current.model.currentMode = 'panning';
121
- current.node.classList.remove('jGIS-identify-tool');
122
+ current.node.classList.remove(POINT_SELECTION_TOOL_CLASS);
122
123
  commands.notifyCommandChanged(CommandIDs.identify);
123
124
  return;
124
125
  }
125
126
  }
126
- current.node.classList.toggle('jGIS-identify-tool');
127
- current.model.toggleIdentify();
127
+ current.node.classList.toggle(POINT_SELECTION_TOOL_CLASS);
128
+ current.model.toggleMode('identifying');
128
129
  commands.notifyCommandChanged(CommandIDs.identify);
129
130
  } }, icons.get(CommandIDs.identify)));
130
131
  commands.addCommand(CommandIDs.temporalController, Object.assign({ label: trans.__('Temporal Controller'), isToggled: () => {
@@ -344,13 +345,7 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
344
345
  execute: async () => {
345
346
  var _a;
346
347
  const model = (_a = tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model;
347
- await Private.renameSelectedItem(model, 'layer', (layerId, newName) => {
348
- const layer = model === null || model === void 0 ? void 0 : model.getLayer(layerId);
349
- if (layer) {
350
- layer.name = newName;
351
- model === null || model === void 0 ? void 0 : model.sharedModel.updateLayer(layerId, layer);
352
- }
353
- });
348
+ await Private.renameSelectedItem(model, 'layer');
354
349
  },
355
350
  });
356
351
  commands.addCommand(CommandIDs.removeLayer, {
@@ -368,9 +363,7 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
368
363
  execute: async () => {
369
364
  var _a;
370
365
  const model = (_a = tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model;
371
- await Private.renameSelectedItem(model, 'group', (groupName, newName) => {
372
- model === null || model === void 0 ? void 0 : model.renameLayerGroup(groupName, newName);
373
- });
366
+ await Private.renameSelectedItem(model, 'group');
374
367
  },
375
368
  });
376
369
  commands.addCommand(CommandIDs.removeGroup, {
@@ -458,13 +451,7 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
458
451
  execute: async () => {
459
452
  var _a;
460
453
  const model = (_a = tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model;
461
- await Private.renameSelectedItem(model, 'source', (sourceId, newName) => {
462
- const source = model === null || model === void 0 ? void 0 : model.getSource(sourceId);
463
- if (source) {
464
- source.name = newName;
465
- model === null || model === void 0 ? void 0 : model.sharedModel.updateSource(sourceId, source);
466
- }
467
- });
454
+ await Private.renameSelectedItem(model, 'source');
468
455
  },
469
456
  });
470
457
  commands.addCommand(CommandIDs.removeSource, {
@@ -568,7 +555,6 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
568
555
  if (!currentWidget || !completionProviderManager) {
569
556
  return;
570
557
  }
571
- console.log('zooming');
572
558
  const model = tracker.currentWidget.model;
573
559
  const selectedItems = (_a = model.localState) === null || _a === void 0 ? void 0 : _a.selected.value;
574
560
  if (!selectedItems) {
@@ -814,6 +800,24 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
814
800
  commands.notifyCommandChanged(CommandIDs.showIdentifyPanelTab);
815
801
  },
816
802
  });
803
+ commands.addCommand(CommandIDs.addMarker, Object.assign({ label: trans.__('Add Marker'), isToggled: () => {
804
+ const current = tracker.currentWidget;
805
+ if (!current) {
806
+ return false;
807
+ }
808
+ return current.model.currentMode === 'marking';
809
+ }, isEnabled: () => {
810
+ // TODO should check if at least one layer exists?
811
+ return true;
812
+ }, execute: args => {
813
+ const current = tracker.currentWidget;
814
+ if (!current) {
815
+ return;
816
+ }
817
+ current.node.classList.toggle(POINT_SELECTION_TOOL_CLASS);
818
+ current.model.toggleMode('marking');
819
+ commands.notifyCommandChanged(CommandIDs.addMarker);
820
+ } }, icons.get(CommandIDs.addMarker)));
817
821
  loadKeybindings(commands, keybindings);
818
822
  }
819
823
  var Private;
@@ -868,39 +872,11 @@ var Private;
868
872
  };
869
873
  }
870
874
  Private.createEntry = createEntry;
871
- async function getUserInputForRename(text, input, original) {
872
- const parent = text.parentElement;
873
- parent.replaceChild(input, text);
874
- input.value = original;
875
- input.select();
876
- input.focus();
877
- return new Promise(resolve => {
878
- input.addEventListener('blur', () => {
879
- parent.replaceChild(text, input);
880
- resolve(input.value);
881
- });
882
- input.addEventListener('keydown', (event) => {
883
- if (event.key === 'Enter') {
884
- event.stopPropagation();
885
- event.preventDefault();
886
- input.blur();
887
- }
888
- else if (event.key === 'Escape') {
889
- event.stopPropagation();
890
- event.preventDefault();
891
- input.value = original;
892
- input.blur();
893
- text.focus();
894
- }
895
- });
896
- });
897
- }
898
- Private.getUserInputForRename = getUserInputForRename;
899
875
  function removeSelectedItems(model, itemTypeToRemove, removeFunction) {
900
876
  var _a, _b;
901
877
  const selected = (_b = (_a = model === null || model === void 0 ? void 0 : model.localState) === null || _a === void 0 ? void 0 : _a.selected) === null || _b === void 0 ? void 0 : _b.value;
902
878
  if (!selected) {
903
- console.info('Nothing selected');
879
+ console.error('Failed to remove selected item -- nothing selected');
904
880
  return;
905
881
  }
906
882
  for (const selection in selected) {
@@ -910,10 +886,10 @@ var Private;
910
886
  }
911
887
  }
912
888
  Private.removeSelectedItems = removeSelectedItems;
913
- async function renameSelectedItem(model, itemType, callback) {
889
+ async function renameSelectedItem(model, itemType) {
914
890
  var _a;
915
891
  const selectedItems = (_a = model === null || model === void 0 ? void 0 : model.localState) === null || _a === void 0 ? void 0 : _a.selected.value;
916
- if (!selectedItems) {
892
+ if (!selectedItems || !model) {
917
893
  console.error(`No ${itemType} selected`);
918
894
  return;
919
895
  }
@@ -928,26 +904,8 @@ var Private;
928
904
  if (!itemId) {
929
905
  return;
930
906
  }
931
- const nodeId = selectedItems[itemId].selectedNodeId;
932
- if (!nodeId) {
933
- return;
934
- }
935
- const node = document.getElementById(nodeId);
936
- if (!node) {
937
- console.warn(`Node with ID ${nodeId} not found`);
938
- return;
939
- }
940
- const edit = document.createElement('input');
941
- edit.classList.add('jp-gis-left-panel-input');
942
- const originalName = node.innerText;
943
- const newName = await Private.getUserInputForRename(node, edit, originalName);
944
- if (!newName) {
945
- console.warn('New name cannot be empty');
946
- return;
947
- }
948
- if (newName !== originalName) {
949
- callback(itemId, newName);
950
- }
907
+ // Set editing state - component will show inline input
908
+ model.setEditingItem(itemType, itemId);
951
909
  }
952
910
  Private.renameSelectedItem = renameSelectedItem;
953
911
  function executeConsole(tracker) {
package/lib/constants.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ProcessingCommandIDs } from '@jupytergis/schema';
2
2
  import { redoIcon, undoIcon } from '@jupyterlab/ui-components';
3
3
  import * as BaseCommandIDs from './commands/BaseCommandIDs';
4
- import { bookOpenIcon, clockIcon, geoJSONIcon, infoIcon, moundIcon, rasterIcon, vectorSquareIcon, } from './icons';
4
+ import { bookOpenIcon, clockIcon, geoJSONIcon, infoIcon, moundIcon, rasterIcon, vectorSquareIcon, markerIcon, } from './icons';
5
5
  /**
6
6
  * The command IDs.
7
7
  */
@@ -34,6 +34,7 @@ const iconObject = {
34
34
  [CommandIDs.symbology]: { iconClass: 'fa fa-brush' },
35
35
  [CommandIDs.identify]: { icon: infoIcon },
36
36
  [CommandIDs.temporalController]: { icon: clockIcon },
37
+ [CommandIDs.addMarker]: { icon: markerIcon },
37
38
  };
38
39
  /**
39
40
  * The registered icons
@@ -14,7 +14,7 @@ const ProcessingFormWrapper = props => {
14
14
  (_a = props.formErrorSignalPromise) === null || _a === void 0 ? void 0 : _a.promise,
15
15
  ]).then(([ok, formChanged]) => {
16
16
  okSignal.current = ok;
17
- formErrorSignal.current = formChanged;
17
+ formErrorSignal.current = formChanged || undefined;
18
18
  setReady(true);
19
19
  });
20
20
  let FormComponent;
@@ -25,7 +25,7 @@ const ProcessingFormWrapper = props => {
25
25
  default:
26
26
  FormComponent = BaseForm;
27
27
  }
28
- return (ready && (React.createElement(FormComponent, { formContext: props.formContext, filePath: props.model.filePath, model: props.model, ok: okSignal.current, cancel: props.cancel, sourceData: props.sourceData, schema: props.schema, syncData: props.syncData })));
28
+ return (ready && (React.createElement(FormComponent, { formContext: props.formContext, filePath: props.model.filePath, model: props.model, ok: okSignal.current, sourceData: props.sourceData, schema: props.schema, syncData: props.syncData })));
29
29
  };
30
30
  /**
31
31
  * Dialog for processing operations
@@ -2,7 +2,8 @@ export interface IColorMap {
2
2
  name: ColorRampName;
3
3
  colors: string[];
4
4
  }
5
- export declare const COLOR_RAMP_NAMES: readonly ["jet", "hot", "cool", "spring", "summer", "autumn", "winter", "bone", "copper", "greys", "YiGnBu", "greens", "YiOrRd", "bluered", "RdBu", "rainbow", "portland", "blackbody", "earth", "electric", "viridis", "inferno", "magma", "plasma", "warm", "bathymetry", "cdom", "chlorophyll", "density", "freesurface-blue", "freesurface-red", "oxygen", "par", "phase", "salinity", "temperature", "turbidity", "velocity-blue", "velocity-green", "ice", "oxy", "matter", "amp", "tempo", "rain", "topo", "balance", "delta", "curl", "diff", "tarn"];
5
+ export declare const COLOR_RAMP_NAMES: readonly ["jet", "hsv", "hot", "cool", "spring", "summer", "autumn", "winter", "bone", "copper", "greys", "YiGnBu", "greens", "YiOrRd", "bluered", "RdBu", "picnic", "rainbow", "portland", "blackbody", "earth", "electric", "viridis", "inferno", "magma", "plasma", "warm", "rainbow-soft", "bathymetry", "cdom", "chlorophyll", "density", "freesurface-blue", "freesurface-red", "oxygen", "par", "phase", "salinity", "temperature", "turbidity", "velocity-blue", "velocity-green", "cubehelix", "ice", "oxy", "matter", "amp", "tempo", "rain", "topo", "balance", "delta", "curl", "diff", "tarn"];
6
+ export declare const COLOR_RAMP_DEFAULTS: Partial<Record<ColorRampName, number>>;
6
7
  export type ColorRampName = (typeof COLOR_RAMP_NAMES)[number];
7
8
  export declare const getColorMapList: () => IColorMap[];
8
9
  /**
@@ -17,7 +17,7 @@ const { __license__: _ } = rawCmocean, cmocean = __rest(rawCmocean, ["__license_
17
17
  Object.assign(colorScale, cmocean);
18
18
  export const COLOR_RAMP_NAMES = [
19
19
  'jet',
20
- // 'hsv', 11 steps min
20
+ 'hsv',
21
21
  'hot',
22
22
  'cool',
23
23
  'spring',
@@ -32,7 +32,7 @@ export const COLOR_RAMP_NAMES = [
32
32
  'YiOrRd',
33
33
  'bluered',
34
34
  'RdBu',
35
- // 'picnic', 11 steps min
35
+ 'picnic',
36
36
  'rainbow',
37
37
  'portland',
38
38
  'blackbody',
@@ -43,7 +43,7 @@ export const COLOR_RAMP_NAMES = [
43
43
  'magma',
44
44
  'plasma',
45
45
  'warm',
46
- // 'rainbow-soft', 11 steps min
46
+ 'rainbow-soft',
47
47
  'bathymetry',
48
48
  'cdom',
49
49
  'chlorophyll',
@@ -58,7 +58,7 @@ export const COLOR_RAMP_NAMES = [
58
58
  'turbidity',
59
59
  'velocity-blue',
60
60
  'velocity-green',
61
- // 'cubehelix' 16 steps min
61
+ 'cubehelix',
62
62
  'ice',
63
63
  'oxy',
64
64
  'matter',
@@ -72,6 +72,12 @@ export const COLOR_RAMP_NAMES = [
72
72
  'diff',
73
73
  'tarn',
74
74
  ];
75
+ export const COLOR_RAMP_DEFAULTS = {
76
+ hsv: 11,
77
+ picnic: 11,
78
+ 'rainbow-soft': 11,
79
+ cubehelix: 16,
80
+ };
75
81
  export const getColorMapList = () => {
76
82
  const colorMapList = [];
77
83
  COLOR_RAMP_NAMES.forEach(name => {
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @module ColorRampControls
3
+ *
4
+ * This component provides the main UI controls for classifying raster layers
5
+ * using different color ramps and classification modes.
6
+ *
7
+ * Allows users to:
8
+ * - Select a color ramp (`ColorRampSelector`)
9
+ * - Choose classification mode and number of classes (`ModeSelectRow`)
10
+ * - Run classification via `classifyFunc`, with loading state (`LoadingIcon`)
11
+ *
12
+ * Props:
13
+ * - `modeOptions`: Available classification modes.
14
+ * - `layerParams`: Layer symbology state.
15
+ * - `classifyFunc`: Callback for classification.
16
+ * - `showModeRow`: Toggle for mode selector.
17
+ * - `showRampSelector`: Toggle for ramp selector.
18
+ */
19
+ import { IDict } from '@jupytergis/schema';
20
+ import React from 'react';
21
+ import { ClassificationMode } from "../../../../types";
22
+ import { ColorRampName } from '../../colorRampUtils';
23
+ interface IColorRampControlsProps {
24
+ modeOptions: ClassificationMode[];
25
+ layerParams: IDict;
26
+ classifyFunc: (selectedMode: ClassificationMode, numberOfShades: number, selectedRamp: ColorRampName, setIsLoading: (isLoading: boolean) => void) => void;
27
+ showModeRow: boolean;
28
+ showRampSelector: boolean;
29
+ }
30
+ export type ColorRampControlsOptions = {
31
+ selectedRamp: ColorRampName;
32
+ numberOfShades: number;
33
+ selectedMode: ClassificationMode;
34
+ };
35
+ declare const ColorRampControls: React.FC<IColorRampControlsProps>;
36
+ export default ColorRampControls;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @module ColorRampControls
3
+ *
4
+ * This component provides the main UI controls for classifying raster layers
5
+ * using different color ramps and classification modes.
6
+ *
7
+ * Allows users to:
8
+ * - Select a color ramp (`ColorRampSelector`)
9
+ * - Choose classification mode and number of classes (`ModeSelectRow`)
10
+ * - Run classification via `classifyFunc`, with loading state (`LoadingIcon`)
11
+ *
12
+ * Props:
13
+ * - `modeOptions`: Available classification modes.
14
+ * - `layerParams`: Layer symbology state.
15
+ * - `classifyFunc`: Callback for classification.
16
+ * - `showModeRow`: Toggle for mode selector.
17
+ * - `showRampSelector`: Toggle for ramp selector.
18
+ */
19
+ import { Button } from '@jupyterlab/ui-components';
20
+ import React, { useEffect, useState } from 'react';
21
+ import { LoadingIcon } from "../../../../shared/components/loading";
22
+ import ColorRampSelector from './ColorRampSelector';
23
+ import ModeSelectRow from './ModeSelectRow';
24
+ import { COLOR_RAMP_DEFAULTS } from '../../colorRampUtils';
25
+ const isValidNumberOfShades = (value) => !isNaN(value) && value > 0;
26
+ const ColorRampControls = ({ layerParams, modeOptions, classifyFunc, showModeRow, showRampSelector, }) => {
27
+ const [selectedRamp, setSelectedRamp] = useState('viridis');
28
+ const [selectedMode, setSelectedMode] = useState('equal interval');
29
+ const [numberOfShades, setNumberOfShades] = useState(9);
30
+ const [isLoading, setIsLoading] = useState(false);
31
+ const [warning, setWarning] = useState(null);
32
+ useEffect(() => {
33
+ if (layerParams.symbologyState) {
34
+ populateOptions();
35
+ }
36
+ }, [
37
+ layerParams.symbologyState.nClasses,
38
+ layerParams.symbologyState.mode,
39
+ layerParams.symbologyState.colorRamp,
40
+ ]);
41
+ useEffect(() => {
42
+ var _a;
43
+ if (!selectedRamp) {
44
+ return;
45
+ }
46
+ const defaultClasses = (_a = COLOR_RAMP_DEFAULTS[selectedRamp]) !== null && _a !== void 0 ? _a : 9;
47
+ setNumberOfShades(defaultClasses);
48
+ setWarning(null);
49
+ }, [selectedRamp]);
50
+ useEffect(() => {
51
+ if (!selectedRamp || !numberOfShades) {
52
+ return;
53
+ }
54
+ const minRequired = COLOR_RAMP_DEFAULTS[selectedRamp];
55
+ const shades = numberOfShades;
56
+ const rampLabel = selectedRamp
57
+ .split('-')
58
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
59
+ .join('-');
60
+ if (minRequired && shades < minRequired) {
61
+ setWarning(`${rampLabel} requires at least ${minRequired} classes (got ${shades})`);
62
+ }
63
+ else {
64
+ setWarning(null);
65
+ }
66
+ }, [selectedRamp, numberOfShades]);
67
+ const populateOptions = () => {
68
+ var _a, _b, _c;
69
+ const { nClasses, mode, colorRamp } = (_a = layerParams.symbologyState) !== null && _a !== void 0 ? _a : {};
70
+ setNumberOfShades(Number(nClasses !== null && nClasses !== void 0 ? nClasses : 9));
71
+ setSelectedMode((_b = mode) !== null && _b !== void 0 ? _b : 'equal interval');
72
+ setSelectedRamp((_c = colorRamp) !== null && _c !== void 0 ? _c : 'viridis');
73
+ };
74
+ return (React.createElement("div", { className: "jp-gis-color-ramp-container" },
75
+ showRampSelector && (React.createElement("div", { className: "jp-gis-symbology-row" },
76
+ React.createElement("label", { htmlFor: "color-ramp-select" }, "Color Ramp:"),
77
+ React.createElement(ColorRampSelector, { selectedRamp: selectedRamp, setSelected: setSelectedRamp }))),
78
+ showModeRow && (React.createElement(ModeSelectRow, { modeOptions: modeOptions, numberOfShades: numberOfShades, setNumberOfShades: setNumberOfShades, selectedMode: selectedMode, setSelectedMode: setSelectedMode })),
79
+ warning && (React.createElement("div", { className: "jp-gis-warning", style: { color: 'orange', marginTop: 4 } }, warning)),
80
+ isLoading ? (React.createElement(LoadingIcon, null)) : (React.createElement(Button, { className: "jp-Dialog-button jp-mod-accept jp-mod-styled", disabled: !isValidNumberOfShades(numberOfShades) || !selectedMode || !!warning, onClick: () => classifyFunc(selectedMode, numberOfShades, selectedRamp, setIsLoading) }, "Classify"))));
81
+ };
82
+ export default ColorRampControls;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @module ColorRampSelector
3
+ *
4
+ * Dropdown component for selecting a color ramp.
5
+ * - Displays the currently selected ramp as a preview on a canvas.
6
+ * - Expands to show a list of available ramps (`ColorRampSelectorEntry`).
7
+ * - Updates the preview and notifies parent via `setSelected` when a ramp is chosen.
8
+ *
9
+ * Props:
10
+ * - `selectedRamp`: Name of the currently selected color ramp.
11
+ * - `setSelected`: Callback fired with the new ramp when selected.
12
+ */
13
+ import React from 'react';
14
+ import { ColorRampName } from "../../colorRampUtils";
15
+ interface IColorRampSelectorProps {
16
+ selectedRamp: ColorRampName;
17
+ setSelected: (item: any) => void;
18
+ }
19
+ declare const ColorRampSelector: React.FC<IColorRampSelectorProps>;
20
+ export default ColorRampSelector;
@@ -1,11 +1,25 @@
1
+ /**
2
+ * @module ColorRampSelector
3
+ *
4
+ * Dropdown component for selecting a color ramp.
5
+ * - Displays the currently selected ramp as a preview on a canvas.
6
+ * - Expands to show a list of available ramps (`ColorRampSelectorEntry`).
7
+ * - Updates the preview and notifies parent via `setSelected` when a ramp is chosen.
8
+ *
9
+ * Props:
10
+ * - `selectedRamp`: Name of the currently selected color ramp.
11
+ * - `setSelected`: Callback fired with the new ramp when selected.
12
+ */
1
13
  import { Button } from '@jupyterlab/ui-components';
2
14
  import React, { useEffect, useRef, useState } from 'react';
3
- import { useColorMapList } from "../../colorRampUtils";
4
- import ColorRampEntry from './ColorRampEntry';
5
- const CanvasSelectComponent = ({ selectedRamp, setSelected, }) => {
15
+ import { useColorMapList, } from "../../colorRampUtils";
16
+ import ColorRampSelectorEntry from './ColorRampSelectorEntry';
17
+ const ColorRampSelector = ({ selectedRamp, setSelected, }) => {
6
18
  const containerRef = useRef(null);
7
19
  const [isOpen, setIsOpen] = useState(false);
8
20
  const [colorMaps, setColorMaps] = useState([]);
21
+ const canvasWidth = 512;
22
+ const canvasHeight = 30;
9
23
  useColorMapList(setColorMaps);
10
24
  useEffect(() => {
11
25
  if (colorMaps.length > 0) {
@@ -40,11 +54,13 @@ const CanvasSelectComponent = ({ selectedRamp, setSelected, }) => {
40
54
  return;
41
55
  }
42
56
  const ramp = colorMaps.filter(c => c.name === rampName);
57
+ canvas.width = canvasWidth;
58
+ canvas.height = canvasHeight;
43
59
  for (let i = 0; i <= 255; i++) {
44
60
  ctx.beginPath();
45
61
  const color = ramp[0].colors[i];
46
62
  ctx.fillStyle = color;
47
- ctx.fillRect(i * 2, 0, 2, 50);
63
+ ctx.fillRect(i * 2, 0, 2, canvasHeight);
48
64
  }
49
65
  canvas.style.visibility = 'initial';
50
66
  };
@@ -56,7 +72,9 @@ const CanvasSelectComponent = ({ selectedRamp, setSelected, }) => {
56
72
  }, []);
57
73
  return (React.createElement("div", { ref: containerRef, className: "jp-gis-canvas-button-wrapper" },
58
74
  React.createElement(Button, { id: "jp-gis-canvas-button", onClick: toggleDropdown, className: "jp-Dialog-button jp-gis-canvas-button" },
59
- React.createElement("canvas", { id: "cv", className: "jp-gis-color-canvas-display", height: "30" })),
60
- React.createElement("div", { className: `jp-gis-color-ramp-dropdown ${isOpen ? 'jp-gis-open' : ''}` }, colorMaps.map((item, index) => (React.createElement(ColorRampEntry, { index: index, colorMap: item, onClick: selectItem }))))));
75
+ React.createElement("div", { className: "jp-gis-color-ramp-entry jp-gis-selected-entry" },
76
+ React.createElement("span", { className: "jp-gis-color-label" }, selectedRamp),
77
+ React.createElement("canvas", { id: "cv", className: "jp-gis-color-canvas-display", width: canvasWidth, height: canvasHeight }))),
78
+ React.createElement("div", { className: `jp-gis-color-ramp-dropdown ${isOpen ? 'jp-gis-open' : ''}` }, colorMaps.map((item, index) => (React.createElement(ColorRampSelectorEntry, { index: index, colorMap: item, onClick: selectItem }))))));
61
79
  };
62
- export default CanvasSelectComponent;
80
+ export default ColorRampSelector;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @module ColorRampSelectorEntry
3
+ *
4
+ * Represents a single selectable color ramp option in the `ColorRampSelector`.
5
+ * Renders a preview ColorRamp on a canvas and triggers `onClick` when selected.
6
+ *
7
+ * Props:
8
+ * - `index`: Unique index for canvas ID.
9
+ * - `colorMap`: Ramp definition including name and colors.
10
+ * - `onClick`: Callback fired with the ramp name when clicked.
11
+ */
12
+ import React from 'react';
13
+ import { IColorMap } from "../../colorRampUtils";
14
+ interface IColorRampSelectorEntryProps {
15
+ index: number;
16
+ colorMap: IColorMap;
17
+ onClick: (item: any) => void;
18
+ }
19
+ declare const ColorRampSelectorEntry: React.FC<IColorRampSelectorEntryProps>;
20
+ export default ColorRampSelectorEntry;
@@ -1,11 +1,25 @@
1
+ /**
2
+ * @module ColorRampSelectorEntry
3
+ *
4
+ * Represents a single selectable color ramp option in the `ColorRampSelector`.
5
+ * Renders a preview ColorRamp on a canvas and triggers `onClick` when selected.
6
+ *
7
+ * Props:
8
+ * - `index`: Unique index for canvas ID.
9
+ * - `colorMap`: Ramp definition including name and colors.
10
+ * - `onClick`: Callback fired with the ramp name when clicked.
11
+ */
1
12
  import React, { useEffect } from 'react';
2
- const ColorRampEntry = ({ index, colorMap, onClick, }) => {
13
+ const ColorRampSelectorEntry = ({ index, colorMap, onClick, }) => {
14
+ const canvasWidth = 512;
3
15
  const canvasHeight = 30;
4
16
  useEffect(() => {
5
17
  const canvas = document.getElementById(`cv-${index}`);
6
18
  if (!canvas) {
7
19
  return;
8
20
  }
21
+ canvas.width = canvasWidth;
22
+ canvas.height = canvasHeight;
9
23
  const ctx = canvas.getContext('2d');
10
24
  if (!ctx) {
11
25
  return;
@@ -19,6 +33,6 @@ const ColorRampEntry = ({ index, colorMap, onClick, }) => {
19
33
  }, []);
20
34
  return (React.createElement("div", { key: colorMap.name, onClick: () => onClick(colorMap.name), className: "jp-gis-color-ramp-entry" },
21
35
  React.createElement("span", { className: "jp-gis-color-label" }, colorMap.name),
22
- React.createElement("canvas", { id: `cv-${index}`, height: canvasHeight, className: "jp-gis-color-canvas" })));
36
+ React.createElement("canvas", { id: `cv-${index}`, width: canvasWidth, height: canvasHeight, className: "jp-gis-color-canvas" })));
23
37
  };
24
- export default ColorRampEntry;
38
+ export default ColorRampSelectorEntry;
@@ -1,10 +1,11 @@
1
1
  import React from 'react';
2
+ import { ClassificationMode } from "../../../../types";
2
3
  interface IModeSelectRowProps {
3
- numberOfShades: string;
4
- setNumberOfShades: (value: string) => void;
5
- selectedMode: string;
6
- setSelectedMode: (value: string) => void;
7
- modeOptions: string[];
4
+ numberOfShades: number;
5
+ setNumberOfShades: React.Dispatch<React.SetStateAction<number>>;
6
+ selectedMode: ClassificationMode;
7
+ setSelectedMode: React.Dispatch<React.SetStateAction<ClassificationMode>>;
8
+ modeOptions: ClassificationMode[];
8
9
  }
9
10
  declare const ModeSelectRow: React.FC<IModeSelectRowProps>;
10
11
  export default ModeSelectRow;
@@ -3,10 +3,15 @@ const ModeSelectRow = ({ numberOfShades, setNumberOfShades, selectedMode, setSel
3
3
  return (React.createElement("div", { className: "jp-gis-symbology-row" },
4
4
  React.createElement("div", { className: "jp-gis-color-ramp-div" },
5
5
  React.createElement("label", { htmlFor: "class-number-input" }, "Classes:"),
6
- React.createElement("input", { className: "jp-mod-styled", name: "class-number-input", type: "number", value: selectedMode === 'continuous' ? 52 : numberOfShades, onChange: event => setNumberOfShades(event.target.value), disabled: selectedMode === 'continuous' })),
6
+ React.createElement("input", { className: "jp-mod-styled", name: "class-number-input", type: "number", value: selectedMode === 'continuous' ? 52 : numberOfShades, onChange: event => {
7
+ const value = Number(event.target.value);
8
+ if (!isNaN(value) && value > 0) {
9
+ setNumberOfShades(value);
10
+ }
11
+ }, disabled: selectedMode === 'continuous' })),
7
12
  React.createElement("div", { className: "jp-gis-color-ramp-div" },
8
13
  React.createElement("label", { htmlFor: "mode-select" }, "Mode:"),
9
14
  React.createElement("div", { className: "jp-select-wrapper" },
10
- React.createElement("select", { name: "mode-select", id: "mode-select", className: "jp-mod-styled", value: selectedMode, onChange: event => setSelectedMode(event.target.value) }, modeOptions.map(mode => (React.createElement("option", { key: mode, value: mode, selected: selectedMode === mode }, mode))))))));
15
+ React.createElement("select", { name: "mode-select", id: "mode-select", className: "jp-mod-styled", value: selectedMode, onChange: event => setSelectedMode(event.target.value) }, modeOptions.map(mode => (React.createElement("option", { key: mode, value: mode }, mode))))))));
11
16
  };
12
17
  export default ModeSelectRow;
@@ -2,13 +2,19 @@
2
2
  import { useEffect, useState } from 'react';
3
3
  import { loadFile } from "../../../tools";
4
4
  async function getGeoJsonProperties({ source, model, }) {
5
- var _a;
6
5
  const result = {};
7
- const data = await loadFile({
8
- filepath: (_a = source.parameters) === null || _a === void 0 ? void 0 : _a.path,
9
- type: 'GeoJSONSource',
10
- model,
11
- });
6
+ const data = await (async () => {
7
+ if (source.parameters.path) {
8
+ return await loadFile({
9
+ filepath: source.parameters.path,
10
+ type: 'GeoJSONSource',
11
+ model,
12
+ });
13
+ }
14
+ else if (source.parameters.data) {
15
+ return source.parameters.data;
16
+ }
17
+ })();
12
18
  if (!data) {
13
19
  throw new Error('Failed to read GeoJSON data');
14
20
  }