From 5965afc040122673da96640d8ea57f015be87dcc Mon Sep 17 00:00:00 2001 From: snmln Date: Thu, 21 Nov 2024 16:21:37 -0500 Subject: [PATCH 1/9] creating util folder --- .../colorRangeSlider/color-range-slider.scss | 71 ++++ .../datasets/colorRangeSlider/index.tsx | 326 ++++++++++++++++++ .../datasets/colorRangeSlider/utils.tsx | 81 +++++ 3 files changed, 478 insertions(+) create mode 100644 app/scripts/components/exploration/components/datasets/colorRangeSlider/color-range-slider.scss create mode 100644 app/scripts/components/exploration/components/datasets/colorRangeSlider/index.tsx create mode 100644 app/scripts/components/exploration/components/datasets/colorRangeSlider/utils.tsx 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/index.tsx b/app/scripts/components/exploration/components/datasets/colorRangeSlider/index.tsx new file mode 100644 index 000000000..2b1f1fb28 --- /dev/null +++ b/app/scripts/components/exploration/components/datasets/colorRangeSlider/index.tsx @@ -0,0 +1,326 @@ +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: '' }); + const maxValRef = useRef({ actual: setDefaultMax, display: '' }); + 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 <= 6 + ? (updatedDisplay = maxVal) + : (updatedDisplay = maxVal.toExponential()); + maxValRef.current.display = updatedDisplay; + + minCharacterCount <= 6 + ? (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} +

+ ); + } +}; From 33f019b1d559c8c56420f600a7338d661548e9d4 Mon Sep 17 00:00:00 2001 From: snmln Date: Thu, 21 Nov 2024 17:02:12 -0500 Subject: [PATCH 2/9] creating initial tests --- .../colorRangeSlider.spec.tsx | 70 +++++++++++++++++++ .../datasets/colorRangeSlider/index.tsx | 5 +- 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 app/scripts/components/exploration/components/datasets/colorRangeSlider/colorRangeSlider.spec.tsx 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..738cf4021 --- /dev/null +++ b/app/scripts/components/exploration/components/datasets/colorRangeSlider/colorRangeSlider.spec.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import '@testing-library/jest-dom'; + +import { fireEvent, prettyDOM, render, screen } 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', () => { + + 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(); + expect(minSlider).toHaveValue('0.131'); + expect(minInput).toHaveValue('0.131'); + + expect(maxSlider).toHaveValue('0.263'); + expect(maxInput).toHaveValue('0.263'); + console.log(prettyDOM()); + + }); + + 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(); + }); +}); diff --git a/app/scripts/components/exploration/components/datasets/colorRangeSlider/index.tsx b/app/scripts/components/exploration/components/datasets/colorRangeSlider/index.tsx index 2b1f1fb28..dad10900e 100644 --- a/app/scripts/components/exploration/components/datasets/colorRangeSlider/index.tsx +++ b/app/scripts/components/exploration/components/datasets/colorRangeSlider/index.tsx @@ -34,7 +34,6 @@ export function ColorRangeSlider({ colorMapScale, setColorMapScale }: ColorrangeRangeSlideProps) { - const setDefaultMin = colorMapScale?.min ? colorMapScale.min : min; const setDefaultMax = colorMapScale?.max ? colorMapScale.max : max; @@ -164,6 +163,7 @@ export function ColorRangeSlider({ ? 'border-secondary-vivid text-secondary-vivid' : ' border-base-light' }`} + data-testid='minInput' value={ fieldFocused ? minValRef.current.actual @@ -206,6 +206,7 @@ export function ColorRangeSlider({
Date: Fri, 22 Nov 2024 10:40:51 -0500 Subject: [PATCH 3/9] updating tests --- .../colorRangeSlider.spec.tsx | 51 +++++++++++++++---- .../datasets/colorRangeSlider/index.tsx | 4 +- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/app/scripts/components/exploration/components/datasets/colorRangeSlider/colorRangeSlider.spec.tsx b/app/scripts/components/exploration/components/datasets/colorRangeSlider/colorRangeSlider.spec.tsx index 738cf4021..85e5517bc 100644 --- a/app/scripts/components/exploration/components/datasets/colorRangeSlider/colorRangeSlider.spec.tsx +++ b/app/scripts/components/exploration/components/datasets/colorRangeSlider/colorRangeSlider.spec.tsx @@ -1,7 +1,7 @@ import React from 'react'; import '@testing-library/jest-dom'; -import { fireEvent, prettyDOM, render, screen } from '@testing-library/react'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { ColorRangeSlider } from './index'; @@ -20,21 +20,19 @@ describe('colorRangeSlider should render with correct content.', () => { jest.clearAllMocks(); }); - it('Renders correct content', () => { - + 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(); - expect(minSlider).toHaveValue('0.131'); - expect(minInput).toHaveValue('0.131'); - - expect(maxSlider).toHaveValue('0.263'); - expect(maxInput).toHaveValue('0.263'); - console.log(prettyDOM()); - + 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', () => { @@ -68,3 +66,36 @@ describe('colorRangeSlider should render with correct content.', () => { ).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'); + + screen.debug(); + + 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 index dad10900e..7b02652a7 100644 --- a/app/scripts/components/exploration/components/datasets/colorRangeSlider/index.tsx +++ b/app/scripts/components/exploration/components/datasets/colorRangeSlider/index.tsx @@ -39,8 +39,8 @@ export function ColorRangeSlider({ const [minVal, setMinVal] = useState(setDefaultMin); const [maxVal, setMaxVal] = useState(setDefaultMax); - const minValRef = useRef({ actual: setDefaultMin, display: '' }); - const maxValRef = useRef({ actual: setDefaultMax, display: '' }); + 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); From 580df2fb5505a2b8b6a9b2427e4058c74ab0a1e7 Mon Sep 17 00:00:00 2001 From: snmln Date: Fri, 22 Nov 2024 13:51:13 -0500 Subject: [PATCH 4/9] small change to force github changes --- .../datasets/colorRangeSlider/colorRangeSlider.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/components/exploration/components/datasets/colorRangeSlider/colorRangeSlider.spec.tsx b/app/scripts/components/exploration/components/datasets/colorRangeSlider/colorRangeSlider.spec.tsx index 85e5517bc..10b9152ba 100644 --- a/app/scripts/components/exploration/components/datasets/colorRangeSlider/colorRangeSlider.spec.tsx +++ b/app/scripts/components/exploration/components/datasets/colorRangeSlider/colorRangeSlider.spec.tsx @@ -67,7 +67,7 @@ describe('colorRangeSlider should render with correct content.', () => { }); }); -describe('colorRangeSlider should render with correct display content.', () => { +describe('colorRangeSlider should render with correct display content', () => { const colorRangeData = { min: -0.0000003, max: 0.0000003, From a3b002ef17d43bb63992994e44e60c297a8ea066 Mon Sep 17 00:00:00 2001 From: snmln Date: Wed, 27 Nov 2024 13:46:36 -0500 Subject: [PATCH 5/9] migrating changes from color-maps-range-slider #1190 --- .../style-generators/raster-timeseries.tsx | 4 ++- app/scripts/components/common/map/types.d.ts | 1 + app/scripts/components/common/uswds/index.tsx | 1 + app/scripts/components/common/uswds/input.tsx | 10 +++++++ .../components/exploration/atoms/hooks.ts | 12 ++++++++ .../components/datasets/colormap-options.tsx | 30 +++++++++++++++---- .../components/datasets/data-layer-card.tsx | 7 ++++- .../components/datasets/dataset-list-item.tsx | 28 +++++++++++++---- .../exploration/components/map/layer.tsx | 3 ++ .../components/exploration/types.d.ts.ts | 3 ++ 10 files changed, 85 insertions(+), 14 deletions(-) create mode 100644 app/scripts/components/common/uswds/input.tsx 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..41af4db4e 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 { 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/colormap-options.tsx b/app/scripts/components/exploration/components/datasets/colormap-options.tsx index a6ce07e21..e349906fe 100644 --- a/app/scripts/components/exploration/components/datasets/colormap-options.tsx +++ b/app/scripts/components/exploration/components/datasets/colormap-options.tsx @@ -1,10 +1,15 @@ import React, { useEffect, useState } from 'react'; 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 = [ @@ -105,11 +110,24 @@ export function ColormapOptions({ colorMap = DEFAULT_COLORMAP, setColorMap}: Col return (
-
Colormap options
+
+ Colormap options +
-
-
- +
+ +
+ {isReversed ? ( ) : ( diff --git a/app/scripts/components/exploration/components/datasets/data-layer-card.tsx b/app/scripts/components/exploration/components/datasets/data-layer-card.tsx index 825cd8eb4..346e4ceba 100644 --- a/app/scripts/components/exploration/components/datasets/data-layer-card.tsx +++ b/app/scripts/components/exploration/components/datasets/data-layer-card.tsx @@ -20,7 +20,10 @@ import { LayerCategoricalGraphic, LayerGradientColormapGraphic } from '$components/common/map/layer-legend'; -import { TimelineDataset } from '$components/exploration/types.d.ts'; +import { + TimelineDataset, + colorMapScale +} from '$components/exploration/types.d.ts'; import { CollecticonDatasetLayers } from '$components/common/icons/dataset-layers'; import { ParentDatasetTitle } from '$components/common/catalog/catalog-content'; import { checkEnvFlag } from '$utils/utils'; @@ -35,6 +38,8 @@ interface CardProps { setVisible: any; colorMap: string | undefined; setColorMap: (colorMap: string) => void; + colorMapScale: colorMapScale | undefined; + setColorMapScale: (colorMapScale: colorMapScale) => void; onClickLayerInfo: () => void; datasetLegend: LayerLegendCategorical | LayerLegendGradient | undefined; } diff --git a/app/scripts/components/exploration/components/datasets/dataset-list-item.tsx b/app/scripts/components/exploration/components/datasets/dataset-list-item.tsx index 18b2a0ea8..3e1ca85f9 100644 --- a/app/scripts/components/exploration/components/datasets/dataset-list-item.tsx +++ b/app/scripts/components/exploration/components/datasets/dataset-list-item.tsx @@ -105,7 +105,10 @@ export function DatasetListItem(props: DatasetListItemProps) { const [isVisible, setVisible] = useTimelineDatasetVisibility(datasetAtom); const [colorMap, setColorMap] = useTimelineDatasetColormap(datasetAtom); - const [modalLayerInfo, setModalLayerInfo] = React.useState(); + const [colorMapScale, setColorMapScale] = + useTimelineDatasetColormapScale(datasetAtom); + const [modalLayerInfo, setModalLayerInfo] = + React.useState(); const [, setSetting] = useTimelineDatasetSettings(datasetAtom); const queryClient = useQueryClient(); @@ -121,7 +124,10 @@ export function DatasetListItem(props: DatasetListItemProps) { }, [queryClient, datasetId]); const onClickLayerInfo = useCallback(() => { - const parentInfoDesc = findDatasetAttribute({datasetId: dataset.data.parentDataset.id, attr: 'infoDescription'}); + const parentInfoDesc = findDatasetAttribute({ + datasetId: dataset.data.parentDataset.id, + attr: 'infoDescription' + }); const data: LayerInfoModalData = { name: dataset.data.name, description: dataset.data.description, @@ -188,7 +194,6 @@ export function DatasetListItem(props: DatasetListItemProps) { [dataset] ); - const onDragging = (e) => { controls.start(e); }; @@ -210,8 +215,19 @@ export function DatasetListItem(props: DatasetListItemProps) { -
- +
+
{modalLayerInfo && ( ); -} \ No newline at end of file +} diff --git a/app/scripts/components/exploration/components/map/layer.tsx b/app/scripts/components/exploration/components/map/layer.tsx index 902e0a6c3..3e063b530 100644 --- a/app/scripts/components/exploration/components/map/layer.tsx +++ b/app/scripts/components/exploration/components/map/layer.tsx @@ -77,6 +77,7 @@ export function Layer(props: LayerProps) { opacity={opacity} onStatusChange={onStatusChange} colorMap={colorMap} + reScale={scale} envApiStacEndpoint={envApiStacEndpoint} envApiRasterEndpoint={envApiRasterEndpoint} /> @@ -95,6 +96,7 @@ export function Layer(props: LayerProps) { opacity={opacity} onStatusChange={onStatusChange} colorMap={colorMap} + reScale={scale} envApiStacEndpoint={envApiStacEndpoint} envApiRasterEndpoint={envApiRasterEndpoint} /> @@ -114,6 +116,7 @@ export function Layer(props: LayerProps) { opacity={opacity} onStatusChange={onStatusChange} colorMap={colorMap} + reScale={scale} envApiStacEndpoint={envApiStacEndpoint} envApiRasterEndpoint={envApiRasterEndpoint} /> diff --git a/app/scripts/components/exploration/types.d.ts.ts b/app/scripts/components/exploration/types.d.ts.ts index b6daad9f4..c322ab651 100644 --- a/app/scripts/components/exploration/types.d.ts.ts +++ b/app/scripts/components/exploration/types.d.ts.ts @@ -105,6 +105,9 @@ export interface DatasetSettings { analysisMetrics?: DataMetric[]; // Active colormap of the layer. colorMap?: string; + // Active colormap scale. + + scale?: colorMapScale; } // Any sort of meta information the dataset like: From 9f7b1b6b09ed9e0d2c35a3e8d4a37bb4ab0c67b1 Mon Sep 17 00:00:00 2001 From: snmln Date: Wed, 27 Nov 2024 14:04:55 -0500 Subject: [PATCH 6/9] adding missing type --- app/scripts/components/exploration/types.d.ts.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/scripts/components/exploration/types.d.ts.ts b/app/scripts/components/exploration/types.d.ts.ts index c322ab651..aae1b3168 100644 --- a/app/scripts/components/exploration/types.d.ts.ts +++ b/app/scripts/components/exploration/types.d.ts.ts @@ -95,7 +95,10 @@ export interface DatasetData extends EnhancedDatasetLayer { timeDensity: TimeDensity; domain: Date[]; } - +export interface colorMapScale { + min: number; + max: number; +} export interface DatasetSettings { // Whether or not the layer should be shown on the map. isVisible?: boolean; From eac65d71c66e272da45cff8cc717968b7ed4fbd0 Mon Sep 17 00:00:00 2001 From: snmln Date: Wed, 27 Nov 2024 14:11:10 -0500 Subject: [PATCH 7/9] coorecting rebase errors: continuation migrating changes from color-maps-range-slider #1190 --- .../colorRangeSlider.spec.tsx | 6 +- .../components/datasets/colormap-options.tsx | 96 ++++++++++---- .../components/datasets/data-layer-card.tsx | 118 ++++++++++++------ .../components/datasets/dataset-list-item.tsx | 3 +- .../exploration/components/map/layer.tsx | 2 +- 5 files changed, 158 insertions(+), 67 deletions(-) diff --git a/app/scripts/components/exploration/components/datasets/colorRangeSlider/colorRangeSlider.spec.tsx b/app/scripts/components/exploration/components/datasets/colorRangeSlider/colorRangeSlider.spec.tsx index 10b9152ba..4ab56189d 100644 --- a/app/scripts/components/exploration/components/datasets/colorRangeSlider/colorRangeSlider.spec.tsx +++ b/app/scripts/components/exploration/components/datasets/colorRangeSlider/colorRangeSlider.spec.tsx @@ -88,12 +88,10 @@ describe('colorRangeSlider should render with correct display content', () => { const maxInput = screen.getByTestId('maxInput'); const minInput = screen.getByTestId('minInput'); - screen.debug(); - expect(screen.getByText('Rescale')).toBeInTheDocument(); await waitFor(() => { - expect(minSlider).toHaveValue("-1.31e-7"); - expect(maxSlider).toHaveValue("2.63e-7"); + 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/colormap-options.tsx b/app/scripts/components/exploration/components/datasets/colormap-options.tsx index e349906fe..cb76ccb22 100644 --- a/app/scripts/components/exploration/components/datasets/colormap-options.tsx +++ b/app/scripts/components/exploration/components/datasets/colormap-options.tsx @@ -1,5 +1,5 @@ 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, @@ -13,16 +13,26 @@ 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]) { @@ -38,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] || @@ -54,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); @@ -68,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]); @@ -79,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 }]; @@ -133,7 +175,12 @@ export function ColormapOptions({ colorMap = DEFAULT_COLORMAP, setColorMap}: Col ) : ( )} - +
@@ -143,13 +190,21 @@ export function ColormapOptions({ colorMap = DEFAULT_COLORMAP, setColorMap}: Col return (
handleColorMapSelect(name.toLowerCase())} >