@jupytergis/base 0.9.1 → 0.10.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.
Files changed (75) 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 +20 -0
  8. package/lib/dialogs/symbology/colorRampUtils.js +132 -0
  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} +26 -65
  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/components/color_ramp/cmocean.json +459 -0
  18. package/lib/dialogs/symbology/components/color_stops/StopContainer.js +1 -1
  19. package/lib/dialogs/symbology/components/color_stops/StopRow.d.ts +3 -2
  20. package/lib/dialogs/symbology/components/color_stops/StopRow.js +4 -29
  21. package/lib/dialogs/symbology/hooks/useGetProperties.js +12 -6
  22. package/lib/dialogs/symbology/symbologyDialog.d.ts +2 -2
  23. package/lib/dialogs/symbology/symbologyUtils.d.ts +2 -1
  24. package/lib/dialogs/symbology/symbologyUtils.js +10 -4
  25. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +9 -5
  26. package/lib/dialogs/symbology/vector_layer/VectorRendering.js +2 -2
  27. package/lib/dialogs/symbology/vector_layer/types/Categorized.js +4 -5
  28. package/lib/dialogs/symbology/vector_layer/types/Graduated.js +16 -28
  29. package/lib/dialogs/symbology/vector_layer/types/Heatmap.js +3 -3
  30. package/lib/formbuilder/editform.js +4 -3
  31. package/lib/formbuilder/objectform/layer/heatmapLayerForm.d.ts +2 -1
  32. package/lib/formbuilder/objectform/layer/heatmapLayerForm.js +4 -0
  33. package/lib/formbuilder/objectform/layer/vectorlayerform.d.ts +2 -1
  34. package/lib/formbuilder/objectform/layer/vectorlayerform.js +4 -0
  35. package/lib/formbuilder/objectform/layer/webGlLayerForm.d.ts +2 -0
  36. package/lib/formbuilder/objectform/layer/webGlLayerForm.js +4 -0
  37. package/lib/icons.d.ts +1 -0
  38. package/lib/icons.js +5 -0
  39. package/lib/index.d.ts +0 -1
  40. package/lib/index.js +0 -1
  41. package/lib/mainview/mainView.d.ts +1 -0
  42. package/lib/mainview/mainView.js +62 -12
  43. package/lib/panelview/annotationPanel.js +1 -1
  44. package/lib/panelview/components/filter-panel/Filter.js +1 -1
  45. package/lib/panelview/components/layers.js +152 -51
  46. package/lib/panelview/components/legendItem.js +36 -1
  47. package/lib/panelview/leftpanel.d.ts +0 -1
  48. package/lib/panelview/leftpanel.js +4 -4
  49. package/lib/panelview/rightpanel.js +4 -4
  50. package/lib/processing/processingFormToParam.js +3 -0
  51. package/lib/shared/components/ToggleGroup.d.ts +2 -2
  52. package/lib/stacBrowser/hooks/useStacSearch.js +2 -2
  53. package/lib/toolbar/widget.js +19 -28
  54. package/lib/tools.d.ts +1 -0
  55. package/lib/tools.js +2 -2
  56. package/lib/types.d.ts +8 -0
  57. package/lib/types.js +8 -0
  58. package/package.json +2 -2
  59. package/style/base.css +2 -0
  60. package/style/icons/book_open.svg +1 -1
  61. package/style/icons/clock-solid.svg +1 -1
  62. package/style/icons/geolocation.svg +1 -1
  63. package/style/icons/info-solid.svg +1 -1
  64. package/style/icons/logo_mini.svg +1 -1
  65. package/style/icons/marker.svg +5 -0
  66. package/style/icons/target_without_center.svg +1 -1
  67. package/style/icons/vector_square.svg +1 -1
  68. package/style/shared/tabs.css +1 -0
  69. package/style/symbologyDialog.css +12 -4
  70. package/lib/classificationModes.d.ts +0 -13
  71. package/lib/classificationModes.js +0 -326
  72. package/lib/dialogs/symbology/components/color_ramp/CanvasSelectComponent.d.ts +0 -11
  73. package/lib/dialogs/symbology/components/color_ramp/ColorRamp.d.ts +0 -16
  74. package/lib/dialogs/symbology/components/color_ramp/ColorRamp.js +0 -32
  75. 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
@@ -0,0 +1,20 @@
1
+ export interface IColorMap {
2
+ name: ColorRampName;
3
+ colors: string[];
4
+ }
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>>;
7
+ export type ColorRampName = (typeof COLOR_RAMP_NAMES)[number];
8
+ export declare const getColorMapList: () => IColorMap[];
9
+ /**
10
+ * Hook that loads and sets color maps.
11
+ */
12
+ export declare const useColorMapList: (setColorMaps: (maps: IColorMap[]) => void) => void;
13
+ /**
14
+ * Ensure we always get a valid hex string from either an RGB(A) array or string.
15
+ */
16
+ export declare const ensureHexColorCode: (color: number[] | string) => string;
17
+ /**
18
+ * Convert hex to [r,g,b,a] array.
19
+ */
20
+ export declare function hexToRgb(hex: string): [number, number, number, number];
@@ -0,0 +1,132 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import colormap from 'colormap';
13
+ import colorScale from 'colormap/colorScale.js';
14
+ import { useEffect } from 'react';
15
+ import rawCmocean from "./components/color_ramp/cmocean.json";
16
+ const { __license__: _ } = rawCmocean, cmocean = __rest(rawCmocean, ["__license__"]);
17
+ Object.assign(colorScale, cmocean);
18
+ export const COLOR_RAMP_NAMES = [
19
+ 'jet',
20
+ 'hsv',
21
+ 'hot',
22
+ 'cool',
23
+ 'spring',
24
+ 'summer',
25
+ 'autumn',
26
+ 'winter',
27
+ 'bone',
28
+ 'copper',
29
+ 'greys',
30
+ 'YiGnBu',
31
+ 'greens',
32
+ 'YiOrRd',
33
+ 'bluered',
34
+ 'RdBu',
35
+ 'picnic',
36
+ 'rainbow',
37
+ 'portland',
38
+ 'blackbody',
39
+ 'earth',
40
+ 'electric',
41
+ 'viridis',
42
+ 'inferno',
43
+ 'magma',
44
+ 'plasma',
45
+ 'warm',
46
+ 'rainbow-soft',
47
+ 'bathymetry',
48
+ 'cdom',
49
+ 'chlorophyll',
50
+ 'density',
51
+ 'freesurface-blue',
52
+ 'freesurface-red',
53
+ 'oxygen',
54
+ 'par',
55
+ 'phase',
56
+ 'salinity',
57
+ 'temperature',
58
+ 'turbidity',
59
+ 'velocity-blue',
60
+ 'velocity-green',
61
+ 'cubehelix',
62
+ 'ice',
63
+ 'oxy',
64
+ 'matter',
65
+ 'amp',
66
+ 'tempo',
67
+ 'rain',
68
+ 'topo',
69
+ 'balance',
70
+ 'delta',
71
+ 'curl',
72
+ 'diff',
73
+ 'tarn',
74
+ ];
75
+ export const COLOR_RAMP_DEFAULTS = {
76
+ hsv: 11,
77
+ picnic: 11,
78
+ 'rainbow-soft': 11,
79
+ cubehelix: 16,
80
+ };
81
+ export const getColorMapList = () => {
82
+ const colorMapList = [];
83
+ COLOR_RAMP_NAMES.forEach(name => {
84
+ const colorRamp = colormap({
85
+ colormap: name,
86
+ nshades: 255,
87
+ format: 'rgbaString',
88
+ });
89
+ colorMapList.push({ name, colors: colorRamp });
90
+ });
91
+ return colorMapList;
92
+ };
93
+ /**
94
+ * Hook that loads and sets color maps.
95
+ */
96
+ export const useColorMapList = (setColorMaps) => {
97
+ useEffect(() => {
98
+ setColorMaps(getColorMapList());
99
+ }, [setColorMaps]);
100
+ };
101
+ /**
102
+ * Ensure we always get a valid hex string from either an RGB(A) array or string.
103
+ */
104
+ export const ensureHexColorCode = (color) => {
105
+ if (typeof color === 'string') {
106
+ return color;
107
+ }
108
+ // color must be an RGBA array
109
+ const hex = color
110
+ .slice(0, -1) // Color input doesn't support hex alpha values so cut that out
111
+ .map((val) => {
112
+ return val.toString(16).padStart(2, '0');
113
+ })
114
+ .join('');
115
+ return '#' + hex;
116
+ };
117
+ /**
118
+ * Convert hex to [r,g,b,a] array.
119
+ */
120
+ export function hexToRgb(hex) {
121
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
122
+ if (!result) {
123
+ console.warn('Unable to parse hex value, defaulting to black');
124
+ return [0, 0, 0, 255];
125
+ }
126
+ return [
127
+ parseInt(result[1], 16),
128
+ parseInt(result[2], 16),
129
+ parseInt(result[3], 16),
130
+ 255, // TODO: Make alpha customizable?
131
+ ];
132
+ }
@@ -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,69 +1,26 @@
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
- import colormap from 'colormap';
3
14
  import React, { useEffect, useRef, useState } from 'react';
4
- import ColorRampEntry from './ColorRampEntry';
5
- const CanvasSelectComponent = ({ selectedRamp, setSelected, }) => {
6
- const colorRampNames = [
7
- 'jet',
8
- // 'hsv', 11 steps min
9
- 'hot',
10
- 'cool',
11
- 'spring',
12
- 'summer',
13
- 'autumn',
14
- 'winter',
15
- 'bone',
16
- 'copper',
17
- 'greys',
18
- 'YiGnBu',
19
- 'greens',
20
- 'YiOrRd',
21
- 'bluered',
22
- 'RdBu',
23
- // 'picnic', 11 steps min
24
- 'rainbow',
25
- 'portland',
26
- 'blackbody',
27
- 'earth',
28
- 'electric',
29
- 'viridis',
30
- 'inferno',
31
- 'magma',
32
- 'plasma',
33
- 'warm',
34
- // 'rainbow-soft', 11 steps min
35
- 'bathymetry',
36
- 'cdom',
37
- 'chlorophyll',
38
- 'density',
39
- 'freesurface-blue',
40
- 'freesurface-red',
41
- 'oxygen',
42
- 'par',
43
- 'phase',
44
- 'salinity',
45
- 'temperature',
46
- 'turbidity',
47
- 'velocity-blue',
48
- 'velocity-green',
49
- // 'cubehelix' 16 steps min
50
- ];
15
+ import { useColorMapList, } from "../../colorRampUtils";
16
+ import ColorRampSelectorEntry from './ColorRampSelectorEntry';
17
+ const ColorRampSelector = ({ selectedRamp, setSelected, }) => {
51
18
  const containerRef = useRef(null);
52
19
  const [isOpen, setIsOpen] = useState(false);
53
20
  const [colorMaps, setColorMaps] = useState([]);
54
- useEffect(() => {
55
- const colorMapList = [];
56
- colorRampNames.forEach(name => {
57
- const colorRamp = colormap({
58
- colormap: name,
59
- nshades: 255,
60
- format: 'rgbaString',
61
- });
62
- const colorMap = { name: name, colors: colorRamp };
63
- colorMapList.push(colorMap);
64
- setColorMaps(colorMapList);
65
- });
66
- }, []);
21
+ const canvasWidth = 512;
22
+ const canvasHeight = 30;
23
+ useColorMapList(setColorMaps);
67
24
  useEffect(() => {
68
25
  if (colorMaps.length > 0) {
69
26
  updateCanvas(selectedRamp);
@@ -97,11 +54,13 @@ const CanvasSelectComponent = ({ selectedRamp, setSelected, }) => {
97
54
  return;
98
55
  }
99
56
  const ramp = colorMaps.filter(c => c.name === rampName);
57
+ canvas.width = canvasWidth;
58
+ canvas.height = canvasHeight;
100
59
  for (let i = 0; i <= 255; i++) {
101
60
  ctx.beginPath();
102
61
  const color = ramp[0].colors[i];
103
62
  ctx.fillStyle = color;
104
- ctx.fillRect(i * 2, 0, 2, 50);
63
+ ctx.fillRect(i * 2, 0, 2, canvasHeight);
105
64
  }
106
65
  canvas.style.visibility = 'initial';
107
66
  };
@@ -113,7 +72,9 @@ const CanvasSelectComponent = ({ selectedRamp, setSelected, }) => {
113
72
  }, []);
114
73
  return (React.createElement("div", { ref: containerRef, className: "jp-gis-canvas-button-wrapper" },
115
74
  React.createElement(Button, { id: "jp-gis-canvas-button", onClick: toggleDropdown, className: "jp-Dialog-button jp-gis-canvas-button" },
116
- React.createElement("canvas", { id: "cv", className: "jp-gis-color-canvas-display", height: "30" })),
117
- 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 }))))));
118
79
  };
119
- export default CanvasSelectComponent;
80
+ export default ColorRampSelector;