Skip to content
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

[E & A] Implement colormap configurability #1117

Merged
merged 45 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
8bd2f18
Core logic for changing the color map
dzole0311 Aug 13, 2024
7618e24
Remove obsolete prop
dzole0311 Aug 15, 2024
b94e8df
Use renderParams from STAC
dzole0311 Aug 15, 2024
02c8e80
Update LayerLegend to handle cases without steps and only with colorm…
dzole0311 Aug 15, 2024
358eb15
Improve the move listener over the colormap
dzole0311 Aug 15, 2024
023f11f
Get rescale from STAC
dzole0311 Aug 15, 2024
20a717c
Add defaults
dzole0311 Aug 15, 2024
3836c4a
Fix types
dzole0311 Aug 15, 2024
d1eb323
Check if the colormap should be categorical based on the sourceParams
dzole0311 Aug 15, 2024
b2d24af
Get default from config if colormap is not present in the atom
dzole0311 Aug 16, 2024
d75d9e1
Fix reversed colormaps recognized as unknown
dzole0311 Aug 16, 2024
8b367b4
Styling tweaks
dzole0311 Aug 16, 2024
6595370
Use USWDS class names
dzole0311 Aug 16, 2024
edd1938
Few more USWDS classes
dzole0311 Aug 16, 2024
5df8580
More classes
dzole0311 Aug 16, 2024
384bf4a
Safelist the USWDS icon
dzole0311 Aug 16, 2024
5a1d5e8
Update colormap in mdx config
dzole0311 Aug 16, 2024
ab59663
Preselect colorMap
dzole0311 Aug 16, 2024
b25d1f6
Remove rescaling of color maps
dzole0311 Aug 16, 2024
4e19296
Define interpolator for Bwr
dzole0311 Aug 16, 2024
9192303
Obsolete code
dzole0311 Aug 16, 2024
b7cba4a
Get colorMaps from veda back-end, include other fixes
dzole0311 Aug 20, 2024
c41c543
Merge branch 'main' into 994-exploration-colormap
dzole0311 Aug 21, 2024
c6a5338
Add reset button, add icon
dzole0311 Aug 21, 2024
1a424df
Obsolete style
dzole0311 Aug 21, 2024
f53bc16
Add classNames
dzole0311 Aug 21, 2024
d5686c5
Replace few more classNames
dzole0311 Aug 21, 2024
0d16202
Merge branch 'main' into 994-exploration-colormap
dzole0311 Aug 22, 2024
8ae02d6
Don't show colormap unless its of type gradient
dzole0311 Aug 22, 2024
6905595
Add loading skeleton and improve validation
dzole0311 Aug 22, 2024
5858224
TS error
dzole0311 Aug 22, 2024
9c6b32a
Address few of the review comments
dzole0311 Aug 23, 2024
5bbcaf1
Make ES lint happy
dzole0311 Aug 23, 2024
4d27a26
Adjust LoadingSkeleton height
dzole0311 Aug 23, 2024
2caa0bf
Address leftover review comments
dzole0311 Aug 26, 2024
16922d0
Make TS happy
dzole0311 Aug 26, 2024
42bc091
Explain reasoning for the flattening of the rescale values in a comment
dzole0311 Aug 26, 2024
3f23711
Update docs for configuring colormaps in veda-ui
dzole0311 Aug 26, 2024
7ff3a00
Keep the rio-tiler reference
dzole0311 Aug 26, 2024
f2707b5
Return default value of colormap along with other settings
hanbyul-here Aug 27, 2024
2605410
Remove the Reset button and the Default label, fix TS errors
dzole0311 Aug 28, 2024
585191c
Fix linting errors
dzole0311 Aug 28, 2024
574e72b
Return default value of colormap along with other settings (#1128)
dzole0311 Aug 28, 2024
0e55cff
Update docs/content/frontmatter/layer.md
dzole0311 Aug 28, 2024
60559fd
Use const
dzole0311 Aug 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 46 additions & 5 deletions app/scripts/components/common/map/layer-legend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
WidgetItemHGroup
} from '$styles/panel';
import { LayerLegendCategorical, LayerLegendGradient } from '$types/veda';
import { divergingColorMaps, sequentialColorMaps } from '$components/exploration/components/datasets/colorMaps';

interface LayerLegendCommonProps {
id: string;
Expand Down Expand Up @@ -300,18 +301,22 @@ export function LayerCategoricalGraphic(props: LayerLegendCategorical) {
);
}

export function LayerGradientGraphic(props: LayerLegendGradient) {
export const LayerGradientGraphic = (props: LayerLegendGradient) => {
hanbyul-here marked this conversation as resolved.
Show resolved Hide resolved
const { stops, min, max, unit } = props;

const [hoverVal, setHoverVal] = useState(0);

const moveListener = useCallback(
(e) => {
const width = e.nativeEvent.target.clientWidth;
const target = e.nativeEvent.target;
const boundingRect = target.getBoundingClientRect();
const offsetX = e.nativeEvent.clientX - boundingRect.left;
const width = boundingRect.width;

const scale = scaleLinear()
.domain([0, width])
.range([Number(min), Number(max)]);
setHoverVal(scale(e.nativeEvent.layerX));

setHoverVal(Math.max(Number(min), Math.min(Number(max), scale(offsetX))));
},
[min, max]
);
Expand Down Expand Up @@ -340,4 +345,40 @@ export function LayerGradientGraphic(props: LayerLegendGradient) {
{unit?.label && <dd className='unit'>{unit.label}</dd>}
</LegendList>
);
}
};

export const LayerGradientColormapGraphic = (props: Omit<LayerLegendGradient, 'stops' | 'type'>) => {
const { colorMap, ...otherProps } = props;

const colormap = findColormapByName(colorMap ?? 'viridis');
dzole0311 marked this conversation as resolved.
Show resolved Hide resolved
if (!colormap) {
return null;
}

const stops = Object.values(colormap).map((value) => {
if (Array.isArray(value) && value.length === 4) {
return `rgba(${value.join(',')})`;
} else {
return `rgba(0, 0, 0, 1)`;
}
});

const processedStops = colormap.isReversed
? stops.reduceRight((acc, stop) => [...acc, stop], [])
: stops;
dzole0311 marked this conversation as resolved.
Show resolved Hide resolved

return <LayerGradientGraphic type='gradient' stops={processedStops} {...otherProps} />;
};

export const findColormapByName = (name: string) => {
const isReversed = name.toLowerCase().endsWith('_r');
const baseName = isReversed ? name.slice(0, -2).toLowerCase() : name.toLowerCase();

const colormap = sequentialColorMaps[baseName] ?? divergingColorMaps[baseName];

if (!colormap) {
return null;
}

return { ...colormap, isReversed };
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface RasterPaintLayerProps extends BaseGeneratorParams {
tileApiEndpoint?: string;
zoomExtent?: number[];
assetUrl: string;
colorMap?: string | undefined;
}

export function RasterPaintLayer(props: RasterPaintLayerProps) {
Expand All @@ -24,19 +25,24 @@ export function RasterPaintLayer(props: RasterPaintLayerProps) {
zoomExtent,
assetUrl,
hidden,
opacity
opacity,
colorMap
} = props;

const { updateStyle } = useMapStyle();
const [minZoom] = zoomExtent ?? [0, 20];
const generatorId = `zarr-timeseries-${id}`;

const updatedSourceParams = useMemo(() => {
return { ...sourceParams, ...colorMap && {colormap_name: colorMap}};
}, [sourceParams, colorMap]);

//
// Generate Mapbox GL layers and sources for raster timeseries
//
const haveSourceParamsChanged = useMemo(
() => JSON.stringify(sourceParams),
[sourceParams]
() => JSON.stringify(updatedSourceParams),
[updatedSourceParams]
);

const generatorParams = useGeneratorParams(props);
Expand All @@ -48,7 +54,7 @@ export function RasterPaintLayer(props: RasterPaintLayerProps) {
const tileParams = qs.stringify({
url: assetUrl,
time_slice: date,
...sourceParams
...updatedSourceParams,
});

const zarrSource: RasterSource = {
Expand All @@ -65,12 +71,13 @@ export function RasterPaintLayer(props: RasterPaintLayerProps) {
paint: {
'raster-opacity': hidden ? 0 : rasterOpacity,
'raster-opacity-transition': {
duration: 320
}
duration: 320,
},
},
minzoom: minZoom,
metadata: {
layerOrderPosition: 'raster'
layerOrderPosition: 'raster',
colorMapVersion: colorMap,
}
};

Expand All @@ -96,7 +103,8 @@ export function RasterPaintLayer(props: RasterPaintLayerProps) {
minZoom,
tileApiEndpoint,
haveSourceParamsChanged,
generatorParams
generatorParams,
colorMap
// generatorParams includes hidden and opacity
// hidden,
// opacity,
Expand All @@ -119,4 +127,4 @@ export function RasterPaintLayer(props: RasterPaintLayerProps) {
}, [updateStyle, generatorId]);

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ export function RasterTimeseries(props: RasterTimeseriesProps) {
hidden,
opacity,
stacApiEndpoint,
tileApiEndpoint
tileApiEndpoint,
colorMap,
} = props;

const { current: mapInstance } = useMaps();
Expand Down Expand Up @@ -259,7 +260,7 @@ export function RasterTimeseries(props: RasterTimeseriesProps) {
LOG && console.log('Payload', payload);
LOG && console.groupEnd();
/* eslint-enable no-console */

let responseData;

try {
Expand All @@ -271,19 +272,19 @@ export function RasterTimeseries(props: RasterTimeseriesProps) {
const mosaicUrl = responseData.links[1].href;
setMosaicUrl(mosaicUrl.replace('/{tileMatrixSetId}', '/WebMercatorQuad'));
} catch (error) {
// @NOTE: conditional logic TO BE REMOVED once new BE endpoints have moved to prod... Fallback on old request url if new endpoints error with nonexistance...
// @NOTE: conditional logic TO BE REMOVED once new BE endpoints have moved to prod... Fallback on old request url if new endpoints error with nonexistance...
if (error.request) {
// The request was made but no response was received
responseData = await requestQuickCache<any>({
url: `${tileApiEndpointToUse}/mosaic/register`, // @NOTE: This will fail anyways with "staging-raster.delta-backend.com" because its already deprecated...
payload,
controller
});

const mosaicUrl = responseData.links[1].href;
setMosaicUrl(mosaicUrl);
} else {
LOG &&
LOG &&
/* eslint-disable-next-line no-console */
console.log('Titiler /register %cEndpoint error', 'color: red;', error);
throw error;
Expand Down Expand Up @@ -321,6 +322,7 @@ export function RasterTimeseries(props: RasterTimeseriesProps) {
changeStatus({ status: 'idle', context: STATUS_KEY.Layer });
};
}, [
colorMap,
stacCollection
// This hook depends on a series of properties, but whenever they change the
// `stacCollection` is guaranteed to change because a new STAC request is
Expand Down Expand Up @@ -358,7 +360,8 @@ export function RasterTimeseries(props: RasterTimeseriesProps) {
const tileParams = qs.stringify(
{
assets: 'cog_default',
...(sourceParams ?? {})
...(sourceParams ?? {}),
...colorMap && {colormap_name: colorMap}
},
// Temporary solution to pass different tile parameters for hls data
{
Expand Down Expand Up @@ -485,6 +488,7 @@ export function RasterTimeseries(props: RasterTimeseriesProps) {
};
}, [
mosaicUrl,
colorMap,
points,
minZoom,
haveSourceParamsChanged,
Expand Down
2 changes: 1 addition & 1 deletion app/scripts/components/common/map/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface BaseTimeseriesProps extends BaseGeneratorParams {
tileApiEndpoint?: string;
zoomExtent?: number[];
onStatusChange?: (result: { status: ActionStatus; id: string }) => void;
colorMap?: string;
}

// export interface ZarrTimeseriesProps extends BaseTimeseriesProps {
Expand All @@ -72,4 +73,3 @@ interface AssetUrlReplacement {
export interface CMRTimeseriesProps extends BaseTimeseriesProps {
assetUrlReplacements?: AssetUrlReplacement;
}

3 changes: 2 additions & 1 deletion app/scripts/components/exploration/atoms/datasets.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { datasetLayers, reconcileDatasets } from '../data-utils';
import { datasetLayers } from '../data-utils';
import { reconcileDatasets } from '../data-utils-no-faux-module';
import { TimelineDataset, TimelineDatasetForUrl } from '../types.d.ts';
import { atomWithUrlValueStability } from '$utils/params-location-atom/atom-with-url-value-stability';

Expand Down
12 changes: 12 additions & 0 deletions app/scripts/components/exploration/atoms/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,18 @@ export function useTimelineDatasetVisibility(
return useAtom(visibilityAtom);
}

export function useTimelineDatasetColormap(
datasetAtom: PrimitiveAtom<TimelineDataset>
) {
const colorMapAtom = useMemo(() => {
return focusAtom(datasetAtom, (optic) =>
optic.prop('settings').prop('colorMap')
);
}, [datasetAtom]);

return useAtom(colorMapAtom);
}

export const useTimelineDatasetAnalysis = (
datasetAtom: PrimitiveAtom<TimelineDataset>
) => {
Expand Down
11 changes: 11 additions & 0 deletions app/scripts/components/exploration/atoms/rescale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { atomWithUrlValueStability } from '$utils/params-location-atom/atom-with-url-value-stability';

const initialParams = new URLSearchParams(window.location.search);

export const reverseAtom = atomWithUrlValueStability<boolean>({
initialValue: initialParams.get('reverse') === 'true',
urlParam: 'reverse',
hydrate: (value) => value === 'true',
areEqual: (prev, next) => prev === next,
dehydrate: (value) => (value ? 'true' : 'false')
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import { glsp, themeVal } from '@devseed-ui/theme-provider';
import { Link } from 'react-router-dom';
import { timelineDatasetsAtom } from '../../atoms/datasets';
import {
reconcileDatasets,
datasetLayers,
allExploreDatasets
} from '../../data-utils';
reconcileDatasets
} from '../../data-utils-no-faux-module';
import { datasetLayers,
allExploreDatasets} from '../../data-utils';
import RenderModalHeader from './header';

import ModalFooterRender from './footer';
Expand Down
Loading
Loading