diff --git a/app/scripts/components/common/map/style-generators/raster-paint-layer.tsx b/app/scripts/components/common/map/style-generators/raster-paint-layer.tsx index bf5f170bf..d6d954e8d 100644 --- a/app/scripts/components/common/map/style-generators/raster-paint-layer.tsx +++ b/app/scripts/components/common/map/style-generators/raster-paint-layer.tsx @@ -13,6 +13,7 @@ interface RasterPaintLayerProps extends BaseGeneratorParams { colorMap?: string | undefined; tileParams: Record; generatorPrefix?: string; + reScale?: { min: number; max: number }; } export function RasterPaintLayer(props: RasterPaintLayerProps) { @@ -24,16 +25,20 @@ export function RasterPaintLayer(props: RasterPaintLayerProps) { hidden, opacity, colorMap, - generatorPrefix = 'raster', + reScale, + generatorPrefix = 'raster' } = props; - const { updateStyle } = useMapStyle(); const [minZoom] = zoomExtent ?? [0, 20]; const generatorId = `${generatorPrefix}-${id}`; const updatedTileParams = useMemo(() => { - return { ...tileParams, ...colorMap && {colormap_name: colorMap}}; - }, [tileParams, colorMap]); + return { + ...tileParams, + ...(colorMap && { colormap_name: colorMap }), + ...(reScale && { reScale: Object.values(reScale) }) + }; + }, [tileParams, colorMap, reScale]); // // Generate Mapbox GL layers and sources for raster timeseries @@ -47,7 +52,9 @@ export function RasterPaintLayer(props: RasterPaintLayerProps) { useEffect( () => { - const tileParamsAsString = qs.stringify(updatedTileParams, { arrayFormat: 'comma' }); + const tileParamsAsString = qs.stringify(updatedTileParams, { + arrayFormat: 'comma' + }); const zarrSource: RasterSource = { type: 'raster', @@ -63,8 +70,8 @@ export function RasterPaintLayer(props: RasterPaintLayerProps) { paint: { 'raster-opacity': hidden ? 0 : rasterOpacity, 'raster-opacity-transition': { - duration: 320, - }, + duration: 320 + } }, minzoom: minZoom, metadata: { @@ -93,7 +100,8 @@ export function RasterPaintLayer(props: RasterPaintLayerProps) { tileApiEndpoint, haveTileParamsChanged, generatorParams, - colorMap + colorMap, + reScale // generatorParams includes hidden and opacity // hidden, // opacity, diff --git a/app/scripts/components/common/map/style-generators/raster-timeseries.tsx b/app/scripts/components/common/map/style-generators/raster-timeseries.tsx index ccaaeb3d5..3030506ad 100644 --- a/app/scripts/components/common/map/style-generators/raster-timeseries.tsx +++ b/app/scripts/components/common/map/style-generators/raster-timeseries.tsx @@ -63,6 +63,7 @@ export function RasterTimeseries(props: RasterTimeseriesProps) { stacApiEndpoint, tileApiEndpoint, colorMap, + reScale, envApiStacEndpoint, envApiRasterEndpoint } = props; @@ -367,7 +368,8 @@ export function RasterTimeseries(props: RasterTimeseriesProps) { { assets: 'cog_default', ...(sourceParams ?? {}), - ...(colorMap && { colormap_name: colorMap }) + ...(colorMap && { colormap_name: colorMap }), + ...(reScale && { rescale: Object.values(reScale) }) }, // Temporary solution to pass different tile parameters for hls data { @@ -495,6 +497,7 @@ export function RasterTimeseries(props: RasterTimeseriesProps) { }, [ mosaicUrl, colorMap, + reScale, points, minZoom, haveSourceParamsChanged, diff --git a/app/scripts/components/common/map/types.d.ts b/app/scripts/components/common/map/types.d.ts index 8eb8bb8c1..39af6eb5c 100644 --- a/app/scripts/components/common/map/types.d.ts +++ b/app/scripts/components/common/map/types.d.ts @@ -54,6 +54,7 @@ export interface BaseTimeseriesProps extends BaseGeneratorParams { zoomExtent?: number[]; onStatusChange?: (result: { status: ActionStatus; id: string }) => void; colorMap?: string; + reScale?: { min: number; max: number }; envApiStacEndpoint: string; envApiRasterEndpoint: string; } diff --git a/app/scripts/components/common/uswds/index.tsx b/app/scripts/components/common/uswds/index.tsx index e5410b060..de6d276ee 100644 --- a/app/scripts/components/common/uswds/index.tsx +++ b/app/scripts/components/common/uswds/index.tsx @@ -2,3 +2,4 @@ export { USWDSAlert } from './alert'; export { USWDSButtonGroup, USWDSButton } from './button'; export { USWDSLink } from './link'; export { USWDSBanner, USWDSBannerContent } from './banner'; +export { USWDSTextInput, USWDSTextInputMask } from './input'; diff --git a/app/scripts/components/common/uswds/input.tsx b/app/scripts/components/common/uswds/input.tsx new file mode 100644 index 000000000..3f6385422 --- /dev/null +++ b/app/scripts/components/common/uswds/input.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { TextInput, TextInputMask } from '@trussworks/react-uswds'; + +export function USWDSTextInput(props) { + return ; +} + +export function USWDSTextInputMask(props) { + return ; +} diff --git a/app/scripts/components/exploration/atoms/hooks.ts b/app/scripts/components/exploration/atoms/hooks.ts index f6ff462c1..8cacd066b 100644 --- a/app/scripts/components/exploration/atoms/hooks.ts +++ b/app/scripts/components/exploration/atoms/hooks.ts @@ -170,3 +170,15 @@ export const useTimelineDatasetAnalysis = ( ) ); }; + +export function useTimelineDatasetColormapScale( + datasetAtom: PrimitiveAtom +) { + const colorMapScaleAtom = useMemo(() => { + return focusAtom(datasetAtom, (optic) => + optic.prop('settings').prop('scale') + ); + }, [datasetAtom]); + + return useAtom(colorMapScaleAtom); +} diff --git a/app/scripts/components/exploration/components/datasets/colorRangeSlider/color-range-slider.scss b/app/scripts/components/exploration/components/datasets/colorRangeSlider/color-range-slider.scss new file mode 100644 index 000000000..87b38814b --- /dev/null +++ b/app/scripts/components/exploration/components/datasets/colorRangeSlider/color-range-slider.scss @@ -0,0 +1,71 @@ +/* Removing the default appearance */ +.thumb, +.thumb::-webkit-slider-thumb { + touch-action: 'none'; + + -webkit-appearance: none; + -webkit-tap-highlight-color: transparent; +} + +.thumb { + pointer-events: none; +} +/* For Chrome browsers */ +.thumb::-webkit-slider-thumb { + -webkit-appearance: none; + pointer-events: all; + width: 20px; + height: 20px; + background-color: #fff; + border-radius: 50%; + border: 2px solid #1565ef; + border-width: 1px; + box-shadow: 0 0 0 1px #c6c6c6; + cursor: pointer; +} + +/* For Firefox browsers */ +.thumb::-moz-range-thumb { + -webkit-appearance: none; + pointer-events: all; + width: 20px; + height: 20px; + background-color: #fff; + border-radius: 50%; + border: 2px solid #1565ef; + border-width: 1px; + box-shadow: 0 0 0 1px #c6c6c6; + cursor: pointer; +} + +input::-webkit-outer-spin-button, +input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +/* Firefox */ +input[type='number'] { + -moz-appearance: textfield; +} + +.tooltiptext { + background-color: #2c3e50; +} + +.tooltipmin::after { + left: 10px; +} + +.tooltipmax::after { + right: 10px; +} + +.tooltiptext::after { + content: ''; + position: absolute; + top: 95%; + border-width: 10px 10px 0px 10px; + border-style: solid; + border-color: #2c3e50 transparent; +} diff --git a/app/scripts/components/exploration/components/datasets/colorRangeSlider/colorRangeSlider.spec.tsx b/app/scripts/components/exploration/components/datasets/colorRangeSlider/colorRangeSlider.spec.tsx new file mode 100644 index 000000000..4ab56189d --- /dev/null +++ b/app/scripts/components/exploration/components/datasets/colorRangeSlider/colorRangeSlider.spec.tsx @@ -0,0 +1,99 @@ +import React from 'react'; +import '@testing-library/jest-dom'; + +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; + +import { ColorRangeSlider } from './index'; + +describe('colorRangeSlider should render with correct content.', () => { + const colorRangeData = { + min: 0, + max: 0.3, + colorMapScale: { max: 0.263, min: 0.131 }, + setColorMapScale: jest.fn() + }; + + beforeEach(() => { + render(); + }); + afterEach(() => { + jest.clearAllMocks(); + }); + + it('Renders correct content', async () => { + const maxSlider = screen.getByTestId('maxSlider'); + const minSlider = screen.getByTestId('minSlider'); + const maxInput = screen.getByTestId('maxInput'); + const minInput = screen.getByTestId('minInput'); + + expect(screen.getByText('Rescale')).toBeInTheDocument(); + await waitFor(() => { + expect(minSlider).toHaveValue('0.131'); + expect(maxSlider).toHaveValue('0.263'); + expect(minInput).toHaveValue(0.131); + expect(maxInput).toHaveValue(0.263); + }); + }); + + it('Shows error when number entered above max', () => { + const minInput = screen.getByTestId('minInput'); + + fireEvent.change(minInput, { target: { value: 0.29 } }); + expect( + screen.getByText('Please enter a value less than 0.263') + ).toBeInTheDocument(); + }); + it('Shows error when number entered below min', () => { + const maxInput = screen.getByTestId('maxInput'); + + fireEvent.change(maxInput, { target: { value: -0.1 } }); + expect( + screen.getByText('Please enter a value larger than 0.131') + ).toBeInTheDocument(); + }); + it('Shows error when number entered outside of min and max', () => { + const minInput = screen.getByTestId('minInput'); + + fireEvent.change(minInput, { target: { value: -0.1 } }); + expect( + screen.getByText('Please enter a value between 0 and 0.3') + ).toBeInTheDocument(); + const maxInput = screen.getByTestId('maxInput'); + + fireEvent.change(maxInput, { target: { value: 0.4 } }); + expect( + screen.getByText('Please enter a value between 0 and 0.3') + ).toBeInTheDocument(); + }); +}); + +describe('colorRangeSlider should render with correct display content', () => { + const colorRangeData = { + min: -0.0000003, + max: 0.0000003, + colorMapScale: { max: 0.000000263, min: -0.000000131 }, + setColorMapScale: jest.fn() + }; + + beforeEach(() => { + render(); + }); + afterEach(() => { + jest.clearAllMocks(); + }); + + it('Renders correct content', async () => { + const maxSlider = screen.getByTestId('maxSlider'); + const minSlider = screen.getByTestId('minSlider'); + const maxInput = screen.getByTestId('maxInput'); + const minInput = screen.getByTestId('minInput'); + + expect(screen.getByText('Rescale')).toBeInTheDocument(); + await waitFor(() => { + expect(minSlider).toHaveValue('-1.31e-7'); + expect(maxSlider).toHaveValue('2.63e-7'); + expect(minInput).toHaveValue(-1.31e-7); + expect(maxInput).toHaveValue(2.63e-7); + }); + }); +}); diff --git a/app/scripts/components/exploration/components/datasets/colorRangeSlider/index.tsx b/app/scripts/components/exploration/components/datasets/colorRangeSlider/index.tsx new file mode 100644 index 000000000..f706be6f1 --- /dev/null +++ b/app/scripts/components/exploration/components/datasets/colorRangeSlider/index.tsx @@ -0,0 +1,329 @@ +import React, { useCallback, useEffect, useState, useRef } from 'react'; + +import './color-range-slider.scss'; +import { + sterlizeNumber, + calculateStep, + rangeCalculation, + displayErrorMessage, + textInputClasses, + thumbPosition, + tooltiptextClasses +} from './utils'; + +import { colorMapScale } from '$components/exploration/types.d.ts'; +import { USWDSTextInput } from '$components/common/uswds'; + +interface ColorrangeRangeSlideProps { + // Absolute minimum of color range + min: number; + + // Absolute maximum of color range + max: number; + + // Previously selected minimum and maximum of colorRangeScale + colorMapScale: colorMapScale | undefined; + + // Update colorRangeScale + setColorMapScale: (colorMapScale: colorMapScale) => void; +} + +export function ColorRangeSlider({ + min, + max, + colorMapScale, + setColorMapScale +}: ColorrangeRangeSlideProps) { + const setDefaultMin = colorMapScale?.min ? colorMapScale.min : min; + const setDefaultMax = colorMapScale?.max ? colorMapScale.max : max; + + const [minVal, setMinVal] = useState(setDefaultMin); + const [maxVal, setMaxVal] = useState(setDefaultMax); + const minValRef = useRef({ actual: setDefaultMin, display: setDefaultMin }); + const maxValRef = useRef({ actual: setDefaultMax, display: setDefaultMax }); + const [maxIsHovering, setMaxIsHovering] = useState(false); + const [minIsHovering, setMinIsHovering] = useState(false); + const digitCount = useRef(1); + const range = useRef(null); + + const [fieldFocused, setFieldFocused] = useState(false); + const [inputError, setInputError] = useState({ + min: false, + max: false, + largerThanMax: false, + lessThanMin: false + }); + + // Convert to percentage + const getPercent = useCallback( + (value) => ((value - min) * 100) / (max - min), + [min, max] + ); + + const resetErrorOnSlide = (value, slider) => { + if (value > min || value < max) { + slider === 'max' + ? setInputError({ ...inputError, max: false, lessThanMin: false }) + : setInputError({ ...inputError, min: false, largerThanMax: false }); + } + }; + + const minMaxBuffer = calculateStep(max, min, digitCount); + + useEffect(() => { + let maxValPrevious; + let minValPrevious; + //checking that there are no current errors with inputs + if (Object.values(inputError).every((error) => !error)) { + //set the filled range bar on initial load + if ( + colorMapScale && + maxVal != maxValPrevious && + minVal != minValPrevious + ) { + const minPercent = getPercent(minValRef.current.actual); + const maxPercent = getPercent(maxValRef.current.actual); + + rangeCalculation(maxPercent, minPercent, range); + + if (range.current) + range.current.style.left = `calc(${minPercent}% + ${ + 10 - minPercent * 0.2 + }px)`; + } else { + //set the filled range bar if change to max slider + if (maxVal != maxValPrevious) { + maxValPrevious = maxVal; + const minPercent = getPercent(minValRef.current.actual); + const maxPercent = getPercent(maxVal); + rangeCalculation(maxPercent, minPercent, range); + } + //set the filled range bar if change to min slider + if (minVal != minValPrevious) { + minValPrevious = minVal; + const minPercent = getPercent(minVal); + const maxPercent = getPercent(maxValRef.current.actual); + rangeCalculation(maxPercent, minPercent, range); + + if (range.current) + range.current.style.left = `calc(${minPercent}% + ${ + 10 - minPercent * 0.2 + }px)`; + } + } + } + + const maxCharacterCount = maxVal.toString().length + 1; + const minCharacterCount = minVal.toString().length + 1; + + let updatedDisplay; + maxCharacterCount <= 7 + ? (updatedDisplay = maxVal) + : (updatedDisplay = maxVal.toExponential()); + maxValRef.current.display = updatedDisplay; + + minCharacterCount <= 7 + ? (updatedDisplay = minVal) + : (updatedDisplay = minVal.toExponential()); + minValRef.current.display = updatedDisplay; + // determining if there is an initial colorMapeScale or if it is the default min and max + if ( + !colorMapScale || + (colorMapScale.max == max && colorMapScale.min == min) + ) { + setColorMapScale({ min: minVal, max: maxVal }); + } else + setColorMapScale({ + min: Number(minValRef.current.actual), + max: Number(maxValRef.current.actual) + }); /* eslint-disable-next-line react-hooks/exhaustive-deps */ + }, [maxVal, minVal, getPercent, setColorMapScale, inputError, max, min]); + + return ( +
+
+ +
+ {minIsHovering && ( +
+ + {sterlizeNumber(minValRef.current.actual, digitCount)} + +
+ )} + + setMinIsHovering(true)} + onMouseLeave={() => setMinIsHovering(false)} + onFocus={() => setFieldFocused(true)} + onBlur={() => { + setFieldFocused(false); + }} + onChange={(event) => { + if (event.target.value != undefined) { + minValRef.current.actual = + event.target.value === '' ? 0 : event.target.value; + const value = Number(event.target.value); + + if (value > maxVal - minMaxBuffer) + return setInputError({ ...inputError, largerThanMax: true }); + if (value < min || value > max) { + return setInputError({ ...inputError, min: true }); + } else { + setInputError({ + ...inputError, + min: false, + largerThanMax: false + }); + const calculatedVal = Math.min(value, maxVal - minMaxBuffer); + setMinVal(calculatedVal); + } + } + }} + /> +
+
+ { + const value = Math.min( + Number(event.target.value), + maxVal - minMaxBuffer + ); + resetErrorOnSlide(value, 'min'); + setMinVal(value); + minValRef.current.actual = value; + }} + className={`thumb ${thumbPosition} z-index-30`} + style={{ + zIndex: minVal >= max - 10 * minMaxBuffer ? '500' : '300' + }} + /> + { + const value = Math.max( + Number(event.target.value), + minVal + minMaxBuffer + ); + resetErrorOnSlide(value, 'max'); + setMaxVal(value); + maxValRef.current.actual = value; + }} + className={`thumb ${thumbPosition} z-400`} + style={{ + zIndex: minVal <= max - 10 * minMaxBuffer ? '500' : '400' + }} + /> +
+
+
+ {/* Show divergent map middle point */} + {min < 0 ? ( +
+ ) : null} +
+
+
+
+ {maxIsHovering && ( +
+ + {sterlizeNumber(maxValRef.current.actual, digitCount)} + +
+ )} + setMaxIsHovering(true)} + onMouseLeave={() => setMaxIsHovering(false)} + onFocus={() => setFieldFocused(true)} + onBlur={() => { + setFieldFocused(false); + }} + onChange={(event) => { + if (event.target.value != undefined) { + maxValRef.current.actual = + event.target.value === '' ? 0 : event.target.value; + const value = Number(event.target.value); + + if (value < minVal + minMaxBuffer) + return setInputError({ ...inputError, lessThanMin: true }); + + if (value < min || value > max) { + return setInputError({ ...inputError, max: true }); + } else { + //unsetting error + setInputError({ + ...inputError, + max: false, + lessThanMin: false + }); + const calculatedVal = Math.max(value, minVal + minMaxBuffer); + setMaxVal(calculatedVal); + } + } + }} + /> +
+ + + {displayErrorMessage(inputError, min, max, maxValRef, minValRef)} +
+ ); +} diff --git a/app/scripts/components/exploration/components/datasets/colorRangeSlider/utils.tsx b/app/scripts/components/exploration/components/datasets/colorRangeSlider/utils.tsx new file mode 100644 index 000000000..148f92ed3 --- /dev/null +++ b/app/scripts/components/exploration/components/datasets/colorRangeSlider/utils.tsx @@ -0,0 +1,81 @@ +import React from 'react'; + +export const sterlizeNumber = (number, digitCount) => { + const fixedNum = Number(number).toFixed(digitCount.current); + return fixedNum; +}; + +export const textInputClasses = + 'flex-1 radius-md height-3 font-size-3xs width-7 border-2px tooltip'; +export const thumbPosition = `position-absolute pointer-events width-card height-0 outline-0`; +export const tooltiptextClasses = + 'text-no-wrap text-white text-center radius-lg padding-x-105 padding-y-05 position-absolute z-1 bottom-205'; +export const calculateStep = (max, min, digitCount) => { + const numericDistance = max - min; + if (numericDistance >= 100) { + return 0.001; + } else { + let decimalPoints; + if (numericDistance.toString().includes('.')) { + decimalPoints = numericDistance.toString().split('.')[1].length || 0; + } else { + decimalPoints = numericDistance.toString().length || 0; + } + digitCount.current = decimalPoints + 2; + + //adding a default buffer for granular control + return Math.pow(10, (decimalPoints + 2) * -1); + } +}; + +//Calculate the range + +export const rangeCalculation = (maxPercent, minPercent, range) => { + const thumbWidth = 20; + if (range.current) { + range.current.style.width = `calc(${ + maxPercent - minPercent >= 100 ? 100 : maxPercent - minPercent + }% - ${(thumbWidth - minPercent * 0.2) * (maxPercent / 100)}px )`; + } + return; +}; + +export const displayErrorMessage = ( + inputError, + min, + max, + maxValRef, + minValRef +) => { + // error message for min input that is outside min max of color map + + if (inputError.max || inputError.min) { + return ( +

+ Please enter a value between {min} and {max} +

+ ); + } + { + /* error message for max input that is less than current min */ + } + + if (inputError.largerThanMax) { + return ( +

+ Please enter a value less than {maxValRef.current.actual} +

+ ); + } + { + /* error message for min input that is larger than current max */ + } + + if (inputError.lessThanMin) { + return ( +

+ Please enter a value larger than {minValRef.current.actual} +

+ ); + } +}; diff --git a/app/scripts/components/exploration/components/datasets/colormap-options.tsx b/app/scripts/components/exploration/components/datasets/colormap-options.tsx index a6ce07e21..cb76ccb22 100644 --- a/app/scripts/components/exploration/components/datasets/colormap-options.tsx +++ b/app/scripts/components/exploration/components/datasets/colormap-options.tsx @@ -1,23 +1,38 @@ import React, { useEffect, useState } from 'react'; -import { Icon } from "@trussworks/react-uswds"; +import { Icon } from '@trussworks/react-uswds'; import { CollecticonDrop } from '@devseed-ui/collecticons'; -import { sequentialColorMaps, divergingColorMaps, restColorMaps } from './colorMaps'; +import { + sequentialColorMaps, + divergingColorMaps, + restColorMaps +} from './colorMaps'; import './colormap-options.scss'; - +import { ColorRangeSlider } from './colorRangeSlider/index'; +import { colorMapScale } from '$components/exploration/types.d.ts'; export const DEFAULT_COLORMAP = 'viridis'; const CURATED_SEQUENTIAL_COLORMAPS = [ - 'viridis', 'plasma', 'inferno', 'magma', 'cividis', - 'purples', 'blues', 'reds', 'greens', 'oranges', - 'ylgnbu', 'ylgn', 'gnbu' + 'viridis', + 'plasma', + 'inferno', + 'magma', + 'cividis', + 'purples', + 'blues', + 'reds', + 'greens', + 'oranges', + 'ylgnbu', + 'ylgn', + 'gnbu' ]; -const CURATED_DIVERGING_COLORMAPS = [ - 'rdbu', 'rdylbu', 'bwr', 'coolwarm' -]; +const CURATED_DIVERGING_COLORMAPS = ['rdbu', 'rdylbu', 'bwr', 'coolwarm']; -export const classifyColormap = (colormapName: string): 'sequential' | 'diverging' | 'rest' | 'unknown' => { +export const classifyColormap = ( + colormapName: string +): 'sequential' | 'diverging' | 'rest' | 'unknown' => { const baseName = normalizeColorMap(colormapName); if (sequentialColorMaps[baseName]) { @@ -33,9 +48,16 @@ export const classifyColormap = (colormapName: string): 'sequential' | 'divergin interface ColormapOptionsProps { colorMap: string | undefined; setColorMap: (colorMap: string) => void; + min: number; + max: number; + setColorMapScale: (colorMapScale: colorMapScale) => void; + colorMapScale: colorMapScale | undefined; } -export const getColormapColors = (colormapName: string, isReversed: boolean): string[] => { +export const getColormapColors = ( + colormapName: string, + isReversed: boolean +): string[] => { const baseName = normalizeColorMap(colormapName); const colormapData = sequentialColorMaps[baseName] || @@ -49,10 +71,19 @@ export const getColormapColors = (colormapName: string, isReversed: boolean): st return `rgba(${r}, ${g}, ${b}, ${a})`; }); - return isReversed ? colors.reduceRight((acc, color) => [...acc, color], []) : colors; + return isReversed + ? colors.reduceRight((acc, color) => [...acc, color], []) + : colors; }; -export function ColormapOptions({ colorMap = DEFAULT_COLORMAP, setColorMap}: ColormapOptionsProps) { +export function ColormapOptions({ + colorMap = DEFAULT_COLORMAP, + min, + max, + setColorMap, + setColorMapScale, + colorMapScale +}: ColormapOptionsProps) { const initialIsReversed = colorMap.endsWith('_r'); const initialColorMap = normalizeColorMap(colorMap); @@ -63,9 +94,15 @@ export function ColormapOptions({ colorMap = DEFAULT_COLORMAP, setColorMap}: Col const [customColorMap, setCustomColorMap] = useState(null); useEffect(() => { - if (colormapType === 'sequential' && !CURATED_SEQUENTIAL_COLORMAPS.includes(selectedColorMap)) { + if ( + colormapType === 'sequential' && + !CURATED_SEQUENTIAL_COLORMAPS.includes(selectedColorMap) + ) { setCustomColorMap(selectedColorMap); - } else if (colormapType === 'diverging' && !CURATED_DIVERGING_COLORMAPS.includes(selectedColorMap)) { + } else if ( + colormapType === 'diverging' && + !CURATED_DIVERGING_COLORMAPS.includes(selectedColorMap) + ) { setCustomColorMap(selectedColorMap); } }, [selectedColorMap, colormapType]); @@ -74,15 +111,25 @@ export function ColormapOptions({ colorMap = DEFAULT_COLORMAP, setColorMap}: Col if (colormapType === 'sequential') { if (customColorMap) { - availableColormaps = [{ name: customColorMap }, ...CURATED_SEQUENTIAL_COLORMAPS.map(name => ({ name }))]; + availableColormaps = [ + { name: customColorMap }, + ...CURATED_SEQUENTIAL_COLORMAPS.map((name) => ({ name })) + ]; } else { - availableColormaps = CURATED_SEQUENTIAL_COLORMAPS.map(name => ({ name })); + availableColormaps = CURATED_SEQUENTIAL_COLORMAPS.map((name) => ({ + name + })); } } else if (colormapType === 'diverging') { if (customColorMap) { - availableColormaps = [{ name: customColorMap }, ...CURATED_DIVERGING_COLORMAPS.map(name => ({ name }))]; + availableColormaps = [ + { name: customColorMap }, + ...CURATED_DIVERGING_COLORMAPS.map((name) => ({ name })) + ]; } else { - availableColormaps = CURATED_DIVERGING_COLORMAPS.map(name => ({ name })); + availableColormaps = CURATED_DIVERGING_COLORMAPS.map((name) => ({ + name + })); } } else if (colormapType === 'rest') { availableColormaps = [{ name: selectedColorMap }]; @@ -105,17 +152,35 @@ export function ColormapOptions({ colorMap = DEFAULT_COLORMAP, setColorMap}: Col return (
-
Colormap options
+
+ Colormap options +
-
-
- +
+ +
+ {isReversed ? ( ) : ( )} - +
@@ -125,13 +190,21 @@ export function ColormapOptions({ colorMap = DEFAULT_COLORMAP, setColorMap}: Col return (
handleColorMapSelect(name.toLowerCase())} >