@@ -297,73 +306,20 @@ const DatasetCard = ({
{name}
- {selectedDate !== undefined && selectedLayerId !== undefined && (
-
-
-
-
-
- Save chart data
-
-
- )}
- {!!selectedLayer?.attributes!.download_link && (
-
-
-
-
-
- Download dataset
-
-
+ {isChartDownloadVisible && (
+
)}
- {!!metadata && (
-
+ {selectedLayer !== undefined && !!selectedLayer.attributes!.download_link && (
+
)}
+ {!!metadata &&
}
{(!!selectedLayer?.attributes!.download_link || !!metadata) && (
)}
@@ -410,71 +366,40 @@ const DatasetCard = ({
)}
- {selectedDate !== undefined && selectedLayerId !== undefined && (
+ {isDatasetActive && showChartOnInteraction && selectedLayer !== undefined && (
+
+
+ Select a point on the map for details.
+
+ )}
+ {isInteractionChartVisible && (
+
+
+
+ )}
+ {isYearChartVisible && (
)}
- {selectedDate !== undefined && dateRange !== undefined && isDatasetActive && (
-
-
-
-
-
-
-
-
-
-
-
-
+ {isDatasetActive &&
+ (isInteractionChartVisible || isYearChartVisible) &&
+ selectedLayer !== undefined &&
+ !!selectedLayer.attributes!.chart_sentence && (
+
+
-
+ )}
+ {isDatasetActive && selectedLayer !== undefined && selectedDate !== undefined && (
+
)}
diff --git a/client/src/components/dataset-card/metadata-button.tsx b/client/src/components/dataset-card/metadata-button.tsx
new file mode 100644
index 0000000..f49558a
--- /dev/null
+++ b/client/src/components/dataset-card/metadata-button.tsx
@@ -0,0 +1,39 @@
+import DatasetMetadata from "@/components/dataset-metadata";
+import { Button } from "@/components/ui/button";
+import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
+import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
+import QuestionMarkIcon from "@/svgs/question-mark.svg";
+import { MetadataItemComponent } from "@/types/generated/strapi.schemas";
+
+interface MetadataButtonProps {
+ datasetName: string;
+ metadata: MetadataItemComponent;
+}
+
+const MetadataButton = ({ datasetName, metadata }: MetadataButtonProps) => {
+ return (
+
+ );
+};
+
+export default MetadataButton;
diff --git a/client/src/components/interaction-chart/index.tsx b/client/src/components/interaction-chart/index.tsx
new file mode 100644
index 0000000..4eaeded
--- /dev/null
+++ b/client/src/components/interaction-chart/index.tsx
@@ -0,0 +1,207 @@
+"use client";
+
+import { AxisBottom, AxisLeft, AxisTop } from "@visx/axis";
+import { GridColumns, GridRows } from "@visx/grid";
+import { Group } from "@visx/group";
+import { useParentSize } from "@visx/responsive";
+import { scaleLinear, scaleTime } from "@visx/scale";
+import { LinePath } from "@visx/shape";
+import { Text, TextProps } from "@visx/text";
+import { extent } from "d3-array";
+import { ComponentProps, useCallback, useMemo } from "react";
+
+import { Skeleton } from "@/components/ui/skeleton";
+import useInteractionChartData from "@/hooks/use-interaction-chart-data";
+import tailwindConfig from "@/lib/tailwind-config";
+import { cn } from "@/lib/utils";
+
+interface InteractionChartProps {
+ data: ReturnType
["data"];
+ loading: boolean;
+}
+
+const CHART_MIN_HEIGHT = 120;
+const CHART_MAX_HEIGHT = 250;
+const X_AXIS_HEIGHT = 22;
+const X_AXIS_OFFSET_RIGHT = 5;
+const X_AXIS_TICK_COUNT = 5;
+const X_AXIS_TICK_HEIGHT = 5;
+const Y_AXIS_WIDTH = 34;
+const Y_AXIS_OFFSET_TOP = 5;
+const Y_AXIS_TICK_WIDTH = 5;
+const Y_AXIS_TICK_COUNT = 5;
+
+const InteractionChart = ({ data, loading }: InteractionChartProps) => {
+ const { parentRef, width } = useParentSize({ ignoreDimensions: ["height"] });
+ const height = useMemo(
+ () => Math.max(Math.min(width / 2.7, CHART_MAX_HEIGHT), CHART_MIN_HEIGHT),
+ [width],
+ );
+
+ const unitWidth = useMemo(() => {
+ if (loading || !data) {
+ return 0;
+ }
+
+ const subtract =
+ data.unit?.split("").reduce((res, char) => {
+ if (char === "˚" || char === "/" || char === " ") {
+ return res - 6;
+ }
+
+ return res;
+ }, Y_AXIS_TICK_WIDTH) ?? Y_AXIS_TICK_WIDTH;
+
+ return (data.unit?.length ?? 0) * 8 + subtract;
+ }, [data, loading]);
+
+ const xScale = useMemo(() => {
+ if (loading || !data) {
+ return undefined;
+ }
+
+ const xValues = data.data.map(({ x }) => x).sort();
+
+ return scaleTime({
+ range: [0, width - Y_AXIS_WIDTH - X_AXIS_OFFSET_RIGHT - unitWidth],
+ domain: [new Date(xValues[0]), new Date(xValues.slice(-1)[0])],
+ nice: X_AXIS_TICK_COUNT,
+ });
+ }, [width, data, loading, unitWidth]);
+
+ const yScale = useMemo(() => {
+ if (loading || !data) {
+ return undefined;
+ }
+
+ return scaleLinear({
+ range: [height - X_AXIS_HEIGHT, Y_AXIS_OFFSET_TOP],
+ domain: extent(data.data.map(({ y }) => y)) as [number, number],
+ nice: Y_AXIS_TICK_COUNT,
+ });
+ }, [height, data, loading]);
+
+ const xAxisTickLabelProps = useCallback<
+ NonNullable["tickLabelProps"], Partial>>
+ >((tick, index, ticks) => {
+ let textAnchor: ComponentProps["textAnchor"] = "middle";
+ let dx = 0;
+ if (index === 0) {
+ textAnchor = "start";
+ } else if (index + 1 === ticks.length) {
+ textAnchor = "end";
+ dx = X_AXIS_OFFSET_RIGHT;
+ }
+
+ return {
+ dx,
+ dy: 3.5,
+ textAnchor,
+ className: cn({
+ "text-right font-sans text-[11px] text-rhino-blue-950": true,
+ "invisible sm:visible": index % 2 === 1,
+ }),
+ };
+ }, []);
+
+ const yAxisTickLabelProps = useCallback<
+ NonNullable["tickLabelProps"], Partial>>
+ >((tick, index) => {
+ let dy = 3.5;
+ if (index === 0) {
+ dy = 0;
+ }
+
+ return {
+ dx: -4,
+ dy,
+ textAnchor: "end",
+ className: "text-right font-sans text-[11px] text-rhino-blue-950",
+ };
+ }, []);
+
+ return (
+
+ {loading && !data &&
}
+ {!loading && !!data && !!xScale && !!yScale && (
+
+ )}
+
+ );
+};
+
+export default InteractionChart;
diff --git a/client/src/components/map/layer-manager/item.tsx b/client/src/components/map/layer-manager/item.tsx
index ed28c54..14d5966 100644
--- a/client/src/components/map/layer-manager/item.tsx
+++ b/client/src/components/map/layer-manager/item.tsx
@@ -1,4 +1,5 @@
import useLayerConfig from "@/hooks/use-layer-config";
+import useLayerInteractionState from "@/hooks/use-layer-interaction-state";
import { LayerSettings } from "@/types/layer";
import AnimatedLayer from "./animated-layer";
@@ -12,13 +13,15 @@ interface LayerManagerItemProps {
}
const LayerManagerItem = ({ id, beforeId, settings }: LayerManagerItemProps) => {
- const layerConfig = useLayerConfig(id, settings);
+ const [interactionState, { setHoveredFeature, setSelectedFeature }] =
+ useLayerInteractionState(id);
+ const layerConfig = useLayerConfig(id, settings, interactionState);
if (!layerConfig) {
return null;
}
- const { type, config } = layerConfig;
+ const { type, config, interactive } = layerConfig;
if (!config.styles) {
return null;
@@ -33,7 +36,15 @@ const LayerManagerItem = ({ id, beforeId, settings }: LayerManagerItemProps) =>
}
if (config.source.type === "vector") {
- return ;
+ return (
+
+ );
}
console.warn(`Unsupported layer type (${config.source.type})`);
diff --git a/client/src/components/map/layer-manager/vector-layer.tsx b/client/src/components/map/layer-manager/vector-layer.tsx
index 8934f3a..3b9b1a0 100644
--- a/client/src/components/map/layer-manager/vector-layer.tsx
+++ b/client/src/components/map/layer-manager/vector-layer.tsx
@@ -1,5 +1,6 @@
+import { GetPickingInfoParams } from "@deck.gl/core";
import { MaskExtension } from "@deck.gl/extensions";
-import { MVTLayer, MVTLayerProps } from "@deck.gl/geo-layers";
+import { MVTLayer, MVTLayerPickingInfo, MVTLayerProps } from "@deck.gl/geo-layers";
import { ScatterplotLayer } from "@deck.gl/layers";
import { BinaryFeatureCollection } from "@loaders.gl/schema";
import { useContext, useEffect } from "react";
@@ -7,17 +8,24 @@ import { VectorSourceRaw as IVectorTileSource } from "react-map-gl";
import { env } from "@/env";
import useMapZoom from "@/hooks/use-map-zoom";
-import { LayerConfig } from "@/types/layer";
-import { convertBinaryToPointGeoJSON, resolveDeckglProperties } from "@/utils/mapbox-deckgl-bridge";
+import { LayerConfig, LayerInteractionState } from "@/types/layer";
+import {
+ convertBinaryToPointGeoJSON,
+ resolveDeckglProperties,
+ resolveInteractive,
+} from "@/utils/mapbox-deckgl-bridge";
import { DeckGLMapboxOverlayContext } from "../deckgl-mapbox-provider";
interface VectorLayerProps {
config: LayerConfig;
beforeId: string;
+ interactive: boolean;
+ onHover: (properties: LayerInteractionState["hoveredFeature"]) => void;
+ onClick: (properties: LayerInteractionState["selectedFeature"]) => void;
}
-const VectorLayer = ({ config, beforeId }: VectorLayerProps) => {
+const VectorLayer = ({ config, beforeId, interactive, onHover, onClick }: VectorLayerProps) => {
const { addLayer, removeLayer } = useContext(DeckGLMapboxOverlayContext);
const zoom = useMapZoom();
@@ -51,6 +59,23 @@ const VectorLayer = ({ config, beforeId }: VectorLayerProps) => {
...resolveDeckglProperties(style, zoom),
extensions: [new MaskExtension()],
maskId: "mask",
+ pickable: interactive,
+ onHover: ({ picked, object }) => {
+ if (picked) {
+ onHover(object.properties);
+ } else {
+ onHover(null);
+ }
+ },
+ onClick: ({ picked, object }) => {
+ const isFeatureInteractive = resolveInteractive(style, zoom, object);
+
+ if (picked && isFeatureInteractive) {
+ onClick(object.properties);
+ } else {
+ onClick(null);
+ }
+ },
};
// Here's an edge case: when the vector layer contains polygons and lines, Mapbox allows
@@ -77,7 +102,20 @@ const VectorLayer = ({ config, beforeId }: VectorLayerProps) => {
};
}
- layers.push(new MVTLayer(layerProps));
+ // We extend the MVTLayer class to make sure that when interacting with the layer, we pick
+ // the correct feature information, which is decoded binary data (i.e. GeoJSON properties) for
+ // the circle layers
+ class MVTJSONLayer extends MVTLayer {
+ getPickingInfo(params: GetPickingInfoParams) {
+ if (style.type === "circle") {
+ return params.info as MVTLayerPickingInfo;
+ }
+
+ return super.getPickingInfo(params);
+ }
+ }
+
+ layers.push(new MVTJSONLayer(layerProps));
});
layers.map((layer) => {
@@ -89,7 +127,7 @@ const VectorLayer = ({ config, beforeId }: VectorLayerProps) => {
removeLayer(layer.id);
});
};
- }, [config, beforeId, addLayer, removeLayer, zoom]);
+ }, [config, beforeId, addLayer, removeLayer, zoom, interactive, onHover, onClick]);
return null;
};
diff --git a/client/src/components/map/legend/item/index.tsx b/client/src/components/map/legend/item/index.tsx
index 33833cb..0efff08 100644
--- a/client/src/components/map/legend/item/index.tsx
+++ b/client/src/components/map/legend/item/index.tsx
@@ -131,11 +131,9 @@ const LegendItem = ({ id, settings, sortableAttributes, sortableListeners }: Leg
)}
- {!isLoading &&
- data.topicSlug !== "contextual" &&
- data.topicSlug !== "hydrometeorological" && (
-
{isLoading && (
<>
diff --git a/client/src/components/panels/drought/index.tsx b/client/src/components/panels/drought/index.tsx
index c5341da..dc098e9 100644
--- a/client/src/components/panels/drought/index.tsx
+++ b/client/src/components/panels/drought/index.tsx
@@ -8,7 +8,7 @@ import useDatasetsBySubTopic from "@/hooks/use-datasets-by-sub-topic";
const DroughtPanel = () => {
const { data, isLoading } = useDatasetsBySubTopic(
"drought",
- ["name", "params_config", "download_link"],
+ ["name", "params_config", "download_link", "show_chart_on_interaction", "chart_sentence"],
true,
);
diff --git a/client/src/components/panels/flood/index.tsx b/client/src/components/panels/flood/index.tsx
index 0b8db01..1a8c1b5 100644
--- a/client/src/components/panels/flood/index.tsx
+++ b/client/src/components/panels/flood/index.tsx
@@ -8,7 +8,7 @@ import useDatasetsBySubTopic from "@/hooks/use-datasets-by-sub-topic";
const FloodPanel = () => {
const { data, isLoading } = useDatasetsBySubTopic(
"flood",
- ["name", "params_config", "download_link"],
+ ["name", "params_config", "download_link", "show_chart_on_interaction", "chart_sentence"],
true,
);
diff --git a/client/src/components/panels/hydrometeorological/index.tsx b/client/src/components/panels/hydrometeorological/index.tsx
index 6828639..9d9cbc4 100644
--- a/client/src/components/panels/hydrometeorological/index.tsx
+++ b/client/src/components/panels/hydrometeorological/index.tsx
@@ -9,7 +9,7 @@ import HydrometeorologicalImage from "../../../../public/assets/images/hydromete
const HydrometeorologicalPanel = () => {
const { data, isLoading } = useDatasetsBySubTopic(
"hydrometeorological",
- ["name", "params_config", "download_link"],
+ ["name", "params_config", "download_link", "show_chart_on_interaction", "chart_sentence"],
true,
);
diff --git a/client/src/hooks/use-deckgl-mapbox-overlay.ts b/client/src/hooks/use-deckgl-mapbox-overlay.ts
index af04a97..efc5d0b 100644
--- a/client/src/hooks/use-deckgl-mapbox-overlay.ts
+++ b/client/src/hooks/use-deckgl-mapbox-overlay.ts
@@ -1,17 +1,46 @@
+import { PickingInfo } from "@deck.gl/core";
import { MapboxOverlay, MapboxOverlayProps } from "@deck.gl/mapbox";
-import { useEffect, useMemo } from "react";
+import { useSetAtom } from "jotai";
+import { useCallback, useEffect, useMemo, useRef } from "react";
import { useMap } from "react-map-gl";
+import { selectedFeatureByLayerAtom } from "@/hooks/use-layer-interaction-state";
+
export default function useDeckGLMapboxOverlay(props: MapboxOverlayProps = { interleaved: true }) {
const { current: map } = useMap();
+ const cursorRef = useRef("grab");
+ const setSelectedFeatureByLayer = useSetAtom(selectedFeatureByLayerAtom);
+
+ const onClick = useCallback(
+ ({ picked }: PickingInfo) => {
+ if (!picked) {
+ // If the user clicks on the map and no geometry was below the cursor, then we make sure to
+ // reset all the layers' selected features
+ setSelectedFeatureByLayer({});
+ }
+ },
+ [setSelectedFeatureByLayer],
+ );
+
+ const onHover = useCallback(
+ ({ picked }: PickingInfo) => {
+ cursorRef.current = picked ? "pointer" : "grab";
+ if (map) {
+ map.getCanvas().style.cursor = cursorRef.current;
+ }
+ },
+ [map],
+ );
const mapboxOverlay = useMemo(
() =>
new MapboxOverlay({
...props,
- getCursor: () => map?.getCanvas().style.cursor || "",
+ getCursor: () => cursorRef.current,
+ onClick,
+ onHover,
}),
- [props, map],
+ [props, onClick, onHover],
);
useEffect(() => {
diff --git a/client/src/hooks/use-interaction-chart-data.ts b/client/src/hooks/use-interaction-chart-data.ts
new file mode 100644
index 0000000..718d719
--- /dev/null
+++ b/client/src/hooks/use-interaction-chart-data.ts
@@ -0,0 +1,97 @@
+import { GeoJsonProperties } from "geojson";
+import { useMemo } from "react";
+
+import { useGetLayers } from "@/types/generated/layer";
+import {
+ ChartDataLayerDataAttributesChartDataDataItemAttributes,
+ DatasetLayersDataItem,
+} from "@/types/generated/strapi.schemas";
+import { LayerParamsConfig } from "@/types/layer";
+
+interface InteractionChartData {
+ data: {
+ x: string;
+ y: number;
+ }[];
+ unit: string | undefined;
+}
+
+export default function useInteractionChartData(
+ layer?: DatasetLayersDataItem,
+ feature?: GeoJsonProperties | null,
+) {
+ const identifier = useMemo(() => {
+ if (!layer || !feature) {
+ return undefined;
+ }
+
+ const paramsConfig = layer.attributes!.params_config as LayerParamsConfig;
+ const featureId = paramsConfig.find(({ key }) => key === "feature-id")?.default as
+ | string
+ | undefined;
+
+ if (!featureId) {
+ return undefined;
+ }
+
+ return feature[featureId];
+ }, [layer, feature]);
+
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore-error
+ const { data, isLoading } = useGetLayers
(
+ {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore-error
+ fields: ["chart_unit"],
+ populate: {
+ chart_data: {
+ fields: ["x_values", "y_values"],
+ filters: {
+ unique_identifier: {
+ $eq: identifier,
+ },
+ },
+ "pagination[limit]": 1,
+ },
+ },
+ filters: {
+ id: {
+ $eq: layer?.id,
+ },
+ },
+ "pagination[limit]": 1,
+ },
+ {
+ query: {
+ enabled: layer !== undefined && feature !== undefined && feature !== null,
+ placeholderData: { data: [] },
+ select: (data) => {
+ if (!data?.data?.length) {
+ return undefined;
+ }
+
+ const { attributes: layerAttributes } = data.data[0];
+
+ if (!layerAttributes!.chart_data?.data?.length) {
+ return undefined;
+ }
+
+ const { data: chartData } = layerAttributes!.chart_data;
+ const chartAttributes = chartData[0]
+ .attributes! as ChartDataLayerDataAttributesChartDataDataItemAttributes;
+
+ return {
+ data: (chartAttributes.x_values as number[]).map((x, index) => ({
+ x,
+ y: (chartAttributes.y_values as number[])[index],
+ })),
+ unit: layerAttributes!.chart_unit,
+ };
+ },
+ },
+ },
+ );
+
+ return { data, isLoading };
+}
diff --git a/client/src/hooks/use-layer-config.ts b/client/src/hooks/use-layer-config.ts
index 0796090..86d95f1 100644
--- a/client/src/hooks/use-layer-config.ts
+++ b/client/src/hooks/use-layer-config.ts
@@ -6,6 +6,7 @@ import { useGetLayersId } from "@/types/generated/layer";
import { LayerType } from "@/types/generated/strapi.schemas";
import {
LayerConfig,
+ LayerInteractionState,
LayerParamsConfig,
LayerResolvedParamsConfig,
LayerSettings,
@@ -14,8 +15,9 @@ import {
const resolveLayerParamsConfig = (
paramsConfig: LayerParamsConfig,
settings: LayerSettings,
+ interaction: LayerInteractionState,
): LayerResolvedParamsConfig => {
- return paramsConfig.reduce((res, param) => {
+ const config: LayerResolvedParamsConfig = paramsConfig.reduce((res, param) => {
const hasSettings = (key: string): key is keyof LayerSettings => {
return key in settings;
};
@@ -31,6 +33,17 @@ const resolveLayerParamsConfig = (
[param.key]: settings[param.key] ?? param.default,
};
}, {});
+
+ const interactive = config["interactive"] as boolean | undefined | null;
+ const featureId = config["feature-id"] as string | undefined | null;
+
+ // If the layer is interactive, we compute the properties related to the interaction
+ if (interactive === true && featureId !== undefined && featureId !== null) {
+ config["hovered-feature-id"] = interaction.hoveredFeature?.[featureId] ?? "";
+ config["selected-feature-id"] = interaction.selectedFeature?.[featureId] ?? "";
+ }
+
+ return config;
};
const resolveLayerConfig = (
@@ -73,7 +86,11 @@ const resolveLayerConfig = (
return converter.convertJson(config);
};
-export default function useLayerConfig(layerId: number, settings: LayerSettings) {
+export default function useLayerConfig(
+ layerId: number,
+ settings: LayerSettings,
+ interaction: LayerInteractionState,
+) {
const { data, isLoading } = useGetLayersId(layerId, {
query: {
select: (data) => {
@@ -97,8 +114,8 @@ export default function useLayerConfig(layerId: number, settings: LayerSettings)
return undefined;
}
- return resolveLayerParamsConfig(data.paramsConfig, settings);
- }, [data, isLoading, settings]);
+ return resolveLayerParamsConfig(data.paramsConfig, settings, interaction);
+ }, [data, isLoading, settings, interaction]);
const resolvedConfig = useMemo(() => {
if (isLoading || !data || !resolvedParamsConfig) {
@@ -116,9 +133,17 @@ export default function useLayerConfig(layerId: number, settings: LayerSettings)
return data.type;
}, [data, isLoading]);
+ const interactive = useMemo(() => {
+ if (!resolvedParamsConfig) {
+ return false;
+ }
+
+ return "interactive" in resolvedParamsConfig && resolvedParamsConfig.interactive === true;
+ }, [resolvedParamsConfig]);
+
if (!type || !resolvedConfig) {
return undefined;
}
- return { type, config: resolvedConfig };
+ return { type, config: resolvedConfig, interactive };
}
diff --git a/client/src/hooks/use-layer-interaction-state.ts b/client/src/hooks/use-layer-interaction-state.ts
new file mode 100644
index 0000000..e38ea41
--- /dev/null
+++ b/client/src/hooks/use-layer-interaction-state.ts
@@ -0,0 +1,56 @@
+"use client";
+
+import { atom, useAtom } from "jotai";
+import { useCallback, useMemo } from "react";
+
+import { LayerInteractionState } from "@/types/layer";
+
+// NOTE: prefer using the hook below instead of the atom directly, unless you need to access all the
+// hovered features at once
+export const hoveredFeatureByLayerAtom = atom<
+ Record
+>({});
+
+// NOTE: prefer using the hook below instead of the atom directly, unless you need to access all the
+// selected features at once
+export const selectedFeatureByLayerAtom = atom<
+ Record
+>({});
+
+export default function useLayerInteractionState(layerId: number | undefined) {
+ const [hoveredFeatureByLayer, setHoveredFeatureByLayer] = useAtom(hoveredFeatureByLayerAtom);
+ const [selectedFeatureByLayer, setSelectedFeatureByLayer] = useAtom(selectedFeatureByLayerAtom);
+
+ const hoveredFeature = useMemo(
+ () => (layerId === undefined ? null : (hoveredFeatureByLayer[layerId] ?? null)),
+ [layerId, hoveredFeatureByLayer],
+ );
+
+ const selectedFeature = useMemo(
+ () => (layerId === undefined ? null : (selectedFeatureByLayer[layerId] ?? null)),
+ [layerId, selectedFeatureByLayer],
+ );
+
+ const setHoveredFeature = useCallback(
+ (value: LayerInteractionState["hoveredFeature"]) => {
+ if (layerId !== undefined) {
+ setHoveredFeatureByLayer((prev) => ({ ...prev, [layerId]: value }));
+ }
+ },
+ [layerId, setHoveredFeatureByLayer],
+ );
+
+ const setSelectedFeature = useCallback(
+ (value: LayerInteractionState["selectedFeature"]) => {
+ if (layerId !== undefined) {
+ setSelectedFeatureByLayer((prev) => ({ ...prev, [layerId]: value }));
+ }
+ },
+ [layerId, setSelectedFeatureByLayer],
+ );
+
+ return [
+ { hoveredFeature, selectedFeature } as LayerInteractionState,
+ { setHoveredFeature, setSelectedFeature },
+ ] as const;
+}
diff --git a/client/src/hooks/use-layer-legend.ts b/client/src/hooks/use-layer-legend.ts
index 1740dae..c3d9caf 100644
--- a/client/src/hooks/use-layer-legend.ts
+++ b/client/src/hooks/use-layer-legend.ts
@@ -7,6 +7,7 @@ export default function useLayerLegend(id: number) {
return useGetLayers<{
name: string;
dataset: string;
+ datasetLayersCount: number;
topicSlug: string;
type: LegendLegendConfigComponent["type"];
unit: string;
@@ -23,6 +24,9 @@ export default function useLayerLegend(id: number) {
dataset: {
fields: ["name"],
populate: {
+ layers: {
+ fields: ["id"],
+ },
topic: {
fields: ["slug"],
},
@@ -44,13 +48,14 @@ export default function useLayerLegend(id: number) {
}
const { name, dataset, legend_config } = data.data[0].attributes!;
- const { name: datasetName, topic } = dataset!.data!.attributes!;
+ const { name: datasetName, topic, layers } = dataset!.data!.attributes!;
const { slug: topicSlug } = topic!.data!.attributes!;
const { type, unit, items } = legend_config!;
return {
name,
dataset: datasetName,
+ datasetLayersCount: layers!.data!.length,
topicSlug,
type,
unit,
diff --git a/client/src/svgs/cursor-arrow-rays.svg b/client/src/svgs/cursor-arrow-rays.svg
new file mode 100644
index 0000000..0c306eb
--- /dev/null
+++ b/client/src/svgs/cursor-arrow-rays.svg
@@ -0,0 +1,4 @@
+
diff --git a/client/src/types/generated/strapi.schemas.ts b/client/src/types/generated/strapi.schemas.ts
index 520e021..19253a9 100644
--- a/client/src/types/generated/strapi.schemas.ts
+++ b/client/src/types/generated/strapi.schemas.ts
@@ -1145,6 +1145,7 @@ export const LayerType = {
export interface Layer {
chart_data?: LayerChartData;
+ chart_sentence?: string;
chart_unit?: string;
createdAt?: string;
createdBy?: LayerCreatedBy;
@@ -1154,6 +1155,7 @@ export interface Layer {
mapbox_config: unknown;
name: string;
params_config: unknown;
+ show_chart_on_interaction?: boolean;
type: LayerType;
updatedAt?: string;
updatedBy?: LayerUpdatedBy;
@@ -1318,6 +1320,7 @@ export const LayerDatasetDataAttributesLayersDataItemAttributesType = {
export type LayerDatasetDataAttributesLayersDataItemAttributes = {
chart_data?: LayerDatasetDataAttributesLayersDataItemAttributesChartData;
+ chart_sentence?: string;
chart_unit?: string;
createdAt?: string;
createdBy?: LayerDatasetDataAttributesLayersDataItemAttributesCreatedBy;
@@ -1327,6 +1330,7 @@ export type LayerDatasetDataAttributesLayersDataItemAttributes = {
mapbox_config?: unknown;
name?: string;
params_config?: unknown;
+ show_chart_on_interaction?: boolean;
type?: LayerDatasetDataAttributesLayersDataItemAttributesType;
updatedAt?: string;
updatedBy?: LayerDatasetDataAttributesLayersDataItemAttributesUpdatedBy;
@@ -1386,6 +1390,7 @@ export type LayerDatasetDataAttributesLayersDataItemAttributesChartDataDataItemA
createdBy?: LayerDatasetDataAttributesLayersDataItemAttributesChartDataDataItemAttributesCreatedBy;
layer?: LayerDatasetDataAttributesLayersDataItemAttributesChartDataDataItemAttributesLayer;
location_code?: string;
+ unique_identifier?: string;
updatedAt?: string;
updatedBy?: LayerDatasetDataAttributesLayersDataItemAttributesChartDataDataItemAttributesUpdatedBy;
x_values?: unknown;
@@ -1667,6 +1672,7 @@ export type LayerRequestDataChartDataItem = number | string;
export type LayerRequestData = {
chart_data?: LayerRequestDataChartDataItem[];
+ chart_sentence?: string;
chart_unit?: string;
dataset?: LayerRequestDataDataset;
download_link?: string;
@@ -1674,6 +1680,7 @@ export type LayerRequestData = {
mapbox_config: unknown;
name: string;
params_config: unknown;
+ show_chart_on_interaction?: boolean;
type: LayerRequestDataType;
};
@@ -1776,6 +1783,7 @@ export const DatasetLayersDataItemAttributesType = {
export type DatasetLayersDataItemAttributes = {
chart_data?: DatasetLayersDataItemAttributesChartData;
+ chart_sentence?: string;
chart_unit?: string;
createdAt?: string;
createdBy?: DatasetLayersDataItemAttributesCreatedBy;
@@ -1785,6 +1793,7 @@ export type DatasetLayersDataItemAttributes = {
mapbox_config?: unknown;
name?: string;
params_config?: unknown;
+ show_chart_on_interaction?: boolean;
type?: DatasetLayersDataItemAttributesType;
updatedAt?: string;
updatedBy?: DatasetLayersDataItemAttributesUpdatedBy;
@@ -2176,6 +2185,7 @@ export type DatasetLayersDataItemAttributesChartDataDataItemAttributes = {
createdBy?: DatasetLayersDataItemAttributesChartDataDataItemAttributesCreatedBy;
layer?: DatasetLayersDataItemAttributesChartDataDataItemAttributesLayer;
location_code?: string;
+ unique_identifier?: string;
updatedAt?: string;
updatedBy?: DatasetLayersDataItemAttributesChartDataDataItemAttributesUpdatedBy;
x_values?: unknown;
@@ -2289,12 +2299,13 @@ export interface ChartData {
createdAt?: string;
createdBy?: ChartDataCreatedBy;
layer?: ChartDataLayer;
- location_code: string;
+ location_code?: string;
+ unique_identifier?: string;
updatedAt?: string;
updatedBy?: ChartDataUpdatedBy;
x_values: unknown;
y_values: unknown;
- year: number;
+ year?: number;
}
export type ChartDataLayerDataAttributesUpdatedByDataAttributes = { [key: string]: unknown };
@@ -2310,6 +2321,7 @@ export type ChartDataLayerDataAttributesUpdatedBy = {
export type ChartDataLayerDataAttributes = {
chart_data?: ChartDataLayerDataAttributesChartData;
+ chart_sentence?: string;
chart_unit?: string;
createdAt?: string;
createdBy?: ChartDataLayerDataAttributesCreatedBy;
@@ -2319,6 +2331,7 @@ export type ChartDataLayerDataAttributes = {
mapbox_config?: unknown;
name?: string;
params_config?: unknown;
+ show_chart_on_interaction?: boolean;
type?: ChartDataLayerDataAttributesType;
updatedAt?: string;
updatedBy?: ChartDataLayerDataAttributesUpdatedBy;
@@ -2692,6 +2705,7 @@ export type ChartDataLayerDataAttributesChartDataDataItemAttributes = {
createdBy?: ChartDataLayerDataAttributesChartDataDataItemAttributesCreatedBy;
layer?: ChartDataLayerDataAttributesChartDataDataItemAttributesLayer;
location_code?: string;
+ unique_identifier?: string;
updatedAt?: string;
updatedBy?: ChartDataLayerDataAttributesChartDataDataItemAttributesUpdatedBy;
x_values?: unknown;
@@ -2779,10 +2793,11 @@ export type ChartDataRequestDataLayer = number | string;
export type ChartDataRequestData = {
layer?: ChartDataRequestDataLayer;
- location_code: string;
+ location_code?: string;
+ unique_identifier?: string;
x_values: unknown;
y_values: unknown;
- year: number;
+ year?: number;
};
export interface ChartDataRequest {
diff --git a/client/src/types/layer.ts b/client/src/types/layer.ts
index 54d3556..52c0b39 100644
--- a/client/src/types/layer.ts
+++ b/client/src/types/layer.ts
@@ -1,3 +1,4 @@
+import { GeoJsonProperties } from "geojson";
import {
CircleLayerSpecification,
FillLayerSpecification,
@@ -14,6 +15,11 @@ export interface LayerSettings {
date?: string;
}
+export interface LayerInteractionState {
+ hoveredFeature: GeoJsonProperties | null;
+ selectedFeature: GeoJsonProperties | null;
+}
+
export interface LayerConfig {
source: AnySource;
styles: (
diff --git a/client/src/utils/mapbox-deckgl-bridge.ts b/client/src/utils/mapbox-deckgl-bridge.ts
index 2436642..4ae687f 100644
--- a/client/src/utils/mapbox-deckgl-bridge.ts
+++ b/client/src/utils/mapbox-deckgl-bridge.ts
@@ -3,6 +3,7 @@ import { binaryToGeojson } from "@loaders.gl/gis";
import { BinaryFeatureCollection } from "@loaders.gl/schema";
import { featureCollection, point } from "@turf/helpers";
import { coordAll } from "@turf/meta";
+import { GeoJsonProperties } from "geojson";
import { DataDrivenPropertyValueSpecification } from "mapbox-gl";
import {
expression as mapboxExpression,
@@ -235,6 +236,41 @@ const resolveIconSizeScale = (
return resolvedValue;
};
+export const resolveInteractive = (
+ style: LayerConfig["styles"][0],
+ zoom: number,
+ feature: GeoJsonProperties,
+ defaultValue = false,
+) => {
+ let value: DataDrivenPropertyValueSpecification | undefined;
+
+ if (
+ style.type === "fill" ||
+ style.type === "circle" ||
+ style.type === "line" ||
+ style.type === "symbol"
+ ) {
+ // NOTE: this property is custom, that's why we need to disable the error
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ value = style.layout?.["interactive"];
+ } else {
+ return undefined;
+ }
+
+ if (value === undefined) {
+ return defaultValue;
+ }
+
+ const resolvedValue = resolveMapboxExpression(value, zoom, feature, "boolean");
+
+ if (resolvedValue === null || resolvedValue === undefined) {
+ return defaultValue;
+ }
+
+ return resolvedValue;
+};
+
export const resolveDeckglProperties = (style: LayerConfig["styles"][0], zoom: number) => {
const resolvedProperties = {
visible: resolveVisible(style),
@@ -266,16 +302,23 @@ export const resolveDeckglProperties = (style: LayerConfig["styles"][0], zoom: n
resolvedProperties.stroked = false;
}
- return Object.entries(resolvedProperties).reduce((res, [key, value]) => {
- if (value === undefined) {
- return res;
- }
-
- return {
- ...res,
- [key]: value,
- };
- }, {}) as Partial;
+ return Object.entries(resolvedProperties).reduce(
+ (res, [key, value]) => {
+ if (value === undefined) {
+ return res;
+ }
+
+ return {
+ ...res,
+ [key]: value,
+ updateTriggers: {
+ ...res["updateTriggers"],
+ [key]: [style],
+ },
+ };
+ },
+ { updateTriggers: {} },
+ ) as Partial;
};
export const convertBinaryToPointGeoJSON = (data: BinaryFeatureCollection) => {
diff --git a/client/tailwind.config.ts b/client/tailwind.config.ts
index 829ca64..39e1275 100644
--- a/client/tailwind.config.ts
+++ b/client/tailwind.config.ts
@@ -25,6 +25,7 @@ const config: Config = {
"300": "#b6cbda",
"400": "#a4bdd0",
"500": "#7999b8",
+ "800": "#4E5F7F",
"950": "#2b3340",
},
"rhino-blue": {
@@ -41,6 +42,7 @@ const config: Config = {
"supernova-yellow": {
"300": "#ffe043",
"400": "#ffcc15",
+ "600": "#CE8800",
},
},
extend: {
diff --git a/client/yarn.lock b/client/yarn.lock
index 233e0db..9e89960 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -5950,6 +5950,7 @@ __metadata:
express: "npm:4.21.1"
husky: "npm:9.1.6"
jiti: "npm:1.21.6"
+ jotai: "npm:2.10.3"
lodash-es: "npm:4.17.21"
mapbox-gl: "npm:3.7.0"
next: "npm:14.2.15"
@@ -8876,6 +8877,21 @@ __metadata:
languageName: node
linkType: hard
+"jotai@npm:2.10.3":
+ version: 2.10.3
+ resolution: "jotai@npm:2.10.3"
+ peerDependencies:
+ "@types/react": ">=17.0.0"
+ react: ">=17.0.0"
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ react:
+ optional: true
+ checksum: 10c0/64f6536aaa91f77dacd8d9714fb846f254bfed6e5354b3005375433d72844f3ae1d6d893ee4dd423d5bddd109d393dd338e562da914605b31190989a3d47db35
+ languageName: node
+ linkType: hard
+
"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0":
version: 4.0.0
resolution: "js-tokens@npm:4.0.0"
diff --git a/cms/config/sync/admin-role.strapi-super-admin.json b/cms/config/sync/admin-role.strapi-super-admin.json
index 0cff370..7ec113e 100644
--- a/cms/config/sync/admin-role.strapi-super-admin.json
+++ b/cms/config/sync/admin-role.strapi-super-admin.json
@@ -13,7 +13,8 @@
"year",
"x_values",
"y_values",
- "layer"
+ "layer",
+ "unique_identifier"
]
},
"conditions": []
@@ -35,7 +36,8 @@
"year",
"x_values",
"y_values",
- "layer"
+ "layer",
+ "unique_identifier"
]
},
"conditions": []
@@ -50,7 +52,8 @@
"year",
"x_values",
"y_values",
- "layer"
+ "layer",
+ "unique_identifier"
]
},
"conditions": []
@@ -160,7 +163,9 @@
"dataset",
"download_link",
"chart_data",
- "chart_unit"
+ "chart_unit",
+ "show_chart_on_interaction",
+ "chart_sentence"
]
},
"conditions": []
@@ -192,7 +197,9 @@
"dataset",
"download_link",
"chart_data",
- "chart_unit"
+ "chart_unit",
+ "show_chart_on_interaction",
+ "chart_sentence"
]
},
"conditions": []
@@ -217,7 +224,9 @@
"dataset",
"download_link",
"chart_data",
- "chart_unit"
+ "chart_unit",
+ "show_chart_on_interaction",
+ "chart_sentence"
]
},
"conditions": []
diff --git a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##chart-data.chart-data.json b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##chart-data.chart-data.json
index cd14bb8..d6aed27 100644
--- a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##chart-data.chart-data.json
+++ b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##chart-data.chart-data.json
@@ -22,7 +22,7 @@
"location_code": {
"edit": {
"label": "location_code",
- "description": "",
+ "description": "Required if the chart's visibility doesn't depend on interaction with the layer. This is the case when the layer's show_chart_on_interaction attribute is false.",
"placeholder": "",
"visible": true,
"editable": true
@@ -36,7 +36,7 @@
"year": {
"edit": {
"label": "year",
- "description": "",
+ "description": "Required if the chart's visibility doesn't depend on interaction with the layer. This is the case when the layer's show_chart_on_interaction attribute is false.",
"placeholder": "",
"visible": true,
"editable": true
@@ -90,6 +90,20 @@
"sortable": true
}
},
+ "unique_identifier": {
+ "edit": {
+ "label": "unique_identifier",
+ "description": "Required if the chart's visibility depends on interaction with the layer. This is the case when the layer's show_chart_on_interaction attribute is true.",
+ "placeholder": "",
+ "visible": true,
+ "editable": true
+ },
+ "list": {
+ "label": "unique_identifier",
+ "searchable": true,
+ "sortable": true
+ }
+ },
"createdAt": {
"edit": {
"label": "createdAt",
@@ -155,16 +169,20 @@
{
"name": "layer",
"size": 6
- },
- {
- "name": "location_code",
- "size": 6
}
],
[
+ {
+ "name": "location_code",
+ "size": 4
+ },
{
"name": "year",
"size": 4
+ },
+ {
+ "name": "unique_identifier",
+ "size": 4
}
],
[
diff --git a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##dataset.dataset.json b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##dataset.dataset.json
index 8d3e28e..b9831d0 100644
--- a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##dataset.dataset.json
+++ b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##dataset.dataset.json
@@ -195,15 +195,17 @@
}
},
"layouts": {
+ "list": [
+ "id",
+ "topic",
+ "sub_topic",
+ "name"
+ ],
"edit": [
[
{
"name": "name",
"size": 6
- },
- {
- "name": "order",
- "size": 6
}
],
[
@@ -237,14 +239,13 @@
"name": "metadata",
"size": 12
}
+ ],
+ [
+ {
+ "name": "order",
+ "size": 4
+ }
]
- ],
- "list": [
- "id",
- "topic",
- "sub_topic",
- "name",
- "order"
]
},
"uid": "api::dataset.dataset"
@@ -252,4 +253,4 @@
"type": "object",
"environment": null,
"tag": null
-}
\ No newline at end of file
+}
diff --git a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##layer.layer.json b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##layer.layer.json
index 9116df2..470517b 100644
--- a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##layer.layer.json
+++ b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##layer.layer.json
@@ -147,6 +147,34 @@
"sortable": true
}
},
+ "show_chart_on_interaction": {
+ "edit": {
+ "label": "show_chart_on_interaction",
+ "description": "Whether the chart should only be shown once the user has interacted with the layer",
+ "placeholder": "",
+ "visible": true,
+ "editable": true
+ },
+ "list": {
+ "label": "show_chart_on_interaction",
+ "searchable": true,
+ "sortable": true
+ }
+ },
+ "chart_sentence": {
+ "edit": {
+ "label": "chart_sentence",
+ "description": "Sentence displayed below the chart. If show_chart_on_interaction is true, the selected feature's attributes can be accessed as follows: {name_of_attribute}.",
+ "placeholder": "",
+ "visible": true,
+ "editable": true
+ },
+ "list": {
+ "label": "chart_sentence",
+ "searchable": true,
+ "sortable": true
+ }
+ },
"createdAt": {
"edit": {
"label": "createdAt",
@@ -207,12 +235,6 @@
}
},
"layouts": {
- "list": [
- "id",
- "dataset",
- "type",
- "name"
- ],
"edit": [
[
{
@@ -263,7 +285,25 @@
"name": "chart_unit",
"size": 6
}
+ ],
+ [
+ {
+ "name": "show_chart_on_interaction",
+ "size": 6
+ }
+ ],
+ [
+ {
+ "name": "chart_sentence",
+ "size": 12
+ }
]
+ ],
+ "list": [
+ "id",
+ "dataset",
+ "type",
+ "name"
]
},
"uid": "api::layer.layer"
diff --git a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##sub-topic.sub-topic.json b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##sub-topic.sub-topic.json
index ad87547..b136cfb 100644
--- a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##sub-topic.sub-topic.json
+++ b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##sub-topic.sub-topic.json
@@ -107,6 +107,11 @@
}
},
"layouts": {
+ "list": [
+ "id",
+ "name",
+ "order"
+ ],
"edit": [
[
{
@@ -118,11 +123,6 @@
"size": 4
}
]
- ],
- "list": [
- "id",
- "name",
- "order"
]
},
"uid": "api::sub-topic.sub-topic"
@@ -130,4 +130,4 @@
"type": "object",
"environment": null,
"tag": null
-}
\ No newline at end of file
+}
diff --git a/cms/src/api/chart-data/content-types/chart-data/schema.json b/cms/src/api/chart-data/content-types/chart-data/schema.json
index 5d224e4..5536283 100644
--- a/cms/src/api/chart-data/content-types/chart-data/schema.json
+++ b/cms/src/api/chart-data/content-types/chart-data/schema.json
@@ -14,12 +14,12 @@
"attributes": {
"location_code": {
"type": "string",
- "required": true
+ "required": false
},
"year": {
"type": "integer",
"min": 0,
- "required": true
+ "required": false
},
"x_values": {
"type": "json",
@@ -34,6 +34,9 @@
"relation": "manyToOne",
"target": "api::layer.layer",
"inversedBy": "chart_data"
+ },
+ "unique_identifier": {
+ "type": "string"
}
}
}
diff --git a/cms/src/api/layer/content-types/layer/schema.json b/cms/src/api/layer/content-types/layer/schema.json
index 2a22619..2b705e9 100644
--- a/cms/src/api/layer/content-types/layer/schema.json
+++ b/cms/src/api/layer/content-types/layer/schema.json
@@ -55,6 +55,13 @@
},
"chart_unit": {
"type": "string"
+ },
+ "show_chart_on_interaction": {
+ "type": "boolean",
+ "default": false
+ },
+ "chart_sentence": {
+ "type": "text"
}
}
}
diff --git a/cms/src/extensions/documentation/documentation/1.0.0/full_documentation.json b/cms/src/extensions/documentation/documentation/1.0.0/full_documentation.json
index 3785c14..1196f28 100644
--- a/cms/src/extensions/documentation/documentation/1.0.0/full_documentation.json
+++ b/cms/src/extensions/documentation/documentation/1.0.0/full_documentation.json
@@ -14,7 +14,7 @@
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html"
},
- "x-generation-date": "2024-11-22T14:01:53.948Z"
+ "x-generation-date": "2024-11-26T13:48:27.187Z"
},
"x-strapi-config": {
"path": "/documentation",
@@ -94,8 +94,6 @@
"properties": {
"data": {
"required": [
- "location_code",
- "year",
"x_values",
"y_values"
],
@@ -119,6 +117,9 @@
}
],
"example": "string or id"
+ },
+ "unique_identifier": {
+ "type": "string"
}
}
}
@@ -173,8 +174,6 @@
"ChartData": {
"type": "object",
"required": [
- "location_code",
- "year",
"x_values",
"y_values"
],
@@ -824,6 +823,9 @@
}
}
},
+ "unique_identifier": {
+ "type": "string"
+ },
"createdAt": {
"type": "string",
"format": "date-time"
@@ -876,6 +878,12 @@
"chart_unit": {
"type": "string"
},
+ "show_chart_on_interaction": {
+ "type": "boolean"
+ },
+ "chart_sentence": {
+ "type": "string"
+ },
"createdAt": {
"type": "string",
"format": "date-time"
@@ -924,6 +932,9 @@
}
}
},
+ "unique_identifier": {
+ "type": "string"
+ },
"createdAt": {
"type": "string",
"format": "date-time"
@@ -1759,6 +1770,9 @@
}
}
},
+ "unique_identifier": {
+ "type": "string"
+ },
"createdAt": {
"type": "string",
"format": "date-time"
@@ -1811,6 +1825,12 @@
"chart_unit": {
"type": "string"
},
+ "show_chart_on_interaction": {
+ "type": "boolean"
+ },
+ "chart_sentence": {
+ "type": "string"
+ },
"createdAt": {
"type": "string",
"format": "date-time"
@@ -2082,6 +2102,12 @@
},
"chart_unit": {
"type": "string"
+ },
+ "show_chart_on_interaction": {
+ "type": "boolean"
+ },
+ "chart_sentence": {
+ "type": "string"
}
}
}
@@ -2304,6 +2330,9 @@
}
}
},
+ "unique_identifier": {
+ "type": "string"
+ },
"createdAt": {
"type": "string",
"format": "date-time"
@@ -2608,6 +2637,12 @@
"chart_unit": {
"type": "string"
},
+ "show_chart_on_interaction": {
+ "type": "boolean"
+ },
+ "chart_sentence": {
+ "type": "string"
+ },
"createdAt": {
"type": "string",
"format": "date-time"
@@ -2921,6 +2956,12 @@
"chart_unit": {
"type": "string"
},
+ "show_chart_on_interaction": {
+ "type": "boolean"
+ },
+ "chart_sentence": {
+ "type": "string"
+ },
"createdAt": {
"type": "string",
"format": "date-time"
diff --git a/cms/types/generated/components.d.ts b/cms/types/generated/components.d.ts
index 0fcf675..ff7c4e4 100644
--- a/cms/types/generated/components.d.ts
+++ b/cms/types/generated/components.d.ts
@@ -1,5 +1,24 @@
import type { Schema, Attribute } from '@strapi/strapi';
+export interface MetadataItem extends Schema.Component {
+ collectionName: 'components_metadata_item';
+ info: {
+ displayName: 'Item';
+ description: '';
+ };
+ attributes: {
+ source: Attribute.String;
+ website: Attribute.String;
+ description: Attribute.Text;
+ main_applications: Attribute.RichText;
+ temporal_coverage: Attribute.RichText;
+ spatial_resolution: Attribute.String;
+ units: Attribute.String;
+ full_name: Attribute.String & Attribute.Required;
+ temporal_resolution: Attribute.RichText;
+ };
+}
+
export interface LegendLegendConfig extends Schema.Component {
collectionName: 'components_legend_legend_configs';
info: {
@@ -35,31 +54,12 @@ export interface LegendItems extends Schema.Component {
};
}
-export interface MetadataItem extends Schema.Component {
- collectionName: 'components_metadata_item';
- info: {
- displayName: 'Item';
- description: '';
- };
- attributes: {
- source: Attribute.String;
- website: Attribute.String;
- description: Attribute.Text;
- main_applications: Attribute.RichText;
- temporal_coverage: Attribute.RichText;
- spatial_resolution: Attribute.String;
- units: Attribute.String;
- full_name: Attribute.String & Attribute.Required;
- temporal_resolution: Attribute.RichText;
- };
-}
-
declare module '@strapi/types' {
export module Shared {
export interface Components {
+ 'metadata.item': MetadataItem;
'legend.legend-config': LegendLegendConfig;
'legend.items': LegendItems;
- 'metadata.item': MetadataItem;
}
}
}
diff --git a/cms/types/generated/contentTypes.d.ts b/cms/types/generated/contentTypes.d.ts
index 72c5e1c..c552e91 100644
--- a/cms/types/generated/contentTypes.d.ts
+++ b/cms/types/generated/contentTypes.d.ts
@@ -800,9 +800,8 @@ export interface ApiChartDataChartData extends Schema.CollectionType {
draftAndPublish: false;
};
attributes: {
- location_code: Attribute.String & Attribute.Required;
+ location_code: Attribute.String;
year: Attribute.Integer &
- Attribute.Required &
Attribute.SetMinMax<
{
min: 0;
@@ -816,6 +815,7 @@ export interface ApiChartDataChartData extends Schema.CollectionType {
'manyToOne',
'api::layer.layer'
>;
+ unique_identifier: Attribute.String;
createdAt: Attribute.DateTime;
updatedAt: Attribute.DateTime;
createdBy: Attribute.Relation<
@@ -919,6 +919,8 @@ export interface ApiLayerLayer extends Schema.CollectionType {
'api::chart-data.chart-data'
>;
chart_unit: Attribute.String;
+ show_chart_on_interaction: Attribute.Boolean & Attribute.DefaultTo;
+ chart_sentence: Attribute.Text;
createdAt: Attribute.DateTime;
updatedAt: Attribute.DateTime;
createdBy: Attribute.Relation<