-
Notifications
You must be signed in to change notification settings - Fork 64
Support divergent color ramps #912
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 3 commits
78e9966
63bcb15
e9ecb40
2e2f37b
67faef4
1af0c01
19ed962
dbeb780
c1fbede
b478a85
df7df8f
4c86aaf
8374c71
c5735c4
fe3465a
5199cb4
63c1185
f9108cb
c643d52
437bcf0
20ee921
422580b
60e643f
29b4b9c
37d946a
a928f56
b283268
8e9b604
f2bc41f
317142d
4af515d
a39b547
e9de64d
b874d0f
e8f1db6
aaf19d4
b30b727
f985d6f
25183fc
504a687
2b5223b
928278a
345af02
cc0901f
18abc0e
78c5ca9
6ec6d16
44b352e
273b7f5
ec77af3
9f56575
b433b1d
c67144a
5de88cc
5eb2a4c
cf7fe08
3033b51
d1c7ce9
443d195
4755f5e
9b55f00
2e0209e
372b9c5
3d07f7b
5370f3e
1cb0b92
cf5fd26
1747e6e
657aba6
612d0fa
10dc8b4
673a777
7a5b754
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,10 @@ import React, { useEffect, useState } from 'react'; | |
| import { LoadingIcon } from '@/src/shared/components/loading'; | ||
| import CanvasSelectComponent from './CanvasSelectComponent'; | ||
| import ModeSelectRow from './ModeSelectRow'; | ||
|
|
||
| import { | ||
| COLOR_RAMP_DEFINITIONS, | ||
| ColorRampName, | ||
| } from '../../../symbology/colorRampUtils'; | ||
nakul-py marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| interface IColorRampProps { | ||
| modeOptions: string[]; | ||
| layerParams: IDict; | ||
|
|
@@ -14,15 +17,22 @@ interface IColorRampProps { | |
| numberOfShades: string, | ||
| selectedRamp: string, | ||
| setIsLoading: (isLoading: boolean) => void, | ||
| criticalValue?: number, | ||
| minValue?: number, | ||
| maxValue?: number, | ||
|
||
| ) => void; | ||
| showModeRow: boolean; | ||
| showRampSelector: boolean; | ||
| layerType?: 'graduated' | 'categorized'; | ||
nakul-py marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| export type ColorRampOptions = { | ||
| selectedRamp: string; | ||
| numberOfShades: string; | ||
| selectedMode: string; | ||
| minValue?: number; | ||
| maxValue?: number; | ||
| criticalValue?: number; | ||
| }; | ||
|
|
||
| const ColorRamp: React.FC<IColorRampProps> = ({ | ||
|
|
@@ -31,10 +41,14 @@ const ColorRamp: React.FC<IColorRampProps> = ({ | |
| classifyFunc, | ||
| showModeRow, | ||
| showRampSelector, | ||
| layerType, | ||
| }) => { | ||
| const [selectedRamp, setSelectedRamp] = useState(''); | ||
| const [selectedMode, setSelectedMode] = useState(''); | ||
| const [numberOfShades, setNumberOfShades] = useState(''); | ||
| const [criticalValue, setCriticalValue] = useState<number | undefined>(0); | ||
| const [minValue, setMinValue] = useState<number | undefined>(-5); | ||
nakul-py marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| const [maxValue, setMaxValue] = useState<number | undefined>(5); | ||
| const [isLoading, setIsLoading] = useState(false); | ||
|
|
||
| useEffect(() => { | ||
|
|
@@ -44,18 +58,27 @@ const ColorRamp: React.FC<IColorRampProps> = ({ | |
| }, [layerParams]); | ||
|
|
||
| const populateOptions = () => { | ||
nakul-py marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| let nClasses, singleBandMode, colorRamp; | ||
| let nClasses, singleBandMode, colorRamp, min, max, crit; | ||
|
|
||
| if (layerParams.symbologyState) { | ||
| nClasses = layerParams.symbologyState.nClasses; | ||
| singleBandMode = layerParams.symbologyState.mode; | ||
| colorRamp = layerParams.symbologyState.colorRamp; | ||
| min = layerParams.symbologyState.minValue; | ||
| max = layerParams.symbologyState.maxValue; | ||
| crit = layerParams.symbologyState.criticalValue; | ||
| } | ||
| setNumberOfShades(nClasses ? nClasses : '9'); | ||
| setSelectedMode(singleBandMode ? singleBandMode : 'equal interval'); | ||
| setSelectedRamp(colorRamp ? colorRamp : 'viridis'); | ||
| setMinValue(min !== undefined ? min : -5); | ||
| setMaxValue(max !== undefined ? max : 5); | ||
| setCriticalValue(crit !== undefined ? crit : 0); | ||
| }; | ||
|
|
||
| const rampDef = COLOR_RAMP_DEFINITIONS[selectedRamp as ColorRampName]; | ||
| const rampType = rampDef?.type || 'Unknown'; | ||
nakul-py marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| return ( | ||
| <div className="jp-gis-color-ramp-container"> | ||
| {showRampSelector && ( | ||
|
|
@@ -65,6 +88,13 @@ const ColorRamp: React.FC<IColorRampProps> = ({ | |
| selectedRamp={selectedRamp} | ||
| setSelected={setSelectedRamp} | ||
| /> | ||
| {selectedRamp && ( | ||
| <div className="jp-gis-ramp-type ml-auto"> | ||
| <span className="jp-gis-ramp-type-label"> | ||
| Color Ramp Type: {rampType} | ||
| </span> | ||
| </div> | ||
| )} | ||
| </div> | ||
| )} | ||
| {showModeRow && ( | ||
|
|
@@ -76,6 +106,64 @@ const ColorRamp: React.FC<IColorRampProps> = ({ | |
| setSelectedMode={setSelectedMode} | ||
| /> | ||
| )} | ||
| {/* 🔹 Divergent colormap controls */} | ||
| {layerType === 'graduated' && rampType === 'Divergent' && ( | ||
nakul-py marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| <> | ||
| <div className="jp-gis-symbology-row"> | ||
| <label htmlFor="min-value">Min Value:</label> | ||
| <input | ||
| id="min-value" | ||
| type="number" | ||
| value={minValue ?? ''} | ||
| onChange={e => | ||
| setMinValue( | ||
| e.target.value !== '' | ||
| ? parseFloat(e.target.value) | ||
| : undefined, | ||
| ) | ||
| } | ||
| className="jp-mod-styled" | ||
| placeholder="Enter min value" | ||
| /> | ||
| </div> | ||
|
|
||
| <div className="jp-gis-symbology-row"> | ||
| <label htmlFor="critical-value">Critical Value:</label> | ||
| <input | ||
nakul-py marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| id="critical-value" | ||
| type="number" | ||
| value={criticalValue ?? ''} | ||
| onChange={e => | ||
| setCriticalValue( | ||
| e.target.value !== '' | ||
| ? parseFloat(e.target.value) | ||
| : undefined, | ||
| ) | ||
| } | ||
| className="jp-mod-styled" | ||
| placeholder="Enter critical value" | ||
| /> | ||
| </div> | ||
|
|
||
| <div className="jp-gis-symbology-row"> | ||
| <label htmlFor="max-value">Max Value:</label> | ||
| <input | ||
| id="max-value" | ||
| type="number" | ||
| value={maxValue ?? ''} | ||
| onChange={e => | ||
| setMaxValue( | ||
| e.target.value !== '' | ||
| ? parseFloat(e.target.value) | ||
| : undefined, | ||
| ) | ||
| } | ||
| className="jp-mod-styled" | ||
| placeholder="Enter max value" | ||
| /> | ||
| </div> | ||
| </> | ||
| )} | ||
| {isLoading ? ( | ||
| <LoadingIcon /> | ||
| ) : ( | ||
|
|
@@ -87,6 +175,9 @@ const ColorRamp: React.FC<IColorRampProps> = ({ | |
| numberOfShades, | ||
| selectedRamp, | ||
| setIsLoading, | ||
| criticalValue, | ||
| minValue, | ||
| maxValue, | ||
| ) | ||
| } | ||
| > | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| import { IJGISLayer } from '@jupytergis/schema'; | ||
| import colormap from 'colormap'; | ||
|
|
||
| import { COLOR_RAMP_DEFINITIONS, ColorRampName } from './colorRampUtils'; | ||
| import { IStopRow } from './symbologyDialog'; | ||
|
|
||
| const COLOR_EXPR_STOPS_START = 3; | ||
|
|
@@ -104,7 +105,35 @@ export namespace Utils { | |
| selectedRamp: string, | ||
nakul-py marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| nClasses: number, | ||
| reverse = false, | ||
| layerType: 'categorized' | 'graduated' = 'graduated', | ||
nakul-py marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| minValue?: number, | ||
| maxValue?: number, | ||
| ) => { | ||
| const rampDef = COLOR_RAMP_DEFINITIONS[selectedRamp as ColorRampName]; | ||
nakul-py marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| let effectiveStops: number[] = []; | ||
|
|
||
| if (layerType === 'categorized') { | ||
| effectiveStops = stops; | ||
| } else { | ||
| if (rampDef?.type === 'Divergent') { | ||
| const min = minValue ?? Math.min(...stops); | ||
| const max = maxValue ?? Math.max(...stops); | ||
|
|
||
| effectiveStops = Array.from( | ||
| { length: nClasses }, | ||
| (_, i) => min + (i / (nClasses - 1)) * (max - min), | ||
| ); | ||
| } else { | ||
| const min = Math.min(...stops); | ||
| const max = Math.max(...stops); | ||
|
|
||
| effectiveStops = Array.from( | ||
| { length: nClasses }, | ||
| (_, i) => min + (i / (nClasses - 1)) * (max - min), | ||
| ); | ||
| } | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I'm feeling a bit tired at the end of the day, and this may be a dumb question :) Previously, we calculated the data stops array outside of this function; can we leverage that existing code instead of adding new code here? E.g. we have Or am I missing an awesome reason why this is better?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes we can use
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Taking a deeper look today. The problem is that the stops are pre-calculated outside this function based on the selected "mode", and then this function is overwriting them with equal interval stops. This means the "mode" dropdown no longer works. Looking in to how we can adjust for this now! |
||
|
|
||
| let colorMap = colormap({ | ||
| colormap: selectedRamp, | ||
| nshades: nClasses > 9 ? nClasses : 9, | ||
|
|
@@ -127,15 +156,15 @@ export namespace Utils { | |
|
|
||
| // Get the last n/2 elements from the second array | ||
| const secondPart = colorMap.slice( | ||
| colorMap.length - (stops.length - firstPart.length), | ||
| colorMap.length - (effectiveStops.length - firstPart.length), | ||
| ); | ||
|
|
||
| // Create the new array by combining the first and last parts | ||
| colorMap = firstPart.concat(secondPart); | ||
| } | ||
|
|
||
| for (let i = 0; i < nClasses; i++) { | ||
| valueColorPairs.push({ stop: stops[i], output: colorMap[i] }); | ||
| valueColorPairs.push({ stop: effectiveStops[i], output: colorMap[i] }); | ||
| } | ||
|
|
||
| return valueColorPairs; | ||
|
|
||
nakul-py marked this conversation as resolved.
Show resolved
Hide resolved
mfisher87 marked this conversation as resolved.
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -218,11 +218,18 @@ const Graduated: React.FC<ISymbologyTabbedDialogWithAttributesProps> = ({ | |
| selectedMode: string, | ||
| numberOfShades: string, | ||
| selectedRamp: string, | ||
| setIsLoading: (isLoading: boolean) => void, | ||
| criticalValue?: number, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to pass this in?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes i think we need |
||
| minValue?: number, | ||
| maxValue?: number, | ||
| ) => { | ||
| setColorRampOptions({ | ||
| selectedRamp, | ||
| numberOfShades, | ||
| selectedMode, | ||
| minValue, | ||
| maxValue, | ||
| criticalValue, | ||
| }); | ||
|
|
||
| let stops: number[]; | ||
|
|
@@ -273,13 +280,18 @@ const Graduated: React.FC<ISymbologyTabbedDialogWithAttributesProps> = ({ | |
| selectedRamp, | ||
| +numberOfShades, | ||
| reverseRamp, | ||
| 'graduated', | ||
| minValue, | ||
| maxValue, | ||
| ); | ||
|
|
||
| if (symbologyTab === 'radius') { | ||
| setRadiusStopRows(stopOutputPairs); | ||
| } else { | ||
| setColorStopRows(stopOutputPairs); | ||
| } | ||
|
|
||
| setIsLoading(false); | ||
| }; | ||
|
|
||
| const handleReset = (method: string) => { | ||
|
|
@@ -408,6 +420,7 @@ const Graduated: React.FC<ISymbologyTabbedDialogWithAttributesProps> = ({ | |
| classifyFunc={buildColorInfoFromClassification} | ||
| showModeRow={true} | ||
| showRampSelector={symbologyTab === 'color'} | ||
| layerType="graduated" | ||
| /> | ||
| <StopContainer | ||
| selectedMethod={symbologyTab || 'color'} | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.