diff --git a/app/scripts/components/common/blocks/scrollytelling/index.tsx b/app/scripts/components/common/blocks/scrollytelling/index.tsx index f9faf26f6..695a229ae 100644 --- a/app/scripts/components/common/blocks/scrollytelling/index.tsx +++ b/app/scripts/components/common/blocks/scrollytelling/index.tsx @@ -34,7 +34,10 @@ import { S_FAILED, S_SUCCEEDED } from '$utils/status'; import { SimpleMap } from '$components/common/mapbox/map'; import Hug from '$styles/hug'; -import LayerLegend from '$components/common/mapbox/layer-legend'; +import { + LayerLegendContainer, + LayerLegend +} from '$components/common/mapbox/layer-legend'; import MapMessage from '$components/common/mapbox/map-message'; import { MapLoading } from '$components/common/loading-skeleton'; import { HintedError } from '$utils/hinted-error'; @@ -65,6 +68,9 @@ const TheMap = styled.div<{ topOffset: number }>` top: ${topOffset}px; height: calc(100vh - ${topOffset}px); `} + .mapboxgl-canvas { + height: 100%; + } `; const TheChapters = styled(Hug)` @@ -392,25 +398,37 @@ function Scrollytelling(props) { {/* Map overlay element Layer legend for the active layer. + + The SwitchTransition animated between 2 elements, so when there's no + legend we use an empty div to ensure that there's an out animation. + We also have to set the timeout to 1 because the empty div will not + have transitions defined for it. This causes the transitionend + listener to never fire leading to an infinite wait. */} - {activeChapterLayer?.layer.legend && ( - - { - node.addEventListener('transitionend', done, false); - }} - classNames='reveal' - > - - - - )} + + { + if (!activeChapterLayer) return; + node?.addEventListener('transitionend', done, false); + }} + classNames='reveal' + > + {activeChapterLayer?.layer.legend ? ( + + + + ) : ( +
+ )} + + @@ -419,9 +437,10 @@ function Scrollytelling(props) { if (!resolvedLayer) return null; const { runtimeData, Component: LayerCmp, layer } = resolvedLayer; - const isHidden = (!activeChapterLayerId || - activeChapterLayerId !== runtimeData.id || - activeChapter.showBaseMap); + const isHidden = + !activeChapterLayerId || + activeChapterLayerId !== runtimeData.id || + activeChapter.showBaseMap; if (!LayerCmp) return null; @@ -441,7 +460,7 @@ function Scrollytelling(props) { sourceParams={layer.sourceParams} zoomExtent={layer.zoomExtent} onStatusChange={onLayerLoadSuccess} - idSuffix={'scrolly-'+ lIdx} + idSuffix={'scrolly-' + lIdx} isHidden={isHidden} /> ); @@ -450,7 +469,11 @@ function Scrollytelling(props) { className='root' mapRef={mapRef} containerRef={mapContainer} - onLoad={() => setMapLoaded(true)} + onLoad={() => { + setMapLoaded(true); + // Fit the map to the container once loaded. + mapRef.current?.resize(); + }} mapOptions={mapOptions} /> diff --git a/app/scripts/components/common/mapbox/index.tsx b/app/scripts/components/common/mapbox/index.tsx index 58a3779ea..45a7392ba 100644 --- a/app/scripts/components/common/mapbox/index.tsx +++ b/app/scripts/components/common/mapbox/index.tsx @@ -28,7 +28,7 @@ import { AoiChangeListenerOverload, AoiState } from '../aoi/types'; import { getLayerComponent, resolveConfigFunctions } from './layers/utils'; import { SimpleMap } from './map'; import MapMessage from './map-message'; -import LayerLegend from './layer-legend'; +import { LayerLegendContainer, LayerLegend } from './layer-legend'; import { useBasemap } from './map-options/use-basemap'; import { DEFAULT_MAP_STYLE_URL } from './map-options/basemaps'; import { Styles } from './layers/styles'; @@ -365,13 +365,27 @@ function MapboxMapComponent(props: MapboxMapProps, ref) { Layer legend for the active layer. */} {baseLayerResolvedData?.legend && ( - - )} + + + {compareLayerResolvedData?.legend && + isComparing && + (baseLayerResolvedData.id !== compareLayerResolvedData.id) && + } + + )} + + + {/* Maps container @@ -442,7 +456,6 @@ function MapboxMapComponent(props: MapboxMapProps, ref) { boundariesOption={boundariesOption} /> {isMapCompareLoaded && - isComparing && compareLayerResolvedData && CompareLayerComponent && ( { if (stops.length === 1) return stops[0]; @@ -44,17 +47,16 @@ const makeGradient = (stops: string[]) => { const printLegendVal = (val: string | number) => typeof val === 'number' ? formatThousands(val, { shorten: true }) : val; -const LayerLegendSelf = styled.div` +export const LegendContainer = styled.div` position: absolute; z-index: 8; bottom: ${variableGlsp()}; right: ${variableGlsp()}; display: flex; flex-flow: column nowrap; - border-radius: ${themeVal('shape.rounded')}; box-shadow: ${themeVal('boxShadow.elevationB')}; + border-radius: ${themeVal('shape.rounded')}; background-color: ${themeVal('color.surface')}; - width: 16rem; &.reveal-enter { opacity: 0; @@ -76,9 +78,23 @@ const LayerLegendSelf = styled.div` &.reveal-exit-active { transition: bottom 240ms ease-in-out, opacity 240ms ease-in-out; } +`; + +const LayerLegendSelf = styled.div` + display: flex; + flex-flow: column nowrap; + width: 16rem; + border-bottom: 1px solid ${themeVal('color.base-100')}; ${WidgetItemHeader} { - padding: ${variableGlsp(0.5, 0.75)}; + padding: ${variableGlsp(0.25, 0.5)}; + } + + &:only-child { + ${WidgetItemHeader} { + padding: ${variableGlsp(0.5)}; + } + border-bottom: 0; } `; @@ -152,78 +168,81 @@ const LegendBody = styled(WidgetItemBodyInner)` .scroll-inner { padding: ${variableGlsp(0.5, 0.75)}; } - .shadow-bottom { border-radius: ${themeVal('shape.rounded')}; } `; -function LayerLegend( +export function LayerLegend( props: LayerLegendCommonProps & (LayerLegendGradient | LayerLegendCategorical) ) { const { id, type, title, description } = props; return ( - - ( - - - - {title} - {/* Subtitle */} - - - - - - - - {type === 'categorical' && ( - - )} - {type === 'gradient' && ( - - )} - - )} - renderBody={() => ( - - -
- {description || ( -

No info available for this layer.

- )} -
-
-
- )} - /> -
+ ( + + + + {title} + {/* Subtitle */} + + + + + + + + {type === 'categorical' && ( + + )} + {type === 'gradient' && ( + + )} + + )} + renderBody={() => ( + + +
+ {description ||

No info available for this layer.

} +
+
+
+ )} + /> ); } -export default LayerLegend; +export function LayerLegendContainer(props: LayerLegendContainerProps) { + return ( + + + {props.children} + + + ); +} function LayerCategoricalGraphic(props: LayerLegendCategorical) { const { stops } = props; diff --git a/app/scripts/components/common/mapbox/layers/raster-timeseries.tsx b/app/scripts/components/common/mapbox/layers/raster-timeseries.tsx index c5adfe260..9d223097f 100644 --- a/app/scripts/components/common/mapbox/layers/raster-timeseries.tsx +++ b/app/scripts/components/common/mapbox/layers/raster-timeseries.tsx @@ -45,7 +45,7 @@ interface MapLayerRasterTimeseriesProps { zoomExtent?: [number, number]; onStatusChange?: (result: { status: ActionStatus; id: string }) => void; isHidden: boolean; - idSuffix?: string + idSuffix?: string; } export interface StacFeature { diff --git a/app/scripts/components/common/mapbox/layers/utils.ts b/app/scripts/components/common/mapbox/layers/utils.ts index 0e171f7ac..cc438fe60 100644 --- a/app/scripts/components/common/mapbox/layers/utils.ts +++ b/app/scripts/components/common/mapbox/layers/utils.ts @@ -69,7 +69,7 @@ export const getCompareLayerData = ( id: stacCol, stacCol, type: type || layerData.type, - zoomExtent: zoomExtent || layerData.zoomExtent, + zoomExtent: zoomExtent ?? layerData.zoomExtent, sourceParams: defaultsDeep({}, sourceParams, layerData.sourceParams), ...passThroughProps }; @@ -92,11 +92,13 @@ export const getCompareLayerData = ( const datasetData = datasets[datasetId]?.data; if (!datasetData) { + // eslint-disable-next-line fp/no-mutating-methods errorHints.push(`Dataset [${datasetId}] not found (compare.datasetId)`); } - const otherLayer = datasetData?.layers?.find((l) => l.id === layerId); + const otherLayer = datasetData?.layers.find((l) => l.id === layerId); if (!otherLayer) { + // eslint-disable-next-line fp/no-mutating-methods errorHints.push( `Layer [${layerId}] not found in dataset [${datasetId}] (compare.layerId)` ); @@ -112,8 +114,11 @@ export const getCompareLayerData = ( return { id: otherLayer.id, type: otherLayer.type, + name: otherLayer.name, + description: otherLayer.description, + legend: otherLayer.legend, stacCol: otherLayer.stacCol, - zoomExtent: zoomExtent || otherLayer.zoomExtent, + zoomExtent: zoomExtent ?? otherLayer.zoomExtent, sourceParams: defaultsDeep({}, sourceParams, otherLayer.sourceParams), ...passThroughProps }; @@ -133,7 +138,7 @@ type Res = T extends Fn ? DatasetDatumReturnType : never : T extends any[] - ? Array> + ? Res[] : T extends object ? ObjResMap : T; @@ -143,10 +148,10 @@ export function resolveConfigFunctions( bag: DatasetDatumFnResolverBag ): Res; /* eslint-disable-next-line no-redeclare */ -export function resolveConfigFunctions>( +export function resolveConfigFunctions( datum: T, bag: DatasetDatumFnResolverBag -): Array>; +): Res[]; /* eslint-disable-next-line no-redeclare */ export function resolveConfigFunctions( datum: any, diff --git a/mock/datasets/no2.data.mdx b/mock/datasets/no2.data.mdx index 8b9ee8396..968ab93a8 100644 --- a/mock/datasets/no2.data.mdx +++ b/mock/datasets/no2.data.mdx @@ -41,8 +41,8 @@ layers: - 0 - 1.5e16 compare: - datasetId: no2 - layerId: no2-monthly + datasetId: nighttime-lights + layerId: nightlights-hd-monthly mapLabel: | ::js ({ dateFns, datetime, compareDatetime }) => { return `${dateFns.format(datetime, 'LLL yyyy')} VS ${dateFns.format(compareDatetime, 'LLL yyyy')}`; @@ -60,6 +60,35 @@ layers: - "#c13b72" - "#461070" - "#050308" + - id: no2-monthly-2 + stacCol: no2-monthly + name: No2 + type: raster + description: Levels in 10¹⁵ molecules cm⁻². Darker colors indicate higher nitrogen dioxide (NO₂) levels associated and more activity. Lighter colors indicate lower levels of NO₂ and less activity. + zoomExtent: + - 0 + - 20 + sourceParams: + resampling_method: bilinear + bidx: 1 + color_formula: gamma r 1.05 + colormap_name: coolwarm + rescale: + - 0 + - 1.5e16 + legend: + unit: + label: Molecules cm3 + type: gradient + min: "Less" + max: "More" + stops: + - "#99c5e0" + - "#f9eaa9" + - "#f7765d" + - "#c13b72" + - "#461070" + - "#050308" - id: no2-monthly-diff stacCol: no2-monthly-diff name: No2 (Diff) diff --git a/mock/discoveries/air-quality-and-covid-19.discoveries.mdx b/mock/discoveries/air-quality-and-covid-19.discoveries.mdx index 97fe0186f..a5710e0df 100644 --- a/mock/discoveries/air-quality-and-covid-19.discoveries.mdx +++ b/mock/discoveries/air-quality-and-covid-19.discoveries.mdx @@ -261,11 +261,10 @@ thematics: