+
);
diff --git a/src/components/Charts/CategoricalChart.tsx b/src/components/Charts/CategoricalChart.tsx
new file mode 100644
index 00000000..24fe166a
--- /dev/null
+++ b/src/components/Charts/CategoricalChart.tsx
@@ -0,0 +1,77 @@
+'use client';
+
+import Highcharts from 'highcharts';
+import { useTheme } from 'next-themes';
+import { useEffect, useState } from 'react';
+
+import { ChartContainer } from '@/components/Charts/helpers/ChartContainer';
+import { ChartType } from '@/domain/enums/ChartType.ts';
+import CategoricalChartProps from '@/domain/props/CategoricalChartProps';
+import CategoricalChartOperations from '@/operations/charts/CategoricalChartOperations.ts';
+
+/**
+ * The `CategoricalChart` component is a box that primarily renders a title, description text, and a bar chart.
+ * It should be used to plot categorical data. For continues data please use the `LineChart` component.
+ * This component has a width of 100%, so it adjusts to the width of its parent element in which it is used.
+ * The height of the entire box depends on the provided text, while the chart itself has a fixed height.
+ * It also provides the option to open the chart in a full-screen modal, where one can download the data as well.
+ *
+ * @param data the actual data to be used in the chart
+ * @param title chart title (optional)
+ * @param description chart description text (optional)
+ * @param small when selected, all components in the line chart box become slightly smaller (optional)
+ * @param noPadding when selected, the main box has no padding on all sides (optional)
+ * @param transparentBackground when selected, the background of the entire component is transparent (optional)
+ * @param disableExpandable when selected, the functionality to open the chart in a larger modal is disabled (optional)
+ * @param disablePieChartSwitch when selected, the functionality to switch to a pie chart is disabled (optional)
+ * @param disableDownload when selected, the functionality to download the chart is disabled (optional)
+ */
+export function CategoricalChart({
+ data,
+ title,
+ description,
+ small,
+ noPadding,
+ transparentBackground,
+ disableExpandable,
+ disablePieChartSwitch,
+ disableDownload,
+}: CategoricalChartProps) {
+ const { theme } = useTheme();
+
+ // build chart options for 'Highcharts'
+ const defaultChartOptions: Highcharts.Options = CategoricalChartOperations.getHighChartOptions(data);
+
+ // controlling if a bar or pie chart is rendered; bar chart is the default
+ const [showPieChart, setShowPieChart] = useState(false);
+ const [chartOptions, setChartOptions] = useState(defaultChartOptions);
+
+ // handling the bar and pie chart switch and the theme switch;
+ useEffect(() => {
+ setChartOptions(CategoricalChartOperations.getHighChartOptions(data, showPieChart));
+ }, [showPieChart, theme]);
+
+ const alternativeSwitchButtonProps = disablePieChartSwitch
+ ? undefined
+ : {
+ defaultChartType: ChartType.COLUMN,
+ alternativeChartType: ChartType.PIE,
+ showAlternativeChart: showPieChart,
+ setShowAlternativeChart: setShowPieChart,
+ };
+
+ return (
+
+ );
+}
diff --git a/src/components/Charts/LineChart.tsx b/src/components/Charts/LineChart.tsx
index 4dd136cf..52891746 100644
--- a/src/components/Charts/LineChart.tsx
+++ b/src/components/Charts/LineChart.tsx
@@ -1,34 +1,20 @@
'use client';
-import { Button } from '@nextui-org/button';
-import { useDisclosure } from '@nextui-org/modal';
import Highcharts from 'highcharts';
-import Exporting from 'highcharts/modules/exporting';
-import OfflineExporting from 'highcharts/modules/offline-exporting';
-import HighchartsReact from 'highcharts-react-official';
-import { Maximize4 } from 'iconsax-react';
import { useTheme } from 'next-themes';
import { useEffect, useState } from 'react';
-import LineChartBarLineSwitchButton from '@/components/Charts/helpers/LineChartBarLineSwitchButton';
-import LineChartSliderButton from '@/components/Charts/helpers/LineChartSliderButton';
-import LineChartXAxisSlider from '@/components/Charts/helpers/LineChartXAxisSlider';
-import { LineChartModal } from '@/components/Charts/LineChartModal';
-import { Tooltip } from '@/components/Tooltip/Tooltip';
+import { ChartContainer } from '@/components/Charts/helpers/ChartContainer';
import { LineChartData } from '@/domain/entities/charts/LineChartData';
+import { ChartType } from '@/domain/enums/ChartType.ts';
import LineChartProps from '@/domain/props/LineChartProps';
import LineChartOperations from '@/operations/charts/LineChartOperations';
-// initialize the exporting module
-if (typeof Highcharts === 'object') {
- Exporting(Highcharts);
- OfflineExporting(Highcharts);
-}
-
/**
* The LineChart component is a box that primarily renders a title, description text, and a line chart.
+ * It should be used to plot categorical data. For continues data please use the `CategoricalChart` component.
* This component has a width of 100%, so it adjusts to the width of its parent element in which it is used.
- * The height of the line chart box depends on the provided text, while the chart itself has a fixed height.
+ * The height of the entire box depends on the provided text, while the chart itself has a fixed height.
* It also provides the option to open the chart in a full-screen modal, where one can download the data as well.
*
* The data to be displayed in the chart can be provided in different types (see `LineChartProps.data`).
@@ -38,44 +24,34 @@ if (typeof Highcharts === 'object') {
* 1. Define an interface and add it to `LineChartProps.data`.
* 2. Add another switch case in `LineChartOperations.convertToLineChartData` to convert the new interface to `LineChartData`.
*
+ * @param data the actual data to be used in the chart
* @param title chart title (optional)
* @param description chart description text (optional)
- * @param expandable when selected, the user is given the option to open the chart in a larger modal (optional)
- * @param barChartSwitch when selected, the user is given the option to switch to a bar chart (optional)
- * @param xAxisSlider when selected, the user is given the option to change the x-axis range via a slider (optional)
* @param small when selected, all components in the line chart box become slightly smaller (optional)
- * @param roundLines when selected, all plotted lines will be rounded (optional)
* @param noPadding when selected, the main box has no padding on all sides (optional)
* @param transparentBackground when selected, the background of the entire component is transparent (optional)
- * @param data the actual data to be used in the chart
+ * @param disableExpandable when selected, the functionality to open the chart in a larger modal is disabled (optional)
+ * @param disableBarChartSwitch when selected, the functionality to switch to a bar chart is disabled (optional)
+ * @param disableXAxisSlider when selected, the functionality to change the x-axis range via a slider is disabled (optional)
+ * @param disableDownload when selected, the functionality to download the chart is disabled (optional)
*/
export function LineChart({
+ data,
title,
description,
- expandable,
- barChartSwitch,
- xAxisSlider,
small,
- roundLines,
noPadding,
transparentBackground,
- data,
+ disableExpandable,
+ disableBarChartSwitch,
+ disableXAxisSlider,
+ disableDownload,
}: LineChartProps) {
- const TITLE_TEXT_SIZE = small ? 'text-sm' : 'text-md';
- const DESCRIPTION_TEXT_SIZE = small ? 'text-tiny' : 'text-sm';
- const CHART_HEIGHT = small ? 12 : 16;
- const ICON_BUTTON_SIZE = small ? 3 : 4;
- const HEADER_PADDING = title ? 3 : 0;
- const MAIN_BOX_PADDING_FACTOR = noPadding ? 0 : 1;
-
- // full screen modal state handling
- const { isOpen, onOpen, onClose, onOpenChange } = useDisclosure();
- // the 'chartOptions' are dependent on the theme
const { theme } = useTheme();
// convert data to `LineChartData` and build chart options for 'Highcharts' (line and bar chart)
const lineChartData: LineChartData = LineChartOperations.convertToLineChartData(data);
- const lineChartOptions: Highcharts.Options = LineChartOperations.getHighChartOptions(lineChartData, roundLines);
+ const lineChartOptions: Highcharts.Options = LineChartOperations.getHighChartOptions(lineChartData);
// the `selectedXAxisRange` saves the to be rendered x-axis range of the chart
// can be changed using the `LinkeChartXAxisSlider` if the param `xAxisSlider==true`
@@ -85,8 +61,6 @@ export function LineChart({
// controlling if a line or bar chart is rendered; line chart is the default
const [showBarChart, setShowBarChart] = useState(false);
const [chartOptions, setChartOptions] = useState(lineChartOptions);
- // handling the x-axis range slider visibility
- const [showXAxisSlider, setShowXAxisSlider] = useState(false);
// handling the line and bar chart switch and the theme switch;
// also handling changing the x-axis range using the `LineChartXAxisSlider`;
@@ -94,115 +68,48 @@ export function LineChart({
useEffect(() => {
if (showBarChart || selectedXAxisRange[1] - selectedXAxisRange[0] === 0) {
setChartOptions(
- LineChartOperations.getHighChartOptions(
- lineChartData,
- roundLines,
- selectedXAxisRange[0],
- selectedXAxisRange[1],
- true
- )
+ LineChartOperations.getHighChartOptions(lineChartData, selectedXAxisRange[0], selectedXAxisRange[1], true)
);
} else {
setChartOptions(
- LineChartOperations.getHighChartOptions(lineChartData, roundLines, selectedXAxisRange[0], selectedXAxisRange[1])
+ LineChartOperations.getHighChartOptions(lineChartData, selectedXAxisRange[0], selectedXAxisRange[1])
);
}
}, [showBarChart, theme, selectedXAxisRange]);
- return (
- <>
-
-
-
- {title}
-
-
- {
- // button to hide/show the slider to manipulate the plotted x-axis range of the chart;
- // can be disabled via `xAxisSlider`
- xAxisSlider && (
-
- )
- }
- {
- // button to switch between line and bar chart; can be disabled via `barChartSwitch`
- barChartSwitch && (
-
- )
- }
- {
- // button to trigger the full screen modal; rendered if `expandable`
- expandable && (
-
-
-
-
-
- )
- }
-
-
- {
- // description text element should only be rendered if description is available
- description && (
-
- {description}
-
- )
- }
- {/* the actual chart */}
-
- {
- // slider to manipulate the plotted x-axis range of the chart; can be disabled via `xAxisSlider`
- showXAxisSlider && (
-
- )
- }
-
+ // chart slider props - to manipulate the shown x-axis range
+ const sliderProps = disableXAxisSlider
+ ? undefined
+ : {
+ title: 'Adjusting x-axis range:',
+ sliderMin: 0,
+ sliderMax: xAxisLength - 1,
+ selectedSliderRange: selectedXAxisRange,
+ setSelectedSliderRange: setSelectedXAxisRange,
+ };
-
- >
+ const alternativeSwitchButtonProps = disableBarChartSwitch
+ ? undefined
+ : {
+ defaultChartType: ChartType.LINE,
+ alternativeChartType: ChartType.COLUMN,
+ showAlternativeChart: showBarChart,
+ setShowAlternativeChart: setShowBarChart,
+ };
+
+ return (
+
);
}
diff --git a/src/components/Charts/LineChartModal.tsx b/src/components/Charts/LineChartModal.tsx
deleted file mode 100644
index 3c1b9157..00000000
--- a/src/components/Charts/LineChartModal.tsx
+++ /dev/null
@@ -1,187 +0,0 @@
-import { Button } from '@nextui-org/button';
-import { Modal, ModalBody, ModalContent, ModalFooter, ModalHeader } from '@nextui-org/modal';
-import { Popover, PopoverContent, PopoverTrigger } from '@nextui-org/popover';
-import Highcharts from 'highcharts';
-import ExportDataModule from 'highcharts/modules/export-data';
-import Exporting from 'highcharts/modules/exporting';
-import OfflineExporting from 'highcharts/modules/offline-exporting';
-import HighchartsReact from 'highcharts-react-official';
-import { DocumentDownload, GalleryImport, Minus } from 'iconsax-react';
-import { useRef } from 'react';
-
-import LineChartBarLineSwitchButton from '@/components/Charts/helpers/LineChartBarLineSwitchButton';
-import LineChartSliderButton from '@/components/Charts/helpers/LineChartSliderButton';
-import LineChartXAxisSlider from '@/components/Charts/helpers/LineChartXAxisSlider';
-import { Tooltip } from '@/components/Tooltip/Tooltip';
-import LineChartModalProps from '@/domain/props/LineChartModalProps';
-import LineChartOperations from '@/operations/charts/LineChartOperations.ts';
-
-// initialize the exporting module
-if (typeof Highcharts === 'object') {
- Exporting(Highcharts);
- ExportDataModule(Highcharts);
- OfflineExporting(Highcharts);
-}
-
-/**
- * This component is tied to the `LineChart` component and should not be used independently.
- * It renders the modal, which can be opened by the user from the `LineChart` to display the chart
- * in a larger view and access additional functionalities, such as downloading the chart as a PNG.
- * For more details, please refer to the `LineChart` component.
- */
-export function LineChartModal({
- title,
- description,
- barChartSwitch,
- xAxisSlider,
- lineChartData,
- chartOptions,
- isOpen,
- onClose,
- onOpenChange,
- showXAxisSlider,
- setShowXAxisSlider,
- showBarChart,
- setShowBarChart,
- selectedXAxisRange,
- setSelectedXAxisRange,
-}: LineChartModalProps) {
- // referencing the Highcharts chart object (needed for download the chart as a png)
- const chartRef = useRef
(null);
-
- // full screen modal by the 'LineChart' component that can be opened if `expandable==true`;
- // offers a larger chart and an additional features (see buttons)
- return (
-
-
-
-
-
{title}
-
- {
- // button to hide/show the slider to manipulate the plotted x-axis range of the chart;
- // can be disabled via `xAxisSlider`
- xAxisSlider && (
-
- )
- }
- {
- // button to switch between line and bar chart; can be disabled via `barChartSwitch`
- barChartSwitch && (
-
- )
- }
-
- {/* chart download dropdown */}
-
-
-
-
-
-
-
-
-
- {
- if (chartRef.current) LineChartOperations.downloadChartPNG(chartRef.current);
- }}
- startContent={ }
- >
- Chart as PNG
-
- {
- if (chartRef.current) LineChartOperations.downloadChartDataSVG(chartRef.current);
- }}
- startContent={ }
- >
- Chart as SVG
-
- {
- if (chartRef.current) LineChartOperations.downloadChartDataCSV(chartRef.current);
- }}
- startContent={ }
- >
- Data as CSV
-
- {
- if (chartRef.current) LineChartOperations.downloadDataJSON(lineChartData);
- }}
- startContent={ }
- >
- Data as JSON
-
-
-
-
- {/* close model button */}
-
-
-
-
-
-
-
-
-
-
- {/* modal main content: description and chart */}
- {description}
-
-
-
-
- {
- // slider to manipulate the plotted x-axis range of the chart; can be disabled via `xAxisSlider`
- showXAxisSlider && (
-
-
-
-
-
- )
- }
-
-
- );
-}
diff --git a/src/components/Charts/helpers/ChartContainer.tsx b/src/components/Charts/helpers/ChartContainer.tsx
new file mode 100644
index 00000000..85d006c7
--- /dev/null
+++ b/src/components/Charts/helpers/ChartContainer.tsx
@@ -0,0 +1,139 @@
+'use client';
+
+import { Button } from '@nextui-org/button';
+import { useDisclosure } from '@nextui-org/modal';
+import Highcharts from 'highcharts';
+import HighchartsReact from 'highcharts-react-official';
+import { Maximize4 } from 'iconsax-react';
+import { useRef, useState } from 'react';
+
+import ChartAlternativeSwitchButton from '@/components/Charts/helpers/buttons/ChartAlternativeSwitchButton';
+import ChartDownloadButton from '@/components/Charts/helpers/buttons/ChartDownloadButton';
+import ChartSliderButton from '@/components/Charts/helpers/buttons/ChartSliderButton';
+import { ChartModal } from '@/components/Charts/helpers/ChartModal';
+import ChartSlider from '@/components/Charts/helpers/ChartSlider';
+import { Tooltip } from '@/components/Tooltip/Tooltip';
+import ChartContainerProps from '@/domain/props/ChartContainerProps';
+
+/**
+ * This component is the general component, which renders a box that primarily displays a title, description text, and a chart.
+ * This component has a width of 100%, so it adjusts to the width of its parent element in which it is used.
+ * The height of the entire box depends on the provided text, while the chart itself has a fixed height.
+ * It also provides the option to open the chart in a full-screen modal, where one can download the data as well.
+ *
+ * It is used by the `CategoricalChart` and `LineChart` components, which define the type of chart through the passed `chartOptions`.
+ * The main goal of this component is to prevents code redundancy between `LineChart` and `CategoricalChart`.
+ */
+export function ChartContainer({
+ chartOptions,
+ chartData,
+ title,
+ description,
+ small,
+ noPadding,
+ transparentBackground,
+ disableExpandable,
+ disableDownload,
+ alternativeSwitchButtonProps,
+ sliderProps,
+}: ChartContainerProps) {
+ const TITLE_TEXT_SIZE = small ? 'text-sm' : 'text-md';
+ const DESCRIPTION_TEXT_SIZE = small ? 'text-tiny' : 'text-sm';
+ const CHART_HEIGHT = small ? 12 : 16;
+ const ICON_BUTTON_SIZE = small ? 3 : 4;
+ const HEADER_PADDING = title ? 3 : 0;
+ const MAIN_BOX_PADDING_FACTOR = noPadding ? 0 : 1;
+
+ const chartRef = useRef(null);
+
+ // full screen modal state handling
+ const { isOpen, onOpen, onClose, onOpenChange } = useDisclosure();
+
+ // handling the x-axis range slider visibility
+ const [showSlider, setShowSlider] = useState(false);
+ return (
+ <>
+
+
+
+ {title}
+
+
+ {
+ // button to hide/show the slider to e.g. manipulate the plotted x-axis range of the chart
+ sliderProps && (
+
+ )
+ }
+ {
+ // button to switch between different chart types
+ alternativeSwitchButtonProps && (
+
+ )
+ }
+ {
+ // button to download chart as png, svg, etc.
+ !disableDownload && (
+
+ )
+ }
+ {
+ // button to trigger the full screen modal; rendered if `disableExpandable` is not selected
+ !disableExpandable && (
+
+
+
+
+
+ )
+ }
+
+
+ {
+ // description text element should only be rendered if description is available
+ description && (
+
+ {description}
+
+ )
+ }
+ {/* the actual chart */}
+
+ {
+ // slider to e.g. manipulate the plotted x-axis range of the chart
+ showSlider && sliderProps &&
+ }
+
+
+
+ >
+ );
+}
diff --git a/src/components/Charts/helpers/ChartModal.tsx b/src/components/Charts/helpers/ChartModal.tsx
new file mode 100644
index 00000000..eb10f838
--- /dev/null
+++ b/src/components/Charts/helpers/ChartModal.tsx
@@ -0,0 +1,96 @@
+import { Button } from '@nextui-org/button';
+import { Modal, ModalBody, ModalContent, ModalFooter, ModalHeader } from '@nextui-org/modal';
+import Highcharts from 'highcharts';
+import HighchartsReact from 'highcharts-react-official';
+import { Minus } from 'iconsax-react';
+import { useRef } from 'react';
+
+import ChartAlternativeSwitchButton from '@/components/Charts/helpers/buttons/ChartAlternativeSwitchButton';
+import ChartDownloadButton from '@/components/Charts/helpers/buttons/ChartDownloadButton';
+import ChartSliderButton from '@/components/Charts/helpers/buttons/ChartSliderButton';
+import ChartSlider from '@/components/Charts/helpers/ChartSlider';
+import { Tooltip } from '@/components/Tooltip/Tooltip';
+import ChartModalProps from '@/domain/props/ChartModalProps';
+
+/**
+ * This component is tied to the `ChartContainer` component and should not be used independently.
+ * It renders the modal, which can be opened by the user from the `ChartContainer`
+ * to display the chart in a larger view.
+ */
+export function ChartModal({
+ chartOptions,
+ chartData,
+ title,
+ description,
+ disableDownload,
+ isOpen,
+ onClose,
+ onOpenChange,
+ alternativeSwitchButtonProps,
+ sliderProps,
+ showSlider,
+ setShowSlider,
+}: ChartModalProps) {
+ const chartRef = useRef(null);
+
+ return (
+
+
+
+
+
{title}
+
+ {sliderProps && showSlider && setShowSlider && (
+
+ )}
+
+ {alternativeSwitchButtonProps && (
+
+ )}
+
+ {!disableDownload && }
+
+ {/* close model button */}
+
+
+
+
+
+
+
+
+
+
+ {/* modal main content: description and chart */}
+ {description}
+
+
+
+
+ {
+ // slider to e.g. manipulate the plotted x-axis range of the chart
+ sliderProps && (
+
+
+
+
+
+ )
+ }
+
+
+ );
+}
diff --git a/src/components/Charts/helpers/ChartSlider.tsx b/src/components/Charts/helpers/ChartSlider.tsx
new file mode 100644
index 00000000..97564604
--- /dev/null
+++ b/src/components/Charts/helpers/ChartSlider.tsx
@@ -0,0 +1,41 @@
+'use client';
+
+import { Slider } from '@nextui-org/slider';
+
+import { ChartSliderProps } from '@/domain/props/ChartContainerProps';
+
+/**
+ * This component is tied to the `ChartContainer` and `ChartModal` component and should not be used independently.
+ * It renders a simple NextUI slider.
+ */
+export default function ChartSlider({
+ title,
+ sliderMin,
+ sliderMax,
+ selectedSliderRange,
+ setSelectedSliderRange,
+}: ChartSliderProps) {
+ return (
+
+
{title || ''}
+ setSelectedSliderRange(e as number[])}
+ color="secondary"
+ size="sm"
+ showOutline
+ classNames={{
+ base: 'max-w-md',
+ track: 'bg-clickableSecondary h-0.5',
+ filler: 'bg-surfaceGrey',
+ step: 'bg-clickableSecondary data-[in-range=true]:bg-surfaceGrey h-1.5 w-0.5',
+ thumb: 'w-5 h-5 bg-clickableSecondary data-[dragging=true]:bg-primary',
+ }}
+ />
+
+ );
+}
diff --git a/src/components/Charts/helpers/LineChartBarLineSwitchButton.tsx b/src/components/Charts/helpers/LineChartBarLineSwitchButton.tsx
deleted file mode 100644
index 09e72e70..00000000
--- a/src/components/Charts/helpers/LineChartBarLineSwitchButton.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { Button } from '@nextui-org/button';
-import { Chart, Diagram } from 'iconsax-react';
-
-import { Tooltip } from '@/components/Tooltip/Tooltip';
-import { LineChartBarLineSwitchButtonProps } from '@/domain/props/LineChartProps';
-
-/**
- * This component is tied to the `LineChart` and `LineChartModal` component
- * and should not be used independently.
- */
-export default function LineChartBarLineSwitchButton({
- showBarChart,
- setShowBarChart,
- size,
-}: LineChartBarLineSwitchButtonProps) {
- return (
-
- setShowBarChart(!showBarChart)}>
- {showBarChart ? : }
-
-
- );
-}
diff --git a/src/components/Charts/helpers/LineChartXAxisSlider.tsx b/src/components/Charts/helpers/LineChartXAxisSlider.tsx
deleted file mode 100644
index fee2ee3d..00000000
--- a/src/components/Charts/helpers/LineChartXAxisSlider.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import { Slider } from '@nextui-org/slider';
-
-import { LineChartXAxisSlider } from '@/domain/props/LineChartProps';
-import LineChartOperations from '@/operations/charts/LineChartOperations.ts';
-
-/**
- * This component is tied to the `LineChart` and `LineChartModal` component
- * and should not be used independently.
- * It renders a NextUI slider with which the shown x-axis range of the chart
- * within `LineChart` and `LineChartModal` can be manipulated.
- */
-export default function LineChartXAxisSlider({
- selectedXAxisRange,
- setSelectedXAxisRange,
- data,
-}: LineChartXAxisSlider) {
- const xAxisValues: number[] = LineChartOperations.getDistinctXAxisValues(data);
-
- return (
-
-
Adjusting x-axis range:
- setSelectedXAxisRange(e as number[])}
- color="secondary"
- size="sm"
- showOutline
- classNames={{
- base: 'max-w-md',
- track: 'bg-clickableSecondary h-0.5',
- filler: 'bg-surfaceGrey',
- step: 'bg-clickableSecondary data-[in-range=true]:bg-surfaceGrey h-1.5 w-0.5',
- thumb: 'w-5 h-5 bg-clickableSecondary data-[dragging=true]:bg-primary',
- }}
- />
-
- );
-}
diff --git a/src/components/Charts/helpers/buttons/ChartAlternativeSwitchButton.tsx b/src/components/Charts/helpers/buttons/ChartAlternativeSwitchButton.tsx
new file mode 100644
index 00000000..962e965f
--- /dev/null
+++ b/src/components/Charts/helpers/buttons/ChartAlternativeSwitchButton.tsx
@@ -0,0 +1,33 @@
+import { Button } from '@nextui-org/button';
+import React from 'react';
+
+import { Tooltip } from '@/components/Tooltip/Tooltip';
+import { ChartAlternativeSwitchButtonProps } from '@/domain/props/ChartContainerProps';
+import ChartDownloadButtonOperations from '@/operations/charts/ChartAlternativeSwitchButtonOperations';
+
+/**
+ * This component is tied to the `ChartContainer` and `ChartModal` component and should not be used independently.
+ */
+export default function ChartAlternativeSwitchButton({
+ defaultChartType,
+ alternativeChartType,
+ showAlternativeChart,
+ setShowAlternativeChart,
+ size = 4,
+}: ChartAlternativeSwitchButtonProps) {
+ const chartSwitchTitle = showAlternativeChart
+ ? ChartDownloadButtonOperations.getChartTypeTitle(defaultChartType)
+ : ChartDownloadButtonOperations.getChartTypeTitle(alternativeChartType);
+
+ const chartSwitchIcon = showAlternativeChart
+ ? ChartDownloadButtonOperations.getChartTypeIcon(defaultChartType, size)
+ : ChartDownloadButtonOperations.getChartTypeIcon(alternativeChartType, size);
+
+ return (
+
+ setShowAlternativeChart(!showAlternativeChart)}>
+ {chartSwitchIcon}
+
+
+ );
+}
diff --git a/src/components/Charts/helpers/buttons/ChartDownloadButton.tsx b/src/components/Charts/helpers/buttons/ChartDownloadButton.tsx
new file mode 100644
index 00000000..d36f76d7
--- /dev/null
+++ b/src/components/Charts/helpers/buttons/ChartDownloadButton.tsx
@@ -0,0 +1,71 @@
+import { Button } from '@nextui-org/button';
+import { Popover, PopoverContent, PopoverTrigger } from '@nextui-org/popover';
+import { DocumentDownload, GalleryImport } from 'iconsax-react';
+
+import { Tooltip } from '@/components/Tooltip/Tooltip';
+import { ChartDownloadButtonProps } from '@/domain/props/ChartContainerProps';
+import ChartDownloadButtonOperations from '@/operations/charts/ChartDownloadButtonOperations.ts';
+
+/**
+ * This component is tied to the `ChartContainer` and `ChartModal` component and should not be used independently.
+ * It renders a button to open a dropdown menu to download the chart as csv, png, etc.
+ */
+export default function ChartDownloadButton({ chartRef, chartData, size }: ChartDownloadButtonProps) {
+ return (
+
+
+
+
+
+
+
+
+
+ {
+ if (chartRef.current) ChartDownloadButtonOperations.downloadChartPNG(chartRef.current);
+ }}
+ startContent={ }
+ >
+ Chart as PNG
+
+ {
+ if (chartRef.current) ChartDownloadButtonOperations.downloadChartDataSVG(chartRef.current);
+ }}
+ startContent={ }
+ >
+ Chart as SVG
+
+ {
+ if (chartRef.current) ChartDownloadButtonOperations.downloadChartDataCSV(chartRef.current);
+ }}
+ startContent={ }
+ >
+ Data as CSV
+
+ {
+ ChartDownloadButtonOperations.downloadDataJSON(chartData);
+ }}
+ startContent={ }
+ >
+ Data as JSON
+
+
+
+ );
+}
diff --git a/src/components/Charts/helpers/LineChartSliderButton.tsx b/src/components/Charts/helpers/buttons/ChartSliderButton.tsx
similarity index 52%
rename from src/components/Charts/helpers/LineChartSliderButton.tsx
rename to src/components/Charts/helpers/buttons/ChartSliderButton.tsx
index b68dae90..b9337eaa 100644
--- a/src/components/Charts/helpers/LineChartSliderButton.tsx
+++ b/src/components/Charts/helpers/buttons/ChartSliderButton.tsx
@@ -2,17 +2,12 @@ import { Button } from '@nextui-org/button';
import { Settings } from 'iconsax-react';
import { Tooltip } from '@/components/Tooltip/Tooltip';
-import { LineChartSliderButtonProps } from '@/domain/props/LineChartProps';
+import { LineChartSliderButtonProps } from '@/domain/props/ChartContainerProps';
/**
- * This component is tied to the `LineChart` and `LineChartModal` component
- * and should not be used independently.
+ * This component is tied to the `ChartContainer` and `ChartModal` component and should not be used independently.
*/
-export default function LineChartSliderButton({
- showXAxisSlider,
- setShowXAxisSlider,
- size,
-}: LineChartSliderButtonProps) {
+export default function ChartSliderButton({ showSlider, setShowSlider, size }: LineChartSliderButtonProps) {
return (
{
- setShowXAxisSlider(!showXAxisSlider);
+ setShowSlider(!showSlider);
}}
>
diff --git a/src/components/HungerAlert/HungerAlertSkeleton.tsx b/src/components/HungerAlert/HungerAlertSkeleton.tsx
index e75b39a3..b92c92ff 100644
--- a/src/components/HungerAlert/HungerAlertSkeleton.tsx
+++ b/src/components/HungerAlert/HungerAlertSkeleton.tsx
@@ -4,7 +4,7 @@ export default function HungerAlertSkeleton() {
return (
diff --git a/src/components/Legend/GradientLegend.tsx b/src/components/Legend/GradientLegend.tsx
index 79a673ce..b7f9e7d5 100644
--- a/src/components/Legend/GradientLegend.tsx
+++ b/src/components/Legend/GradientLegend.tsx
@@ -1,13 +1,20 @@
+import clsx from 'clsx';
+
+import { ColorsData } from '@/domain/props/ColorsData';
import GradientLegendProps from '@/domain/props/GradientLegendProps';
-export default function GradientLegend({ colors, startLabel, endLabel, hasNotAnalyzedPoint }: GradientLegendProps) {
- const gradients: string = colors
- .map((color: string, index: number) => {
- const percentage = (index / (colors.length - 1)) * 100;
- return `hsl(var(--nextui-${color})) ${percentage}%`;
+import { Tooltip } from '../Tooltip/Tooltip';
+
+export default function GradientLegend({ colorsData, startLabel, endLabel, hasNotAnalyzedPoint }: GradientLegendProps) {
+ const gradients: string = colorsData
+ .map((colorData: ColorsData, index: number) => {
+ const percentage = (index / (colorsData.length - 1)) * 100;
+ return `hsl(var(--nextui-${colorData.color})) ${percentage}%`;
})
.join(', ');
+ const segmentWidth: number = 100 / colorsData.length;
+
return (
{hasNotAnalyzedPoint && (
@@ -20,12 +27,31 @@ export default function GradientLegend({ colors, startLabel, endLabel, hasNotAna
)}
-
+
+ {colorsData.map((colorData, index) => (
+
+ ))}
+
+
{startLabel}
{endLabel}
diff --git a/src/components/Legend/LegendContainer.tsx b/src/components/Legend/LegendContainer.tsx
index d3ff0835..11e02c07 100644
--- a/src/components/Legend/LegendContainer.tsx
+++ b/src/components/Legend/LegendContainer.tsx
@@ -19,7 +19,7 @@ export default function LegendContainer({ items, loading = false }: LegendContai
const [showInfoPopup, setInfoPopup] = useState(false);
return !isMobile ? (
-
+
import('@/components/Legend/MapLegend'), {
+ ssr: false,
+ loading: () => ,
+});
+
+export default function MapLegendLoader() {
+ return ;
+}
diff --git a/src/components/Legend/MapLegendSkeleton.tsx b/src/components/Legend/MapLegendSkeleton.tsx
new file mode 100644
index 00000000..410cd473
--- /dev/null
+++ b/src/components/Legend/MapLegendSkeleton.tsx
@@ -0,0 +1,26 @@
+import { Skeleton } from '@nextui-org/skeleton';
+
+export default function MapLegendSkeleton() {
+ return (
+ <>
+ {/* Desktop version */}
+
+
+ {/* Mobile version */}
+
+ >
+ );
+}
diff --git a/src/components/Map/Alerts/ConflictLayer.tsx b/src/components/Map/Alerts/ConflictLayer.tsx
index c8f95370..14712a68 100644
--- a/src/components/Map/Alerts/ConflictLayer.tsx
+++ b/src/components/Map/Alerts/ConflictLayer.tsx
@@ -19,7 +19,6 @@ export function ConflictLayer() {
data &&
(Object.keys(conflictsByType) as ConflictType[]).map((conflictType) => (
ConflictOperations.createClusterCustomIcon(cluster, conflictType)}
showCoverageOnHover={false}
diff --git a/src/components/Map/Alerts/HazardLayer.tsx b/src/components/Map/Alerts/HazardLayer.tsx
index fdbf5203..532a016a 100644
--- a/src/components/Map/Alerts/HazardLayer.tsx
+++ b/src/components/Map/Alerts/HazardLayer.tsx
@@ -26,7 +26,6 @@ export function HazardLayer() {
zoomToBoundsOnClick
maxClusterRadius={60}
spiderfyOnMaxZoom={false}
- animate={false}
>
{hazardsByType[hazardType].map((hazard) => (
diff --git a/src/components/Map/BackToGlobalButton.tsx b/src/components/Map/BackToGlobalButton.tsx
index 59bb6a18..0b4437af 100644
--- a/src/components/Map/BackToGlobalButton.tsx
+++ b/src/components/Map/BackToGlobalButton.tsx
@@ -8,14 +8,13 @@ import { useAccordionsModal } from '@/domain/contexts/AccodionsModalContext';
import { useSelectedCountryId } from '@/domain/contexts/SelectedCountryIdContext';
export default function BackToGlobalButton() {
- const { selectedCountryId, setSelectedCountryId } = useSelectedCountryId();
+ const { selectedCountryId } = useSelectedCountryId();
const { clearAccordionModal } = useAccordionsModal();
const map = useMap();
const handleBackButtonClick = (): void => {
map.zoomOut(4);
clearAccordionModal();
- setSelectedCountryId(null);
};
return selectedCountryId ? (
diff --git a/src/components/Map/FcsAccordion.tsx b/src/components/Map/FcsAccordion.tsx
index 276f735a..b7a32e8a 100644
--- a/src/components/Map/FcsAccordion.tsx
+++ b/src/components/Map/FcsAccordion.tsx
@@ -81,8 +81,6 @@ export default function FcsAccordion({ countryData, loading, countryIso3Data, co
{currencyExchangeChartData ? (
-
+
) : (
No data about currency exchange
)}
@@ -156,14 +145,7 @@ export default function FcsAccordion({ countryData, loading, countryIso3Data, co
content: (
{balanceOfTradeChartData ? (
-
+
) : (
No data about balance of trade
)}
@@ -177,15 +159,7 @@ export default function FcsAccordion({ countryData, loading, countryIso3Data, co
content: (
{headlineAndFoodInflationChartData ? (
-
+
) : (
No data about headline and food inflation
)}
diff --git a/src/components/Map/FcsChoropleth.tsx b/src/components/Map/FcsChoropleth.tsx
index eaeadcd5..80b41801 100644
--- a/src/components/Map/FcsChoropleth.tsx
+++ b/src/components/Map/FcsChoropleth.tsx
@@ -4,12 +4,13 @@ import { useTheme } from 'next-themes';
import React, { useEffect, useRef } from 'react';
import { GeoJSON } from 'react-leaflet';
+import { useSelectedCountryId } from '@/domain/contexts/SelectedCountryIdContext';
import { CountryMapData } from '@/domain/entities/country/CountryMapData.ts';
import { LayerWithFeature } from '@/domain/entities/map/LayerWithFeature.ts';
import FcsChoroplethProps from '@/domain/props/FcsChoroplethProps';
import { AccessibilityOperations } from '@/operations/map/AccessibilityOperations';
import FcsChoroplethOperations from '@/operations/map/FcsChoroplethOperations';
-import { MapboxMapOperations } from '@/operations/map/MapboxMapOperations';
+import { MapOperations } from '@/operations/map/MapOperations';
import CountryLoadingLayer from './CountryLoading';
import FscCountryChoropleth from './FcsCountryChoropleth';
@@ -17,8 +18,6 @@ import FscCountryChoropleth from './FcsCountryChoropleth';
export default function FcsChoropleth({
data,
countryId,
- selectedCountryId,
- setSelectedCountryId,
loading,
regionData,
countryData,
@@ -27,6 +26,7 @@ export default function FcsChoropleth({
fcsData,
}: FcsChoroplethProps) {
const geoJsonRef = useRef
(null);
+ const { selectedCountryId, setSelectedCountryId } = useSelectedCountryId();
const { theme } = useTheme();
const handleBackClick = () => {
@@ -39,8 +39,8 @@ export default function FcsChoropleth({
geoJsonRef.current.eachLayer((layer: LayerWithFeature) => {
if (!layer) return;
const feature = layer.feature as Feature;
- if (FcsChoroplethOperations.checkIfActive(data.features[0] as CountryMapData, fcsData)) {
- const tooltipContainer = MapboxMapOperations.createCountryNameTooltipElement(feature?.properties?.adm0_name);
+ if (FcsChoroplethOperations.checkIfActive(feature as CountryMapData, fcsData)) {
+ const tooltipContainer = MapOperations.createCountryNameTooltipElement(feature?.properties?.adm0_name);
layer.bindTooltip(tooltipContainer, { className: 'leaflet-tooltip', sticky: true });
} else {
layer.unbindTooltip();
@@ -71,7 +71,7 @@ export default function FcsChoropleth({
data={data}
style={FcsChoroplethOperations.countryStyle(data.features[0], theme === 'dark', fcsData)}
onEachFeature={(feature, layer) =>
- FcsChoroplethOperations.onEachFeature(feature, layer, setSelectedCountryId, theme === 'dark', fcsData)
+ FcsChoroplethOperations.onEachFeature(feature, layer, setSelectedCountryId, fcsData)
}
/>
)}
diff --git a/src/components/Map/FcsRegionTooltip.tsx b/src/components/Map/FcsRegionTooltip.tsx
index 367ee1e7..3ad1ad99 100644
--- a/src/components/Map/FcsRegionTooltip.tsx
+++ b/src/components/Map/FcsRegionTooltip.tsx
@@ -53,6 +53,10 @@ export default function FcsRegionTooltip({ feature }: FcsRegionTooltipProps) {
title="Number of people with insufficient food consumption"
data={FcsRegionTooltipOperations.getFcsChartData(feature.properties.fcsGraph)}
small
+ disableDownload
+ disableBarChartSwitch
+ disableXAxisSlider
+ disableExpandable
/>
)}
diff --git a/src/components/Map/IpcMap/IpcChoropleth.tsx b/src/components/Map/IpcMap/IpcChoropleth.tsx
index 8fc4d444..5dcb627a 100644
--- a/src/components/Map/IpcMap/IpcChoropleth.tsx
+++ b/src/components/Map/IpcMap/IpcChoropleth.tsx
@@ -2,25 +2,16 @@ import { FeatureCollection, GeoJsonProperties, Geometry } from 'geojson';
import React from 'react';
import CountryLoadingLayer from '@/components/Map/CountryLoading';
+import { useSelectedCountryId } from '@/domain/contexts/SelectedCountryIdContext';
import { useIpcQuery } from '@/domain/hooks/globalHooks';
import { IpcChoroplethProps } from '@/domain/props/IpcChoroplethProps';
import IpcCountryChoropleth from './IpcCountryChoropleth';
import IpcGlobalChoropleth from './IpcGlobalChoropleth';
-function IpcChoropleth({
- countries,
- selectedCountryId,
- setSelectedCountryId,
- countryData,
- ipcRegionData,
- selectedCountryName,
-}: IpcChoroplethProps) {
- const handleBackClick = () => {
- setSelectedCountryId(null);
- };
-
+function IpcChoropleth({ countries, countryData, ipcRegionData, selectedCountryName }: IpcChoroplethProps) {
const { data: ipcData } = useIpcQuery(true);
+ const { selectedCountryId, setSelectedCountryId } = useSelectedCountryId();
return (
<>
@@ -50,7 +41,6 @@ function IpcChoropleth({
regionIpcData={ipcRegionData}
countryData={countryData}
countryName={selectedCountryName}
- handleBackButtonClick={handleBackClick}
/>
)}
>
diff --git a/src/components/Map/Map.tsx b/src/components/Map/Map.tsx
index e286f3da..77567b5a 100644
--- a/src/components/Map/Map.tsx
+++ b/src/components/Map/Map.tsx
@@ -3,33 +3,37 @@ import 'leaflet/dist/leaflet.css';
import { Feature, FeatureCollection, GeoJSON, GeoJsonProperties, Geometry } from 'geojson';
import L, { Map as LeafletMap } from 'leaflet';
import { useEffect, useRef, useState } from 'react';
-import { MapContainer } from 'react-leaflet';
+import { GeoJSON as LeafletGeoJSON, MapContainer, Pane, SVGOverlay, TileLayer } from 'react-leaflet';
import BackToGlobalButton from '@/components/Map/BackToGlobalButton';
-import { MAP_MAX_ZOOM, MAP_MIN_ZOOM } from '@/domain/constant/map/Map';
+import {
+ countryBaseStyle,
+ countryBorderStyle,
+ disputedAreaStyle,
+ MAP_MAX_ZOOM,
+ MAP_MIN_ZOOM,
+ oceanBounds,
+} from '@/domain/constant/map/Map';
import { useSelectedAlert } from '@/domain/contexts/SelectedAlertContext';
import { useSelectedCountryId } from '@/domain/contexts/SelectedCountryIdContext';
import { useSelectedMap } from '@/domain/contexts/SelectedMapContext';
-import { useSelectedMapVisibility } from '@/domain/contexts/SelectedMapVisibilityContext';
import { useSidebar } from '@/domain/contexts/SidebarContext';
import { CountryData } from '@/domain/entities/country/CountryData.ts';
import { CountryIso3Data } from '@/domain/entities/country/CountryIso3Data.ts';
import { CountryMapData } from '@/domain/entities/country/CountryMapData.ts';
import { GlobalInsight } from '@/domain/enums/GlobalInsight';
import { MapProps } from '@/domain/props/MapProps';
-import { MapOperations } from '@/operations/map/MapOperations.ts';
+import { MapOperations } from '@/operations/map/MapOperations';
import { AlertContainer } from './Alerts/AlertContainer';
import FcsChoropleth from './FcsChoropleth';
import IpcChoropleth from './IpcMap/IpcChoropleth';
import NutritionChoropleth from './NutritionChoropleth';
-import VectorTileLayer from './VectorTileLayer';
import ZoomControl from './ZoomControl';
export default function Map({ countries, disputedAreas, fcsData, alertData }: MapProps) {
const mapRef = useRef
(null);
const { selectedMapType } = useSelectedMap();
- const { setSelectedMapVisibility } = useSelectedMapVisibility();
const { resetAlert } = useSelectedAlert();
const { selectedCountryId, setSelectedCountryId } = useSelectedCountryId();
const { closeSidebar } = useSidebar();
@@ -43,8 +47,6 @@ export default function Map({ countries, disputedAreas, fcsData, alertData }: Ma
const [selectedCountryName, setSelectedCountryName] = useState(undefined);
const onZoomThresholdReached = () => {
- setSelectedCountryId(null);
- setSelectedMapVisibility(true);
MapOperations.resetSelectedCountryData(
setRegionData,
setCountryData,
@@ -52,13 +54,11 @@ export default function Map({ countries, disputedAreas, fcsData, alertData }: Ma
setRegionNutritionData,
setIpcRegionData
);
+ setSelectedCountryId(null);
};
useEffect(() => {
if (selectedCountryId) {
- setSelectedMapVisibility(
- selectedMapType === GlobalInsight.VEGETATION || selectedMapType === GlobalInsight.RAINFALL
- );
closeSidebar();
resetAlert();
@@ -84,6 +84,9 @@ export default function Map({ countries, disputedAreas, fcsData, alertData }: Ma
setRegionNutritionData,
setIpcRegionData
);
+ window.gtag('event', `${selectedCountryData.properties.iso3}_country_selected`, {
+ selectedMap: selectedMapType,
+ });
setSelectedCountryName(selectedCountryData.properties.adm0_name);
mapRef.current?.fitBounds(L.geoJSON(selectedCountryData as GeoJSON).getBounds(), { animate: true });
}
@@ -103,54 +106,92 @@ export default function Map({ countries, disputedAreas, fcsData, alertData }: Ma
maxZoom={MAP_MAX_ZOOM}
maxBoundsViscosity={1.0}
zoomControl={false}
- markerZoomAnimation={false}
- zoomAnimation={false}
style={{ height: '100%', width: '100%', zIndex: 1 }}
>
- {countries && }
- {selectedMapType === GlobalInsight.FOOD &&
- countries.features &&
+
+
+
+
+
+
+
+
+
+
+
+ {selectedMapType === GlobalInsight.FOOD && countries.features && (
+ <>
+ {countries.features.map((country) => (
+ ] }}
+ loading={countryClickLoading}
+ countryData={countryData}
+ countryIso3Data={countryIso3Data}
+ regionData={regionData}
+ selectedCountryName={selectedCountryName}
+ fcsData={fcsData}
+ />
+ ))}
+ {!selectedCountryId && (
+
+
+
+ )}
+ >
+ )}
+
+ {selectedMapType === GlobalInsight.NUTRITION &&
countries.features.map((country) => (
- ] }}
- selectedCountryId={selectedCountryId}
- setSelectedCountryId={setSelectedCountryId}
- loading={countryClickLoading}
- countryData={countryData}
- countryIso3Data={countryIso3Data}
- regionData={regionData}
+ regionNutritionData={regionNutritionData}
selectedCountryName={selectedCountryName}
- fcsData={fcsData}
/>
))}
+ {selectedMapType === GlobalInsight.VEGETATION && (
+
+
+
+ )}
+
+ {selectedMapType === GlobalInsight.RAINFALL && (
+
+
+
+ )}
+
{selectedMapType === GlobalInsight.IPC && (
)}
- {selectedMapType === GlobalInsight.NUTRITION &&
- countries.features &&
- countries.features.map((country) => (
- ] }}
- selectedCountryId={selectedCountryId}
- setSelectedCountryId={setSelectedCountryId}
- regionNutritionData={regionNutritionData}
- selectedCountryName={selectedCountryName}
- />
- ))}
+
+
+
+
+
+
+
diff --git a/src/components/Map/MapSkeleton.tsx b/src/components/Map/MapSkeleton.tsx
index b8999cd3..f6b73bef 100644
--- a/src/components/Map/MapSkeleton.tsx
+++ b/src/components/Map/MapSkeleton.tsx
@@ -1,5 +1,11 @@
+import ZoomControlSkeleton from '@/components/Map/ZoomControlSkeleton';
import MapSkeletonData from '@/domain/constant/map/MapSkeletonData';
export default function MapSkeleton() {
- return ;
+ return (
+
+
+
+
+ );
}
diff --git a/src/components/Map/NutritionChoropleth.tsx b/src/components/Map/NutritionChoropleth.tsx
index a6fa9ce1..3dd4d627 100644
--- a/src/components/Map/NutritionChoropleth.tsx
+++ b/src/components/Map/NutritionChoropleth.tsx
@@ -4,11 +4,12 @@ import { useTheme } from 'next-themes';
import React, { useEffect, useRef } from 'react';
import { GeoJSON } from 'react-leaflet';
+import { useSelectedCountryId } from '@/domain/contexts/SelectedCountryIdContext';
import { CountryMapData } from '@/domain/entities/country/CountryMapData.ts';
import { LayerWithFeature } from '@/domain/entities/map/LayerWithFeature.ts';
import { useNutritionQuery } from '@/domain/hooks/globalHooks';
import NutritionChoroplethProps from '@/domain/props/NutritionChoroplethProps';
-import { MapboxMapOperations } from '@/operations/map/MapboxMapOperations';
+import { MapOperations } from '@/operations/map/MapOperations';
import NutritionChoroplethOperations from '@/operations/map/NutritionChoroplethOperations';
import CountryLoadingLayer from './CountryLoading';
@@ -17,12 +18,11 @@ import NutritionStateChoropleth from './NutritionStateChoropleth';
export default function NutritionChoropleth({
data,
countryId,
- selectedCountryId,
- setSelectedCountryId,
regionNutritionData,
selectedCountryName,
}: NutritionChoroplethProps) {
const geoJsonRef = useRef(null);
+ const { selectedCountryId, setSelectedCountryId } = useSelectedCountryId();
const { theme } = useTheme();
const { data: nutritionData } = useNutritionQuery(true);
@@ -34,7 +34,7 @@ export default function NutritionChoropleth({
if (!layer) return;
const feature = layer.feature as Feature;
if (NutritionChoroplethOperations.checkIfActive(data.features[0] as CountryMapData, nutritionData)) {
- const tooltipContainer = MapboxMapOperations.createCountryNameTooltipElement(feature?.properties?.adm0_name);
+ const tooltipContainer = MapOperations.createCountryNameTooltipElement(feature?.properties?.adm0_name);
layer.bindTooltip(tooltipContainer, { className: 'leaflet-tooltip', sticky: true });
} else {
layer.unbindTooltip();
diff --git a/src/components/Map/VectorTileLayer.tsx b/src/components/Map/VectorTileLayer.tsx
deleted file mode 100644
index 66d13f43..00000000
--- a/src/components/Map/VectorTileLayer.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import 'mapbox-gl/dist/mapbox-gl.css';
-
-import { LeafletContextInterface, useLeafletContext } from '@react-leaflet/core';
-import mapboxgl from 'mapbox-gl';
-import { useTheme } from 'next-themes';
-import React, { RefObject, useEffect, useRef, useState } from 'react';
-
-import { useSelectedMap } from '@/domain/contexts/SelectedMapContext';
-import { useSelectedMapVisibility } from '@/domain/contexts/SelectedMapVisibilityContext';
-import { VectorTileLayerProps } from '@/domain/props/VectorTileLayerProps';
-import { MapboxMapOperations } from '@/operations/map/MapboxMapOperations';
-
-export default function VectorTileLayer({ countries, disputedAreas }: VectorTileLayerProps) {
- const { theme } = useTheme();
- const context: LeafletContextInterface = useLeafletContext();
- const mapContainer: RefObject = useRef(null);
- const { selectedMapType } = useSelectedMap();
- const [map, setMap] = useState();
- const { selectedMapVisibility } = useSelectedMapVisibility();
-
- mapboxgl.accessToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN as string;
-
- useEffect(() => {
- const baseMap: mapboxgl.Map = MapboxMapOperations.createMapboxMap(
- theme === 'dark',
- { countries, disputedAreas },
- mapContainer
- );
- baseMap.on('load', () => {
- setMap(baseMap);
- });
-
- MapboxMapOperations.synchronizeLeafletMapbox(baseMap, mapContainer, context);
- MapboxMapOperations.initDisputedLayer(baseMap);
- // The following layers currently don't work due to CORS issues.
- MapboxMapOperations.initRainfallLayer(baseMap);
- MapboxMapOperations.initVegetationLayer(baseMap);
- MapboxMapOperations.initFCSLayer(baseMap);
-
- return () => {
- baseMap.remove();
- context.map.off('move');
- setMap(undefined);
- };
- }, [context]);
-
- useEffect(() => {
- if (map && selectedMapVisibility) {
- MapboxMapOperations.removeActiveMapLayer(map, theme === 'dark');
- MapboxMapOperations.addMapAsLayer(map, selectedMapType);
- } else if (map) {
- MapboxMapOperations.removeActiveMapLayer(map, theme === 'dark');
- }
- }, [map, selectedMapType, selectedMapVisibility]);
-
- useEffect(() => {
- if (map) {
- MapboxMapOperations.changeMapTheme(map, theme === 'dark');
- }
- }, [theme]);
-
- return
;
-}
diff --git a/src/components/Map/ZoomControlSkeleton.tsx b/src/components/Map/ZoomControlSkeleton.tsx
new file mode 100644
index 00000000..65390e9b
--- /dev/null
+++ b/src/components/Map/ZoomControlSkeleton.tsx
@@ -0,0 +1,9 @@
+import { Skeleton } from '@nextui-org/skeleton';
+
+export default function ZoomControlSkeleton() {
+ return (
+
+
+
+ );
+}
diff --git a/src/components/Subscribe/NestedPopover.tsx b/src/components/Subscribe/NestedPopover.tsx
index bcdffef2..724421a1 100644
--- a/src/components/Subscribe/NestedPopover.tsx
+++ b/src/components/Subscribe/NestedPopover.tsx
@@ -82,9 +82,20 @@ export function NestedPopover({ items, onSelectionChange }: NestedPopoverProps)
* take care of nested menu open state
* @param itemId is the id of the nested menu item
*/
- const handleNestedMenuToggle = (itemId: string): void => {
- setOpenNestedMenu(openNestedMenu === itemId ? null : itemId);
- setIsNestedOpen(true);
+ const hoverNestedMenuItem = (itemId: string): void => {
+ const hoveredItem = items.find((it) => it.topic_id === itemId);
+ if (hoveredItem?.options && hoveredItem.options.length > 0) {
+ setOpenNestedMenu(itemId);
+ setIsNestedOpen(true);
+ }
+ };
+
+ const hoverMainMenuItem = (itemId: string): void => {
+ const hoveredMainItem = items.find((it) => it.topic_id === itemId);
+ if (hoveredMainItem?.options === undefined || hoveredMainItem?.options.length === 0) {
+ setOpenNestedMenu(null);
+ setIsNestedOpen(false);
+ }
};
const ifOptionsSelected = (option: IOption): boolean => {
@@ -132,6 +143,7 @@ export function NestedPopover({ items, onSelectionChange }: NestedPopoverProps)
key={item.topic_id}
className="m-1 h-10 text-left text-gray-700 hover:bg-blue-100 hover:text-black dark:text-gray-300 dark:hover:bg-gray-700 dark:hover:text-white p-2"
onClick={() => selectMainMenuItem(item)}
+ onMouseEnter={() => hoverMainMenuItem(item.topic_id)}
>
{item.topic_description}
@@ -145,18 +157,17 @@ export function NestedPopover({ items, onSelectionChange }: NestedPopoverProps)
handleNestedMenuToggle(item.topic_id)}
- onMouseEnter={() => handleNestedMenuToggle(item.topic_id)}
+ onMouseEnter={() => hoverNestedMenuItem(item.topic_id)}
>
{item.topic_description}
{/* nested menu items */}
- {isNestedOpen && openNestedMenu?.includes(item.topic_id) && (
+ {isNestedOpen && openNestedMenu === item.topic_id && (
{item.options?.map((option) => (
diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx
index b1b532a0..231b9ac6 100644
--- a/src/components/Tooltip/Tooltip.tsx
+++ b/src/components/Tooltip/Tooltip.tsx
@@ -1,4 +1,5 @@
import { Tooltip as NextUITooltip } from '@nextui-org/tooltip';
+import clsx from 'clsx';
import TooltipProps from '@/domain/props/TooltipProps';
@@ -11,9 +12,11 @@ import TooltipProps from '@/domain/props/TooltipProps';
* @param text textual content of the tooltip
* @param delay delay with which tooltip appears on hover in milliseconds; default is 0
* @param warning selected if the tooltip should be highlighted (optional)
+ * @param titleStyle tailwind classes to style the title (optional)
+ * @param textStyle tailwind classes to style the text (optional)
* @constructor
*/
-export function Tooltip({ children, title, text, delay, warning }: TooltipProps) {
+export function Tooltip({ children, title, text, delay, warning, titleStyle, textStyle }: TooltipProps) {
const OFFSET: number = 10;
const RADIUS = 'sm';
const SHADOW = 'md';
@@ -23,11 +26,11 @@ export function Tooltip({ children, title, text, delay, warning }: TooltipProps)
const tooltipContent = title ? (
-
{title}
-
{text}
+
{title}
+
{text}
) : (
- {text}
+ {text}
);
return (
diff --git a/src/components/Topbar/Topbar.tsx b/src/components/Topbar/Topbar.tsx
index f39bec6f..73620365 100644
--- a/src/components/Topbar/Topbar.tsx
+++ b/src/components/Topbar/Topbar.tsx
@@ -24,15 +24,15 @@ export function Topbar() {
return (
-
+
-
+
{pageLinks.map((item) => (
diff --git a/src/domain/constant/PageLinks.ts b/src/domain/constant/PageLinks.ts
index 89d932f5..c56eb073 100644
--- a/src/domain/constant/PageLinks.ts
+++ b/src/domain/constant/PageLinks.ts
@@ -5,4 +5,5 @@ export const pageLinks = [
{ href: '/wiki', label: 'Wiki' },
{ href: '/disclaimer', label: 'Disclaimer' },
{ href: '/download-portal', label: 'Download Portal' },
+ { href: '/comparison-portal', label: 'Comparison Portal' },
];
diff --git a/src/domain/constant/legend/mapLegendData.tsx b/src/domain/constant/legend/mapLegendData.tsx
index 17cdb644..6dff0678 100644
--- a/src/domain/constant/legend/mapLegendData.tsx
+++ b/src/domain/constant/legend/mapLegendData.tsx
@@ -106,7 +106,15 @@ export function mapLegendData(
case GlobalInsight.FOOD:
legendData.push({
title: 'Prevalence of insufficient food consumption',
- colors: ['fcsGradient1', 'fcsGradient2', 'fcsGradient3', 'fcsGradient4', 'fcsGradient5', 'fcsGradient6'],
+ colorsData: [
+ { color: 'fcsGradient1', title: 'Very Low', value: '0-5%' },
+ { color: 'fcsGradient2', title: 'Low', value: '5-10%' },
+ { color: 'fcsGradient3', title: 'Moderately Low', value: '10-20%' },
+ { color: 'fcsGradient4', title: 'Moderately High', value: '20-30%' },
+ { color: 'fcsGradient5', title: 'High', value: '30-40%' },
+ { color: 'fcsGradient6', title: 'Very High', value: 'Above 40%' },
+ ],
+
startLabel: '0%',
endLabel: 'above 40%',
popoverInfo: (
@@ -158,17 +166,18 @@ export function mapLegendData(
case GlobalInsight.RAINFALL:
legendData.push({
title: 'Rainfall',
- colors: [
- 'vegetationGradient1',
- 'vegetationGradient2',
- 'vegetationGradient3',
- 'vegetationGradient4',
- 'vegetationGradient5',
- 'rainfallGradient6',
- 'rainfallGradient7',
- 'rainfallGradient8',
- 'rainfallGradient9',
+ colorsData: [
+ { color: 'vegetationGradient1', value: '<40%' },
+ { color: 'vegetationGradient2', value: '40-60%' },
+ { color: 'vegetationGradient3', value: '60-80%' },
+ { color: 'vegetationGradient4', value: '80-90%' },
+ { color: 'vegetationGradient5', value: '90-110%' },
+ { color: 'rainfallGradient6', value: '110-120%' },
+ { color: 'rainfallGradient7', value: '120-140%' },
+ { color: 'rainfallGradient8', value: '140-180%' },
+ { color: 'rainfallGradient9', value: '>180%' },
],
+
startLabel: '<40%',
endLabel: '>180%',
popoverInfo: (
@@ -196,17 +205,18 @@ export function mapLegendData(
case GlobalInsight.VEGETATION:
legendData.push({
title: 'Vegetation',
- colors: [
- 'vegetationGradient1',
- 'vegetationGradient2',
- 'vegetationGradient3',
- 'vegetationGradient4',
- 'vegetationGradient5',
- 'vegetationGradient6',
- 'vegetationGradient7',
- 'vegetationGradient8',
- 'vegetationGradient9',
+ colorsData: [
+ { color: 'vegetationGradient1', value: '<50%' },
+ { color: 'vegetationGradient2', value: '50-70%' },
+ { color: 'vegetationGradient3', value: '70-80%' },
+ { color: 'vegetationGradient4', value: '80-90%' },
+ { color: 'vegetationGradient5', value: '90-110%' },
+ { color: 'vegetationGradient6', value: '110-120%' },
+ { color: 'vegetationGradient7', value: '120-130%' },
+ { color: 'vegetationGradient8', value: '130-150%' },
+ { color: 'vegetationGradient9', value: '>150%' },
],
+
startLabel: '<50%',
endLabel: '>150%',
popoverInfo: (
@@ -236,14 +246,14 @@ export function mapLegendData(
legendData.push({
title: 'Number of people in IPC/CH Phase 3 or above (millions)',
hasNotAnalyzedPoint: true,
- colors: [
- 'ipcGradient1',
- 'ipcGradient2',
- 'ipcGradient3',
- 'ipcGradient4',
- 'ipcGradient5',
- 'ipcGradient6',
- 'ipcGradient7',
+ colorsData: [
+ { color: 'ipcGradient1', value: '0-0.099' },
+ { color: 'ipcGradient2', value: '0.1-0.49' },
+ { color: 'ipcGradient3', value: '0.5-0.99' },
+ { color: 'ipcGradient4', value: '1.0-2.99' },
+ { color: 'ipcGradient5', value: '3.0-4.99' },
+ { color: 'ipcGradient6', value: '5.0-9.99' },
+ { color: 'ipcGradient7', value: '>10' },
],
startLabel: '0',
endLabel: '>10',
@@ -356,7 +366,13 @@ export function mapLegendData(
} else {
legendData.push({
title: 'Risk of Inadequate Micronutrient Intake',
- colors: ['ipcGradient1', 'ipcGradient2', 'ipcGradient3', 'ipcGradient4', 'ipcGradient5'],
+ colorsData: [
+ { color: 'ipcGradient1', title: 'Lowest', value: '0-19%' },
+ { color: 'ipcGradient2', title: 'Low', value: '20-39%' },
+ { color: 'ipcGradient3', title: 'Moderate', value: '40-59%' },
+ { color: 'ipcGradient4', title: 'High', value: '60-79%' },
+ { color: 'ipcGradient5', title: 'Highest', value: '80-100%' },
+ ],
startLabel: '0%',
endLabel: '100%',
popoverInfo: (
diff --git a/src/domain/constant/map/Map.ts b/src/domain/constant/map/Map.ts
index e74a01ad..8bcea761 100644
--- a/src/domain/constant/map/Map.ts
+++ b/src/domain/constant/map/Map.ts
@@ -1,2 +1,25 @@
-export const MAP_MAX_ZOOM = 8;
+import { LatLngBoundsExpression } from 'leaflet';
+
+export const MAP_MAX_ZOOM = 6;
export const MAP_MIN_ZOOM = 3;
+export const oceanBounds: LatLngBoundsExpression = [
+ [-90, -180],
+ [90, 180],
+];
+export const countryBaseStyle = {
+ fillColor: 'hsl(var(--nextui-countriesBase))',
+ fillOpacity: 1,
+ weight: 0,
+};
+
+export const countryBorderStyle = {
+ color: 'hsl(var(--nextui-countryBorders))',
+ weight: 1,
+ fillOpacity: 0,
+};
+export const disputedAreaStyle = {
+ fillOpacity: 0,
+ color: 'black',
+ weight: 1,
+ dashArray: '5,5',
+};
diff --git a/src/domain/contexts/SelectedMapContext.tsx b/src/domain/contexts/SelectedMapContext.tsx
index fea9beeb..0a80ddc3 100644
--- a/src/domain/contexts/SelectedMapContext.tsx
+++ b/src/domain/contexts/SelectedMapContext.tsx
@@ -1,7 +1,6 @@
import { createContext, ReactNode, useContext, useMemo, useState } from 'react';
import { GlobalInsight } from '../enums/GlobalInsight';
-import { useSelectedMapVisibility } from './SelectedMapVisibilityContext';
interface SelectedMapTypeState {
selectedMapType: GlobalInsight;
@@ -12,11 +11,7 @@ const SelectedMapContext = createContext(undef
export function SelectedMapProvider({ children }: { children: ReactNode }) {
const [selectedMapType, setSelectedMapTypeState] = useState(GlobalInsight.FOOD);
- const { setSelectedMapVisibility } = useSelectedMapVisibility();
const setSelectedMapType = (value: GlobalInsight) => {
- if (value !== selectedMapType) {
- setSelectedMapVisibility(true);
- }
setSelectedMapTypeState(value);
window.gtag('event', `${value}_map_selected`);
};
diff --git a/src/domain/contexts/SelectedMapVisibilityContext.tsx b/src/domain/contexts/SelectedMapVisibilityContext.tsx
deleted file mode 100644
index d368d384..00000000
--- a/src/domain/contexts/SelectedMapVisibilityContext.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { createContext, useContext, useMemo, useState } from 'react';
-
-interface SelectedMapVisibilityState {
- selectedMapVisibility: boolean;
- setSelectedMapVisibility: (value: boolean) => void;
-}
-
-const SelectedMapVisibilityContext = createContext(undefined);
-
-export function SelectedMapVisibilityProvider({ children }: { children: React.ReactNode }) {
- const [selectedMapVisibility, setSelectedMapVisibility] = useState(true);
-
- const value = useMemo(
- () => ({
- selectedMapVisibility,
- setSelectedMapVisibility,
- }),
- [selectedMapVisibility]
- );
-
- return {children} ;
-}
-
-export function useSelectedMapVisibility() {
- const context = useContext(SelectedMapVisibilityContext);
- if (!context) {
- throw new Error('useSelectedMapVisibility must be used within a SelectedMapVisibilityProvider');
- }
- return context;
-}
diff --git a/src/domain/entities/charts/CategoricalChartData.ts b/src/domain/entities/charts/CategoricalChartData.ts
new file mode 100644
index 00000000..cf1ea35b
--- /dev/null
+++ b/src/domain/entities/charts/CategoricalChartData.ts
@@ -0,0 +1,12 @@
+export interface CategoricalDataPoint {
+ y: number;
+}
+
+export interface CategoricalChartData {
+ yAxisLabel?: string;
+ categories: {
+ name: string;
+ dataPoint: CategoricalDataPoint;
+ color?: string;
+ }[];
+}
diff --git a/src/domain/entities/charts/LineChartData.ts b/src/domain/entities/charts/LineChartData.ts
index d0ea8e12..5066718b 100644
--- a/src/domain/entities/charts/LineChartData.ts
+++ b/src/domain/entities/charts/LineChartData.ts
@@ -16,8 +16,8 @@ export interface ChartVerticalLine {
}
export interface ChartVerticalBand {
- xStart?: number; // if null -> band extends to the end of the axis dynamically
- xEnd?: number; // if null -> band extends to the end of the axis dynamically
+ xStart?: number;
+ xEnd?: number;
color?: string;
label?: string;
}
@@ -39,7 +39,6 @@ export interface LineChartData {
type: LineChartDataType.LINE_CHART_DATA;
xAxisType: AxisTypeValue;
yAxisLabel?: string;
- roundLines?: boolean;
predictionVerticalLineX?: number;
lines: {
name: string;
diff --git a/src/domain/entities/country/CountryMimiData.ts b/src/domain/entities/country/CountryMimiData.ts
index 2ccab121..dbc4746f 100644
--- a/src/domain/entities/country/CountryMimiData.ts
+++ b/src/domain/entities/country/CountryMimiData.ts
@@ -1,8 +1,5 @@
-import { LatLngExpression } from 'leaflet';
-
import { Feature } from '@/domain/entities/common/Feature.ts';
-import { Geometry } from '../common/Geometry';
import { RegionNutritionProperties } from '../region/RegionNutritionProperties';
export interface CountryMimiData {
@@ -13,7 +10,6 @@ export interface CountryMimiData {
};
};
features: (Feature & {
- geometry: Geometry;
id: string;
})[];
}
diff --git a/src/domain/enums/ChartType.ts b/src/domain/enums/ChartType.ts
new file mode 100644
index 00000000..00dd5ba2
--- /dev/null
+++ b/src/domain/enums/ChartType.ts
@@ -0,0 +1,5 @@
+export enum ChartType {
+ LINE = 'Line',
+ COLUMN = 'Column',
+ PIE = 'Pie',
+}
diff --git a/src/domain/props/CategoricalChartProps.tsx b/src/domain/props/CategoricalChartProps.tsx
new file mode 100644
index 00000000..bcd2fb53
--- /dev/null
+++ b/src/domain/props/CategoricalChartProps.tsx
@@ -0,0 +1,16 @@
+import { CategoricalChartData } from '@/domain/entities/charts/CategoricalChartData.ts';
+
+export default interface CategoricalChartProps {
+ data: CategoricalChartData;
+
+ title?: string;
+ description?: string;
+
+ small?: boolean;
+ noPadding?: boolean;
+ transparentBackground?: boolean;
+
+ disableExpandable?: boolean;
+ disablePieChartSwitch?: boolean;
+ disableDownload?: boolean;
+}
diff --git a/src/domain/props/ChartContainerProps.tsx b/src/domain/props/ChartContainerProps.tsx
new file mode 100644
index 00000000..6b021d1f
--- /dev/null
+++ b/src/domain/props/ChartContainerProps.tsx
@@ -0,0 +1,61 @@
+import Highcharts from 'highcharts';
+import HighchartsReact from 'highcharts-react-official';
+import { Dispatch, MutableRefObject, SetStateAction } from 'react';
+
+import { CategoricalChartData } from '@/domain/entities/charts/CategoricalChartData.ts';
+import { LineChartData } from '@/domain/entities/charts/LineChartData.ts';
+import { ChartType } from '@/domain/enums/ChartType.ts';
+
+/**
+ * helpers props
+ */
+
+export interface LineChartSliderButtonProps {
+ showSlider: boolean;
+ setShowSlider: Dispatch>;
+ size: number;
+}
+
+export interface ChartAlternativeSwitchButtonProps {
+ defaultChartType: ChartType;
+ alternativeChartType: ChartType;
+ showAlternativeChart: boolean;
+ setShowAlternativeChart: Dispatch>;
+ size?: number;
+}
+
+export interface ChartSliderProps {
+ title?: string;
+ sliderMin: number;
+ sliderMax: number;
+ selectedSliderRange: number[]; // [rangeMin, rangeMax]
+ setSelectedSliderRange: Dispatch>;
+}
+
+export interface ChartDownloadButtonProps {
+ chartRef: MutableRefObject;
+ chartData: LineChartData | CategoricalChartData;
+ size: number;
+}
+
+/**
+ * main `ChartContainer` props
+ */
+
+export default interface ChartContainerProps {
+ chartOptions: Highcharts.Options;
+ chartData: LineChartData | CategoricalChartData;
+
+ title?: string;
+ description?: string;
+ small?: boolean;
+ noPadding?: boolean;
+ transparentBackground?: boolean;
+
+ disableExpandable?: boolean;
+ disableDownload?: boolean;
+
+ alternativeSwitchButtonProps?: ChartAlternativeSwitchButtonProps;
+
+ sliderProps?: ChartSliderProps;
+}
diff --git a/src/domain/props/ChartModalProps.tsx b/src/domain/props/ChartModalProps.tsx
new file mode 100644
index 00000000..b3f6f338
--- /dev/null
+++ b/src/domain/props/ChartModalProps.tsx
@@ -0,0 +1,28 @@
+import Highcharts from 'highcharts';
+import { Dispatch, SetStateAction } from 'react';
+
+import { CategoricalChartData } from '@/domain/entities/charts/CategoricalChartData.ts';
+import { LineChartData } from '@/domain/entities/charts/LineChartData.ts';
+import { ChartAlternativeSwitchButtonProps, ChartSliderProps } from '@/domain/props/ChartContainerProps';
+
+export default interface ChartModalProps {
+ chartOptions: Highcharts.Options;
+ chartData: LineChartData | CategoricalChartData;
+
+ title?: string;
+ description?: string;
+ disableDownload?: boolean;
+
+ // modal open/close control
+ isOpen: boolean;
+ onClose: () => void;
+ onOpenChange: () => void;
+
+ // alternative chart switch
+ alternativeSwitchButtonProps?: ChartAlternativeSwitchButtonProps;
+
+ // slider props
+ sliderProps?: ChartSliderProps;
+ showSlider?: boolean;
+ setShowSlider?: Dispatch>;
+}
diff --git a/src/domain/props/ColorsData.tsx b/src/domain/props/ColorsData.tsx
new file mode 100644
index 00000000..786f491b
--- /dev/null
+++ b/src/domain/props/ColorsData.tsx
@@ -0,0 +1,5 @@
+export interface ColorsData {
+ color: string;
+ title?: string;
+ value: string;
+}
diff --git a/src/domain/props/FcsChoroplethProps.ts b/src/domain/props/FcsChoroplethProps.ts
index 07d1034d..b9ce9ed8 100644
--- a/src/domain/props/FcsChoroplethProps.ts
+++ b/src/domain/props/FcsChoroplethProps.ts
@@ -7,8 +7,6 @@ import { CountryIso3Data } from '@/domain/entities/country/CountryIso3Data.ts';
export default interface FcsChoroplethProps {
data: FeatureCollection;
countryId: number;
- selectedCountryId: number | null;
- setSelectedCountryId: (countryId: number | null) => void;
loading: boolean;
regionData?: FeatureCollection;
countryData?: CountryData;
diff --git a/src/domain/props/GradientLegendContainerItem.ts b/src/domain/props/GradientLegendContainerItem.ts
index cc301b29..c8df3110 100644
--- a/src/domain/props/GradientLegendContainerItem.ts
+++ b/src/domain/props/GradientLegendContainerItem.ts
@@ -1,7 +1,9 @@
import { ReactNode } from 'react';
+import { ColorsData } from './ColorsData';
+
export interface GradientLegendContainerItem {
- colors: string[];
+ colorsData: ColorsData[];
title: string;
startLabel: string;
endLabel: string;
diff --git a/src/domain/props/GradientLegendProps.ts b/src/domain/props/GradientLegendProps.ts
index c10256fd..bf76f913 100644
--- a/src/domain/props/GradientLegendProps.ts
+++ b/src/domain/props/GradientLegendProps.ts
@@ -1,5 +1,7 @@
+import { ColorsData } from './ColorsData';
+
export default interface GradientLegendProps {
- colors: string[];
+ colorsData: ColorsData[];
startLabel: string;
endLabel: string;
hasNotAnalyzedPoint?: boolean;
diff --git a/src/domain/props/IpcChoroplethProps.tsx b/src/domain/props/IpcChoroplethProps.tsx
index ce9d47d2..28728fa1 100644
--- a/src/domain/props/IpcChoroplethProps.tsx
+++ b/src/domain/props/IpcChoroplethProps.tsx
@@ -5,8 +5,6 @@ import { CountryMapDataWrapper } from '@/domain/entities/country/CountryMapData'
export interface IpcChoroplethProps {
countries: CountryMapDataWrapper;
- selectedCountryId: number | null;
- setSelectedCountryId: (countryId: number | null) => void;
handleBackButtonClick?: () => void;
countryData?: CountryData;
ipcRegionData?: FeatureCollection;
diff --git a/src/domain/props/IpcCountryChoroplethProps.tsx b/src/domain/props/IpcCountryChoroplethProps.tsx
index 24e00fd8..4ecd7366 100644
--- a/src/domain/props/IpcCountryChoroplethProps.tsx
+++ b/src/domain/props/IpcCountryChoroplethProps.tsx
@@ -6,5 +6,4 @@ export default interface IpcCountryChoroplethProps {
regionIpcData: FeatureCollection;
countryData: CountryData | undefined;
countryName?: string;
- handleBackButtonClick?: () => void;
}
diff --git a/src/domain/props/LineChartModalProps.tsx b/src/domain/props/LineChartModalProps.tsx
deleted file mode 100644
index 71390639..00000000
--- a/src/domain/props/LineChartModalProps.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import Highcharts from 'highcharts';
-
-import { LineChartData } from '@/domain/entities/charts/LineChartData.ts';
-
-export default interface LineChartModalProps {
- title?: string;
- description?: string;
- barChartSwitch?: boolean;
- xAxisSlider?: boolean;
-
- lineChartData: LineChartData;
- chartOptions: Highcharts.Options;
-
- isOpen: boolean;
- onClose: () => void;
- onOpenChange: () => void;
-
- showXAxisSlider: boolean;
- setShowXAxisSlider: (b: boolean) => void;
- showBarChart: boolean;
- setShowBarChart: (b: boolean) => void;
- selectedXAxisRange: number[];
- setSelectedXAxisRange: (r: number[]) => void;
-}
diff --git a/src/domain/props/LineChartProps.tsx b/src/domain/props/LineChartProps.tsx
index 61f07c1a..d9a50954 100644
--- a/src/domain/props/LineChartProps.tsx
+++ b/src/domain/props/LineChartProps.tsx
@@ -4,36 +4,17 @@ import { InflationGraphs } from '@/domain/entities/charts/InflationGraphs.ts';
import { LineChartData } from '@/domain/entities/charts/LineChartData.ts';
export default interface LineChartProps {
+ data: LineChartData | BalanceOfTradeGraph | CurrencyExchangeGraph | InflationGraphs;
+
title?: string;
description?: string;
- expandable?: boolean;
- barChartSwitch?: boolean;
- xAxisSlider?: boolean;
+
small?: boolean;
- roundLines?: boolean;
noPadding?: boolean;
transparentBackground?: boolean;
- data: LineChartData | BalanceOfTradeGraph | CurrencyExchangeGraph | InflationGraphs;
-}
-
-/**
- * following: helpers props
- */
-
-export interface LineChartSliderButtonProps {
- showXAxisSlider: boolean;
- setShowXAxisSlider: (b: boolean) => void;
- size: number;
-}
-
-export interface LineChartBarLineSwitchButtonProps {
- showBarChart: boolean;
- setShowBarChart: (b: boolean) => void;
- size: number;
-}
-export interface LineChartXAxisSlider {
- selectedXAxisRange: number[]; // [xAxisRangeMinIndex, xAxisRangeMaxIndex]
- setSelectedXAxisRange: (ns: number[]) => void;
- data: LineChartData;
+ disableExpandable?: boolean;
+ disableBarChartSwitch?: boolean;
+ disableXAxisSlider?: boolean;
+ disableDownload?: boolean;
}
diff --git a/src/domain/props/NutritionChoroplethProps.tsx b/src/domain/props/NutritionChoroplethProps.tsx
index 2e316ae0..290190d0 100644
--- a/src/domain/props/NutritionChoroplethProps.tsx
+++ b/src/domain/props/NutritionChoroplethProps.tsx
@@ -3,8 +3,6 @@ import { FeatureCollection } from 'geojson';
export default interface NutritionChoroplethProps {
data: FeatureCollection;
countryId: number;
- selectedCountryId?: number | null;
- setSelectedCountryId: (countryId: number | null) => void;
regionNutritionData?: FeatureCollection;
selectedCountryName?: string;
}
diff --git a/src/domain/props/TooltipProps.tsx b/src/domain/props/TooltipProps.tsx
index 325d7473..9879bae0 100644
--- a/src/domain/props/TooltipProps.tsx
+++ b/src/domain/props/TooltipProps.tsx
@@ -6,4 +6,6 @@ export default interface TooltipProps {
text: string;
delay?: number;
warning?: boolean;
+ titleStyle?: string;
+ textStyle?: string;
}
diff --git a/src/domain/props/VectorTileLayerProps.tsx b/src/domain/props/VectorTileLayerProps.tsx
deleted file mode 100644
index 80d38472..00000000
--- a/src/domain/props/VectorTileLayerProps.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import { CountryMapDataWrapper } from '../entities/country/CountryMapData';
-import { DisputedAreas } from '../entities/DisputedAreas';
-
-export interface VectorTileLayerProps {
- countries: CountryMapDataWrapper;
- disputedAreas?: DisputedAreas;
-}
diff --git a/src/operations/charts/CategoricalChartOperations.ts b/src/operations/charts/CategoricalChartOperations.ts
new file mode 100644
index 00000000..3c59b3b1
--- /dev/null
+++ b/src/operations/charts/CategoricalChartOperations.ts
@@ -0,0 +1,183 @@
+'use client';
+
+import Highcharts, { TooltipFormatterContextObject } from 'highcharts';
+import highchartsMore from 'highcharts/highcharts-more';
+import patternFill from 'highcharts/modules/pattern-fill';
+
+import { CategoricalChartData } from '@/domain/entities/charts/CategoricalChartData.ts';
+import { getTailwindColor } from '@/utils/tailwind-util.ts';
+
+// initialize the exporting module
+if (typeof Highcharts === 'object') {
+ highchartsMore(Highcharts);
+ patternFill(Highcharts);
+}
+
+/**
+ * Using CategoricalChartOperations, the CategoricalChart component can generate
+ * the chart `Highcharts.Options` object required by the Highcharts component.
+ */
+export default class CategoricalChartOperations {
+ /**
+ * The first four bar colors are fixed; if more than four vars are rendered,
+ * the default Highcharts colors will be used.
+ */
+ private static getCategoriesColorList() {
+ return [
+ getTailwindColor('--nextui-clusterRed'),
+ getTailwindColor('--nextui-clusterGreen'),
+ getTailwindColor('--nextui-clusterBlue'),
+ getTailwindColor('--nextui-clusterOrange'),
+ ];
+ }
+
+ /**
+ * Tooltip formatter function for `Options.tooltip.formatter` usage only.
+ */
+ private static chartTooltipFormatter(points: TooltipFormatterContextObject[] | undefined) {
+ let tooltip = '';
+ points?.forEach((p) => {
+ if (p.point.options.y) {
+ tooltip += `\u25CF ${p.point.options.y}
`;
+ }
+ });
+ return tooltip;
+ }
+
+ /**
+ * Tooltip formatter function for `Options.plotOptions.pie.dataLabels.formatter` usage only.
+ */
+ private static pieDataLabelsFormatter(point: Highcharts.Point, data: CategoricalChartData) {
+ return `${point.name}:
${point.y} ${data.yAxisLabel}
`;
+ }
+
+ /**
+ * With this static function, the CategoricalChart component can build the `HighCharts.Options` object
+ * for a bar chart or pie chart, out of a given `CategoricalChartData` instance.
+ * @param data `CategoricalChartData` object, containing all data to be plotted in the chart
+ * @param pieChart if true, a pie chart instead of a bar chart is created
+ */
+ public static getHighChartOptions(data: CategoricalChartData, pieChart?: boolean): Highcharts.Options {
+ const seriesData = [];
+ const categories = [];
+ const defaultCategoriesColors = CategoricalChartOperations.getCategoriesColorList();
+ for (let i = 0; i < data.categories.length; i += 1) {
+ const categoryData = data.categories[i];
+
+ // the first four category colors are fixed; however, they can also be overridden by the `color` property;
+ // if more than four lines are rendered the default Highcharts colors will be used (`categoryColor` stays undefined)
+ let categoryColor;
+ if (categoryData.color) {
+ categoryColor = categoryData.color;
+ } else {
+ categoryColor = defaultCategoriesColors.pop();
+ }
+
+ // collect category names
+ categories.push(categoryData.name);
+
+ // build series object for highchart
+ seriesData.push({
+ name: categoryData.name,
+ y: categoryData.dataPoint.y,
+ color: categoryColor,
+ });
+ }
+
+ // constructing the final HighCharts.Options
+ return {
+ title: {
+ text: '',
+ },
+ chart: {
+ type: pieChart ? 'Line' : 'column',
+ backgroundColor: 'transparent',
+ },
+ legend: {
+ enabled: false,
+ },
+ xAxis: {
+ categories,
+ visible: !pieChart,
+ labels: {
+ style: {
+ color: getTailwindColor('--nextui-secondary'),
+ fontSize: '0.7rem',
+ },
+ },
+ lineColor: getTailwindColor('--nextui-chartsXAxisLine'),
+ tickColor: getTailwindColor('--nextui-chartsXAxisLine'),
+ tickLength: 4,
+ },
+ yAxis: {
+ visible: !pieChart,
+ title: {
+ text: data.yAxisLabel,
+ style: {
+ color: getTailwindColor('--nextui-secondary'),
+ },
+ },
+ labels: {
+ style: {
+ color: getTailwindColor('--nextui-secondary'),
+ fontSize: '0.7rem',
+ },
+ formatter() {
+ return Highcharts.numberFormat(this.value as number, -1);
+ },
+ },
+ lineColor: 'transparent',
+ gridLineColor: getTailwindColor('--nextui-chartsGridLine'),
+ },
+ tooltip: {
+ enabled: !pieChart,
+ shared: true,
+ formatter() {
+ return CategoricalChartOperations.chartTooltipFormatter(this.points);
+ },
+ backgroundColor: getTailwindColor('--nextui-chartsLegendBackground'),
+ style: {
+ color: getTailwindColor('--nextui-foreground'),
+ fontSize: '0.7rem',
+ },
+ },
+ exporting: {
+ enabled: false, // disabling export menu icon
+ },
+ series: [
+ {
+ name: '',
+ data: seriesData,
+ type: pieChart ? 'pie' : 'column',
+ },
+ ],
+ plotOptions: {
+ column: {
+ animation: true,
+ grouping: true,
+ shadow: false,
+ borderWidth: 0,
+ },
+ pie: {
+ animation: true,
+ allowPointSelect: true,
+ cursor: 'pointer',
+ innerSize: '60%',
+ borderWidth: 0,
+ dataLabels: {
+ enabled: true,
+ formatter() {
+ return CategoricalChartOperations.pieDataLabelsFormatter(this.point, data);
+ },
+ style: {
+ color: getTailwindColor('--nextui-secondary'),
+ fontSize: '12px',
+ borderWidth: 0,
+ fontWeight: 'light',
+ },
+ },
+ },
+ },
+ };
+ }
+}
diff --git a/src/operations/charts/ChartAlternativeSwitchButtonOperations.tsx b/src/operations/charts/ChartAlternativeSwitchButtonOperations.tsx
new file mode 100644
index 00000000..39b2ecb5
--- /dev/null
+++ b/src/operations/charts/ChartAlternativeSwitchButtonOperations.tsx
@@ -0,0 +1,32 @@
+import { Chart, Diagram, Graph } from 'iconsax-react';
+import React from 'react';
+
+import { ChartType } from '@/domain/enums/ChartType.ts';
+
+export default class ChartDownloadButtonOperations {
+ public static getChartTypeIcon(chartType: ChartType, size: number) {
+ switch (chartType) {
+ case ChartType.LINE:
+ return ;
+ case ChartType.COLUMN:
+ return ;
+ case ChartType.PIE:
+ return ;
+ default:
+ return ;
+ }
+ }
+
+ public static getChartTypeTitle(chartType: ChartType) {
+ switch (chartType) {
+ case ChartType.LINE:
+ return 'Line';
+ case ChartType.COLUMN:
+ return 'Bar';
+ case ChartType.PIE:
+ return 'Pie';
+ default:
+ return '';
+ }
+ }
+}
diff --git a/src/operations/charts/ChartDownloadButtonOperations.ts b/src/operations/charts/ChartDownloadButtonOperations.ts
new file mode 100644
index 00000000..93eed440
--- /dev/null
+++ b/src/operations/charts/ChartDownloadButtonOperations.ts
@@ -0,0 +1,71 @@
+'use client';
+
+import Highcharts from 'highcharts';
+import highchartsMore from 'highcharts/highcharts-more';
+import ExportData from 'highcharts/modules/export-data';
+import Exporting from 'highcharts/modules/exporting';
+import OfflineExporting from 'highcharts/modules/offline-exporting';
+import patternFill from 'highcharts/modules/pattern-fill';
+import HighchartsReact from 'highcharts-react-official';
+
+import { CategoricalChartData } from '@/domain/entities/charts/CategoricalChartData.ts';
+import { LineChartData } from '@/domain/entities/charts/LineChartData.ts';
+
+// initialize the exporting module
+if (typeof Highcharts === 'object') {
+ highchartsMore(Highcharts);
+ patternFill(Highcharts);
+ Exporting(Highcharts);
+ ExportData(Highcharts);
+ OfflineExporting(Highcharts);
+}
+/**
+ * All chart download functionalities are implemented here.
+ */
+export default class ChartDownloadButtonOperations {
+ /**
+ * Trigger download of the given line chart as a png file.
+ */
+ public static downloadChartPNG(chart: HighchartsReact.RefObject): void {
+ chart.chart.exportChartLocal({
+ type: 'image/png',
+ filename: 'chart-download',
+ });
+ }
+
+ /**
+ * Trigger download of the given line chart as a svg file.
+ */
+ public static downloadChartDataSVG(chart: HighchartsReact.RefObject): void {
+ const svg = chart.chart.getSVG();
+ const blob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' });
+ const url = URL.createObjectURL(blob);
+ // create a temporary link element and trigger the download
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = 'chart.svg'; // Name of the SVG file
+ a.click();
+ // clean up the URL object
+ URL.revokeObjectURL(url);
+ }
+
+ /**
+ * Trigger download of the given line chart data as a csv file.
+ */
+ public static downloadChartDataCSV(chart: HighchartsReact.RefObject): void {
+ chart.chart.downloadCSV();
+ }
+
+ /**
+ * Trigger download of the given line chart `data` as a json file.
+ */
+ public static downloadDataJSON(data: LineChartData | CategoricalChartData): void {
+ // convert data json object to string and encode as URI
+ const jsonString = `data:text/json;charset=utf-8,${encodeURIComponent(JSON.stringify(data, null, 2))}`;
+ // create a temporary link element and trigger the download
+ const link = document.createElement('a');
+ link.href = jsonString;
+ link.download = 'hunger_map_chart_data.json';
+ link.click();
+ }
+}
diff --git a/src/operations/charts/LineChartOperations.ts b/src/operations/charts/LineChartOperations.ts
index ba7bb7ef..ea2269ee 100644
--- a/src/operations/charts/LineChartOperations.ts
+++ b/src/operations/charts/LineChartOperations.ts
@@ -8,7 +8,6 @@ import Highcharts, {
} from 'highcharts';
import highchartsMore from 'highcharts/highcharts-more';
import patternFill from 'highcharts/modules/pattern-fill';
-import HighchartsReact from 'highcharts-react-official';
import { BalanceOfTradeGraph } from '@/domain/entities/charts/BalanceOfTradeGraph.ts';
import { CurrencyExchangeGraph } from '@/domain/entities/charts/CurrencyExchangeGraph.ts';
@@ -16,18 +15,17 @@ import { InflationGraphs } from '@/domain/entities/charts/InflationGraphs.ts';
import { LineChartData } from '@/domain/entities/charts/LineChartData.ts';
import { LineChartDataType } from '@/domain/enums/LineChartDataType.ts';
import { formatToMillion } from '@/utils/formatting.ts';
+import { getTailwindColor } from '@/utils/tailwind-util.ts';
+// initialize the exporting module
if (typeof Highcharts === 'object') {
highchartsMore(Highcharts);
patternFill(Highcharts);
}
-
/**
* Using LineChartOperations, the LineChart component can convert its received data into LineChartData
* and then generate the chart `Highcharts.Options` object required by the Highcharts component.
* Two types of options can be created: rendering the LineChart data as a line chart or as a bar chart.
- *
- * In addition, several download functionalities are implemented.
*/
export default class LineChartOperations {
/**
@@ -45,15 +43,15 @@ export default class LineChartOperations {
*/
private static getLineColorList() {
return [
- 'hsl(var(--nextui-clusterRed))',
- 'hsl(var(--nextui-clusterGreen))',
- 'hsl(var(--nextui-clusterBlue))',
- 'hsl(var(--nextui-clusterOrange))',
+ getTailwindColor('--nextui-clusterRed'),
+ getTailwindColor('--nextui-clusterGreen'),
+ getTailwindColor('--nextui-clusterBlue'),
+ getTailwindColor('--nextui-clusterOrange'),
];
}
/**
- * Formatter function for `LineChart.Options.tooltip.formatter` usage only.
+ * Formatter function for `Options.tooltip.formatter` usage only.
*/
private static chartTooltipFormatter(
xAxisType: AxisTypeValue,
@@ -70,14 +68,14 @@ export default class LineChartOperations {
if (p.point.options.y) {
tooltip += `\u25CF ${p.point.options.y}
`;
} else if (p.point.options.high !== undefined && p.point.options.low !== undefined) {
- tooltip += ` (
${p.point.options.low} - ${p.point.options.high}
)
`;
+ tooltip += ` (
${p.point.options.low} - ${p.point.options.high}
)
`;
}
});
return tooltip;
}
/**
- * Formatter function for `LineChart.Options.xAxis.labels.formatter` usage only.
+ * Tooltip formatter function for `LineChart.Options.xAxis.labels.formatter` usage only.
*/
private static chartXAxisFormatter(xAxisType: AxisTypeValue, x: string | number): string {
if (xAxisType === 'datetime' && typeof x === 'number') {
@@ -91,7 +89,6 @@ export default class LineChartOperations {
* is converted to the `LineChartData` type.
* To support another interface in `LineChartProps.data`, one has to add another switch case here
* that converts the new interface into `LineChartData`.
- * @param data
*/
public static convertToLineChartData(
data: LineChartData | BalanceOfTradeGraph | CurrencyExchangeGraph | InflationGraphs
@@ -182,14 +179,12 @@ export default class LineChartOperations {
* can be manipulated; important: if a min is defined a max must be defined as well and vice versa.
*
* @param data `LineChartData` object, containing all data to be plotted in the chart
- * @param roundLines if true, all plotted lines will be rounded
* @param barChart if true, bars are plotted instead of lines
* @param xAxisSelectedMinIdx index of selected x-axis range min value
* @param xAxisSelectedMaxIdx index of selected x-axis range max value
*/
public static getHighChartOptions(
data: LineChartData,
- roundLines?: boolean,
xAxisSelectedMinIdx?: number,
xAxisSelectedMaxIdx?: number,
barChart?: boolean
@@ -213,7 +208,7 @@ export default class LineChartOperations {
if (lineData.color) {
categoryColor = lineData.color;
} else if (lineData.prediction) {
- categoryColor = 'hsl(var(--nextui-chartForecast))';
+ categoryColor = getTailwindColor('--nextui-chartForecast');
} else {
categoryColor = defaultLineColors.pop();
}
@@ -270,7 +265,7 @@ export default class LineChartOperations {
} else {
// plot series as line
series.push({
- type: roundLines ? 'spline' : 'line',
+ type: 'line',
name: lineData.name,
data: seriesData,
color: categoryColor,
@@ -346,14 +341,14 @@ export default class LineChartOperations {
label: {
text: b.label || '',
style: {
- color: 'hsl(var(--nextui-secondary))',
+ color: getTailwindColor('--nextui-secondary'),
fontSize: '0.7rem',
},
},
}));
const plotLines = verticalLines.map((l) => ({
value: l.x,
- color: l.color || 'hsl(var(--nextui-chartsGridLine))',
+ color: l.color || getTailwindColor('--nextui-chartsGridLine'),
dashStyle: l.dashStyle,
zIndex: 2,
}));
@@ -369,25 +364,25 @@ export default class LineChartOperations {
legend: {
itemStyle: {
fontSize: '0.7rem',
- color: 'hsl(var(--nextui-secondary))',
+ color: getTailwindColor('--nextui-secondary'),
},
itemHoverStyle: {
- color: 'hsl(var(--nextui-hover))',
+ color: getTailwindColor('--nextui-hover'),
},
},
xAxis: {
type: data.xAxisType,
labels: {
style: {
- color: 'hsl(var(--nextui-secondary))',
+ color: getTailwindColor('--nextui-secondary'),
fontSize: '0.7rem',
},
formatter() {
return LineChartOperations.chartXAxisFormatter(data.xAxisType, this.value);
},
},
- lineColor: 'hsl(var(--nextui-chartsXAxisLine))',
- tickColor: 'hsl(var(--nextui-chartsXAxisLine))',
+ lineColor: getTailwindColor('--nextui-chartsXAxisLine'),
+ tickColor: getTailwindColor('--nextui-chartsXAxisLine'),
tickLength: 4,
plotBands,
plotLines,
@@ -396,12 +391,12 @@ export default class LineChartOperations {
title: {
text: data.yAxisLabel,
style: {
- color: 'hsl(var(--nextui-secondary))',
+ color: getTailwindColor('--nextui-secondary'),
},
},
labels: {
style: {
- color: 'hsl(var(--nextui-secondary))',
+ color: getTailwindColor('--nextui-secondary'),
fontSize: '0.7rem',
},
formatter() {
@@ -409,16 +404,16 @@ export default class LineChartOperations {
},
},
lineColor: 'transparent',
- gridLineColor: 'hsl(var(--nextui-chartsGridLine))',
+ gridLineColor: getTailwindColor('--nextui-chartsGridLine'),
},
tooltip: {
shared: true,
formatter() {
return LineChartOperations.chartTooltipFormatter(data.xAxisType, this.x, this.points);
},
- backgroundColor: 'hsl(var(--nextui-chartsLegendBackground))',
+ backgroundColor: getTailwindColor('--nextui-chartsLegendBackground'),
style: {
- color: 'hsl(var(--nextui-foreground))',
+ color: getTailwindColor('--nextui-foreground'),
fontSize: '0.7rem',
},
},
@@ -471,50 +466,4 @@ export default class LineChartOperations {
},
};
}
-
- /**
- * Trigger download of the given line chart as a png file.
- */
- public static downloadChartPNG(chart: HighchartsReact.RefObject): void {
- chart.chart.exportChartLocal({
- type: 'image/png',
- filename: 'chart-download',
- });
- }
-
- /**
- * Trigger download of the given line chart as a svg file.
- */
- public static downloadChartDataSVG(chart: HighchartsReact.RefObject): void {
- const svg = chart.chart.getSVG();
- const blob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' });
- const url = URL.createObjectURL(blob);
- // create a temporary link element and trigger the download
- const a = document.createElement('a');
- a.href = url;
- a.download = 'chart.svg'; // Name of the SVG file
- a.click();
- // clean up the URL object
- URL.revokeObjectURL(url);
- }
-
- /**
- * Trigger download of the given line chart data as a csv file.
- */
- public static downloadChartDataCSV(chart: HighchartsReact.RefObject): void {
- chart.chart.downloadCSV();
- }
-
- /**
- * Trigger download of the given line chart `data` as a json file.
- */
- public static downloadDataJSON(data: LineChartData): void {
- // convert data json object to string and encode as URI
- const jsonString = `data:text/json;charset=utf-8,${encodeURIComponent(JSON.stringify(data, null, 2))}`;
- // create a temporary link element and trigger the download
- const link = document.createElement('a');
- link.href = jsonString;
- link.download = 'hunger_map_chart_data.json';
- link.click();
- }
}
diff --git a/src/operations/legends/LegendOperations.ts b/src/operations/legends/LegendOperations.ts
index bbe17c2a..b025e051 100644
--- a/src/operations/legends/LegendOperations.ts
+++ b/src/operations/legends/LegendOperations.ts
@@ -6,8 +6,8 @@ export class LegendOperations {
return false;
}
return (
- Array.isArray((value as GradientLegendContainerItem).colors) &&
- (value as GradientLegendContainerItem).colors.every((color) => typeof color === 'string')
+ Array.isArray((value as GradientLegendContainerItem).colorsData) &&
+ (value as GradientLegendContainerItem).colorsData.every((colorsData) => typeof colorsData.color === 'string')
);
}
}
diff --git a/src/operations/map/FcsChoroplethOperations.ts b/src/operations/map/FcsChoroplethOperations.ts
index 88dd361d..8acd0760 100644
--- a/src/operations/map/FcsChoroplethOperations.ts
+++ b/src/operations/map/FcsChoroplethOperations.ts
@@ -3,8 +3,7 @@ import L from 'leaflet';
import { CountryFcsData } from '@/domain/entities/country/CountryFcsData.ts';
import { CountryMapData } from '@/domain/entities/country/CountryMapData.ts';
-import { MapColorsType } from '@/domain/entities/map/MapColorsType';
-import { getColors, inactiveCountryOverlayStyling } from '@/styles/MapColors';
+import { inactiveCountryOverlayStyling } from '@/styles/MapColors';
class FcsChoroplethOperations {
static async handleCountryClick(
@@ -26,26 +25,27 @@ class FcsChoroplethOperations {
feature: Feature,
layer: L.Layer,
setSelectedCountryId: (countryId: number) => void,
- isDark: boolean,
fcsData: Record
) {
const pathLayer = layer as L.Path;
- const mapColors: MapColorsType = getColors(isDark);
pathLayer.on({
click: async () => {
if (this.checkIfActive(feature as CountryMapData, fcsData)) {
FcsChoroplethOperations.handleCountryClick(feature, setSelectedCountryId);
+ document.getElementsByClassName('leaflet-container').item(0)?.classList.remove('interactive');
}
},
mouseover: () => {
if (this.checkIfActive(feature as CountryMapData, fcsData)) {
- pathLayer.setStyle({ fillOpacity: 0.5, fillColor: mapColors.outline });
+ pathLayer.setStyle({ fillOpacity: 0.3, fillColor: 'hsl(var(--nextui-countryHover))' });
+ document.getElementsByClassName('leaflet-container').item(0)?.classList.add('interactive');
}
},
mouseout: () => {
if (this.checkIfActive(feature as CountryMapData, fcsData)) {
pathLayer.setStyle({ fillOpacity: 0 });
+ document.getElementsByClassName('leaflet-container').item(0)?.classList.remove('interactive');
}
},
});
diff --git a/src/operations/map/FcsCountryChoroplethOperations.tsx b/src/operations/map/FcsCountryChoroplethOperations.tsx
index 84655019..6fa81171 100644
--- a/src/operations/map/FcsCountryChoroplethOperations.tsx
+++ b/src/operations/map/FcsCountryChoroplethOperations.tsx
@@ -7,7 +7,7 @@ import FcsRegionTooltip from '@/components/Map/FcsRegionTooltip';
export class FcsCountryChoroplethOperations {
static fcsFill(fcs?: number): string {
- if (fcs === undefined) return '#2D6092';
+ if (fcs === undefined) return 'hsl(var(--nextui-countriesBase))';
if (fcs <= 0.05) return '#29563A';
if (fcs <= 0.1) return '#73B358';
if (fcs <= 0.2) return '#CBCC58';
@@ -19,7 +19,7 @@ export class FcsCountryChoroplethOperations {
static styleFunction(feature?: Feature): L.PathOptions {
return {
fillColor: FcsCountryChoroplethOperations.fcsFill(feature?.properties?.fcs?.score),
- color: '#000',
+ color: 'hsl(var(--nextui-countryBorders))',
weight: 1,
fillOpacity: 0.6,
};
diff --git a/src/operations/map/IpcChoroplethOperations.tsx b/src/operations/map/IpcChoroplethOperations.tsx
index e833288e..9a0025bb 100644
--- a/src/operations/map/IpcChoroplethOperations.tsx
+++ b/src/operations/map/IpcChoroplethOperations.tsx
@@ -14,7 +14,7 @@ export class IpcChoroplethOperations {
ipcData: CountryIpcData[],
isDark: boolean
) => {
- const country = ipcData.find((c) => parseInt(c.adm0_code, 10) === adm0code);
+ const country = ipcData.find((c) => parseInt(c.adm0_code, 10) === adm0code); // TODO refactor this, this is crazy inefficient, there should never be such an expensive calculation in a render method
return feature.properties?.ipcData
? {
color: '#000',
@@ -37,9 +37,8 @@ export class IpcChoroplethOperations {
};
static ipcCountryStyle = (feature: Feature | undefined) => ({
- color: '#fff',
- opacity: 0.8,
- weight: 0.5,
+ color: 'hsl(var(--nextui-countryBorders))',
+ weight: 1,
fillOpacity: 1,
fillColor: IpcChoroplethOperations.fillCountryIpc(feature?.properties?.ipcPhase),
});
@@ -96,13 +95,18 @@ export class IpcChoroplethOperations {
layer.on({
click: () => {
setSelectedCountryId(feature?.properties?.adm0_id);
+ document.getElementsByClassName('leaflet-container').item(0)?.classList.remove('interactive');
},
mouseover: () => {
pathLayer.setStyle({ ...originalStyle, fillOpacity: 0.7 });
+ document.getElementsByClassName('leaflet-container').item(0)?.classList.add('interactive');
+
pathLayer.openTooltip();
},
mouseout: () => {
pathLayer.setStyle(originalStyle);
+ document.getElementsByClassName('leaflet-container').item(0)?.classList.remove('interactive');
+
pathLayer.closeTooltip();
},
});
diff --git a/src/operations/map/MapOperations.ts b/src/operations/map/MapOperations.tsx
similarity index 72%
rename from src/operations/map/MapOperations.ts
rename to src/operations/map/MapOperations.tsx
index fcfad145..cba69e42 100644
--- a/src/operations/map/MapOperations.ts
+++ b/src/operations/map/MapOperations.tsx
@@ -1,6 +1,9 @@
-import { Feature, FeatureCollection, GeoJsonProperties, Geometry } from 'geojson';
+import { Feature as GeoJsonFeature, FeatureCollection, GeoJsonProperties, Geometry } from 'geojson';
+import { createRoot } from 'react-dom/client';
+import CountryHoverPopover from '@/components/CountryHoverPopover/CountryHoverPopover';
import container from '@/container';
+import { Feature } from '@/domain/entities/common/Feature';
import { CountryData } from '@/domain/entities/country/CountryData.ts';
import { CountryIso3Data } from '@/domain/entities/country/CountryIso3Data.ts';
import { CountryMapData } from '@/domain/entities/country/CountryMapData.ts';
@@ -27,7 +30,7 @@ export class MapOperations {
if (newRegionData && newRegionData.features) {
setRegionData({
type: 'FeatureCollection',
- features: newRegionData.features as Feature[],
+ features: newRegionData.features as GeoJsonFeature[],
});
}
}
@@ -38,7 +41,7 @@ export class MapOperations {
if (newIpcRegionData && newIpcRegionData.features) {
setIpcRegionData({
type: 'FeatureCollection',
- features: newIpcRegionData?.features as Feature[],
+ features: newIpcRegionData?.features as GeoJsonFeature[],
});
}
}
@@ -60,7 +63,7 @@ export class MapOperations {
if (newRegionNutritionData && newRegionNutritionData.features) {
setRegionNutritionData({
type: 'FeatureCollection',
- features: newRegionNutritionData.features as Feature[],
+ features: newRegionNutritionData.features as GeoJsonFeature[],
});
}
}
@@ -84,4 +87,20 @@ export class MapOperations {
setRegionNutritionData(undefined);
setIpcRegionData(undefined);
}
+
+ static convertCountriesToFeatureCollection = (countryFeatures: Feature[]): FeatureCollection => ({
+ type: 'FeatureCollection',
+ features: countryFeatures as GeoJsonFeature[],
+ });
+
+ /**
+ * Create a 'HTMLDivElement' rending the given 'countryName' within a 'CountryHoverPopover'.
+ * Needed cause leaflet tooltips do not accept React components.
+ */
+ static createCountryNameTooltipElement(countryName: string): HTMLDivElement {
+ const tooltipContainer = document.createElement('div');
+ const root = createRoot(tooltipContainer);
+ root.render( );
+ return tooltipContainer;
+ }
}
diff --git a/src/operations/map/MapboxMapOperations.tsx b/src/operations/map/MapboxMapOperations.tsx
deleted file mode 100644
index 9942eadb..00000000
--- a/src/operations/map/MapboxMapOperations.tsx
+++ /dev/null
@@ -1,295 +0,0 @@
-/* eslint-disable */
-import { LeafletContextInterface } from '@react-leaflet/core';
-import { FeatureCollection } from 'geojson';
-import mapboxgl from 'mapbox-gl';
-import React, { RefObject } from 'react';
-
-import { MapColorsType } from '@/domain/entities/map/MapColorsType.ts';
-import { GlobalInsight } from '@/domain/enums/GlobalInsight.ts';
-import { getColors } from '@/styles/MapColors.ts';
-import disputedPattern from '../../../public/disputed_pattern.png';
-import { VectorTileLayerProps } from '@/domain/props/VectorTileLayerProps';
-import { createRoot } from 'react-dom/client';
-import CountryHoverPopover from '@/components/CountryHoverPopover/CountryHoverPopover.tsx';
-
-
-export class MapboxMapOperations {
- static createMapboxMap(isDark: boolean, { countries, disputedAreas }: VectorTileLayerProps, mapContainer: RefObject): mapboxgl.Map {
- const mapColors: MapColorsType = getColors(isDark);
-
- return new mapboxgl.Map({
- container: mapContainer.current as unknown as string | HTMLElement,
- logoPosition: 'bottom-left', // default which can be changed to 'bottom-right'
- style: {
- version: 8,
- name: 'HungerMap LIVE',
- metadata: '{metadata}',
- sources: {
- countries: {
- type: 'geojson',
- data: countries as FeatureCollection,
- generateId: true,
- },
- disputedAreas: {
- type: 'geojson',
- data: disputedAreas as FeatureCollection,
- generateId: true,
- },
- mapboxStreets: {
- type: 'vector',
- url: 'mapbox://mapbox.mapbox-streets-v8',
- bounds: [-180, -60, 180, 90],
- },
- },
- layers: [
- {
- id: 'ocean',
- type: 'background',
- paint: { 'background-color': mapColors.ocean },
- },
- {
- id: 'countries-base',
- type: 'fill',
- source: 'countries',
- paint: { 'fill-color': mapColors.countriesBase },
- },
- // additional layers (FCS, vegetation etc.) are being placed here
- {
- id: 'country-borders',
- type: 'line',
- source: 'countries',
- filter: ['==', ['get', 'disp_area'], 'NO'],
- paint: {
- 'line-color': mapColors.outline,
- 'line-width': 0.7,
- },
- },
- {
- id: 'disputed-borders',
- type: 'line',
- source: 'disputedAreas',
- paint: {
- 'line-color': mapColors.outline,
- 'line-width': 1.5,
- 'line-dasharray': [10, 10],
- },
- },
- {
- id: 'mapbox-roads',
- type: 'line',
- source: 'mapboxStreets',
- 'source-layer': 'road',
- filter: ['in', 'class', 'motorway', 'trunk'],
- paint: {
- 'line-color': mapColors.roads,
- 'line-width': ['interpolate', ['exponential', 1.5], ['zoom'], 5, 0.5, 18, 10],
- },
- minzoom: 5,
- },
- ],
- },
- interactive: false,
- });
- }
-
- static synchronizeLeafletMapbox(
- baseMap: mapboxgl.Map,
- mapContainer: RefObject,
- context: LeafletContextInterface
- ): void {
- baseMap.dragRotate.disable();
-
- const syncZoom = () => {
- baseMap.setZoom(context.map.getZoom() - 1);
- baseMap.setMaxZoom(context.map.getMaxZoom() - 1);
- baseMap.setMinZoom(context.map.getMinZoom() - 1);
- };
-
- const container = context.layerContainer || context.map;
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- const leafletMap = container.getContainer();
- leafletMap.appendChild(mapContainer.current);
-
- baseMap.setZoom(context.map.getZoom());
- baseMap.setMaxZoom(context.map.getMaxZoom() - 1);
- baseMap.setMinZoom(context.map.getMinZoom() - 1);
-
- const { lat, lng } = context.map.getCenter();
- baseMap.setCenter([lng, lat]);
- baseMap.setZoom(context.map.getZoom() - 1);
-
- context.map.on('move', () => {
- const { lat: moveLat, lng: moveLng } = context.map.getCenter();
- baseMap.setCenter([moveLng, moveLat]);
- syncZoom();
- });
-
- context.map.on('zoom', () => {
- const { lat: zoomLat, lng: zoomLng } = context.map.getCenter();
- baseMap.setCenter([zoomLng, zoomLat]);
- syncZoom();
- });
-
- context.map.on('movestart', () => {
- const { lat: moveStartLat, lng: moveStartLng } = context.map.getCenter();
- baseMap.setCenter([moveStartLng, moveStartLat]);
- syncZoom();
- });
-
- context.map.on('zoomstart', () => {
- syncZoom();
- });
-
- context.map.on('moveend', () => {
- const { lat: moveEndLat, lng: moveEndLng } = context.map.getCenter();
- baseMap.setCenter([moveEndLng, moveEndLat]);
- syncZoom();
- });
-
- context.map.on('zoomend', () => {
- const { lat: zoomEndLat, lng: zoomEndLng } = context.map.getCenter();
- baseMap.setCenter([zoomEndLng, zoomEndLat]);
- syncZoom();
- });
- }
-
- static changeMapTheme(baseMap: mapboxgl.Map, isDark: boolean) {
- const mapColors: MapColorsType = getColors(isDark);
- baseMap.setPaintProperty('ocean', 'background-color', mapColors.ocean);
- baseMap.setPaintProperty('countries-base', 'fill-color', mapColors.countriesBase);
- baseMap.setPaintProperty('countries-inactive', 'fill-color', mapColors.inactiveCountriesOverlay);
- baseMap.setPaintProperty('countries-hover', 'fill-color', mapColors.outline);
- baseMap.setPaintProperty('country-borders', 'line-color', mapColors.outline);
- baseMap.setPaintProperty('disputed-borders', 'line-color', mapColors.outline);
- baseMap.setPaintProperty('mapbox-roads', 'line-color', mapColors.roads);
- }
-
- static initDisputedLayer(baseMap: mapboxgl.Map) {
- baseMap.on('load', () => {
- baseMap.loadImage(disputedPattern.src, (error, image) => {
- if (error) throw error;
- baseMap.addImage('diagonal-stripe-pattern', image!, { pixelRatio: 8 });
- baseMap.addLayer({
- id: 'disputed-area-pattern',
- type: 'fill',
- source: 'disputedAreas',
- paint: {
- 'fill-pattern': 'diagonal-stripe-pattern',
- },
- });
- });
- });
- }
-
- static FCS_RASTER = 'fcsRaster';
-
- static FCS_LAYER = 'fcsLayer';
-
- static RAINFALL_RASTER = 'rainfallRaster';
-
- static RAINFALL_LAYER = 'rainfallLayer';
-
- static VEGETATION_RASTER = 'vegetationRaster';
-
- static VEGETATION_LAYER = 'vegetationLayer';
-
- static initFCSLayer(baseMap: mapboxgl.Map) {
- baseMap.on('load', () => {
- baseMap.addSource(this.FCS_RASTER, {
- type: 'raster',
- tiles: ['https://static.hungermapdata.org/proteus_tiles/{z}/{x}/{y}.png'],
- tileSize: 256,
- scheme: 'tms',
- maxzoom: 6
- });
- });
- }
-
- static initRainfallLayer(baseMap: mapboxgl.Map) {
- baseMap.on('load', () => {
- baseMap.addSource(this.RAINFALL_RASTER, {
- type: 'raster',
- tiles: [`https://dev.api.earthobservation.vam.wfp.org/tiles/latest/r3q_dekad/{z}/{x}/{y}.png`],
- tileSize: 256,
- scheme: 'xyz',
- maxzoom: 7,
- bounds: [-180, -49, 180, 49],
- });
- });
- }
-
- static initVegetationLayer(baseMap: mapboxgl.Map) {
- baseMap.on('load', () => {
- baseMap.addSource(this.VEGETATION_RASTER, {
- type: 'raster',
- tiles: [`https://dev.api.earthobservation.vam.wfp.org/tiles/latest/viq_dekad/{z}/{x}/{y}.png`],
- tileSize: 256,
- scheme: 'xyz',
- maxzoom: 7,
- bounds: [-180, -60, 180, 80],
- });
- });
- }
-
- static addMapAsLayer(baseMap: mapboxgl.Map, selectedMapType: GlobalInsight) {
- switch (selectedMapType) {
- case GlobalInsight.FOOD:
- baseMap.addLayer(
- {
- id: this.FCS_LAYER,
- type: 'raster',
- source: this.FCS_RASTER,
- },
- 'country-borders'
- );
- break;
- case GlobalInsight.VEGETATION:
- baseMap.addLayer(
- {
- id: this.VEGETATION_LAYER,
- type: 'raster',
- source: this.VEGETATION_RASTER,
- },
- 'country-borders'
- );
- break;
- case GlobalInsight.RAINFALL:
- baseMap.addLayer(
- {
- id: this.RAINFALL_LAYER,
- type: 'raster',
- source: this.RAINFALL_RASTER,
- },
- 'country-borders'
- );
- break;
- default:
- }
- }
-
- static removeActiveMapLayer(baseMap: mapboxgl.Map, isDark: boolean) {
- const mapColors: MapColorsType = getColors(isDark);
- const layers = baseMap.getStyle()?.layers;
- if (!layers) return;
- const layerToRemove = layers.find((layer) =>
- [this.FCS_LAYER, this.VEGETATION_LAYER, this.RAINFALL_LAYER].includes(layer.id)
- );
- if (!layerToRemove) {
- baseMap.setPaintProperty('countries-base', 'fill-color', mapColors.countriesBase);
- return;
- }
- baseMap.removeLayer(layerToRemove.id);
- }
-
- /**
- * Create a 'HTMLDivElement' rending the given 'countryName' within a 'CountryHoverPopover'.
- * Needed cause leaflet tooltips or mapbox popups does not accept React components.
- */
- static createCountryNameTooltipElement(countryName: string): HTMLDivElement {
- const tooltipContainer = document.createElement('div');
- const root = createRoot(tooltipContainer);
- root.render( );
- return tooltipContainer;
- }
-}
diff --git a/src/operations/map/NutritionChoroplethOperations.ts b/src/operations/map/NutritionChoroplethOperations.ts
index b235648f..39567292 100644
--- a/src/operations/map/NutritionChoroplethOperations.ts
+++ b/src/operations/map/NutritionChoroplethOperations.ts
@@ -40,6 +40,7 @@ export default class NutritionChoroplethOperations {
if (this.checkIfActive(feature, nutritionData)) {
NutritionChoroplethOperations.handleCountryClick(feature, setSelectedCountryId);
}
+ document.getElementsByClassName('leaflet-container').item(0)?.classList.remove('interactive');
},
});
layer.on('mouseover', () => {
@@ -47,6 +48,7 @@ export default class NutritionChoroplethOperations {
pathLayer.setStyle({
fillOpacity: 0.8,
});
+ document.getElementsByClassName('leaflet-container').item(0)?.classList.add('interactive');
}
});
pathLayer.on('mouseout', () => {
@@ -54,6 +56,7 @@ export default class NutritionChoroplethOperations {
pathLayer.setStyle({
fillOpacity: 0.5,
});
+ document.getElementsByClassName('leaflet-container').item(0)?.classList.remove('interactive');
}
});
}
diff --git a/src/styles/MapColors.ts b/src/styles/MapColors.ts
index 5552e2fc..99ccc8e5 100644
--- a/src/styles/MapColors.ts
+++ b/src/styles/MapColors.ts
@@ -1,15 +1,15 @@
import { MapColorsType } from '@/domain/entities/map/MapColorsType.ts';
export const getColors = (isDark: boolean): MapColorsType => ({
- countriesBase: isDark ? '#0e6397' : '#fefeff',
+ countriesBase: isDark ? '#002129' : '#fefeff',
inactiveCountriesOverlay: isDark ? '#a69f9f' : '#d2d1d1',
- ocean: isDark ? '#111111' : '#91cccb',
+ ocean: isDark ? '#002a38' : '#91cccb',
outline: isDark ? '#0e2a3a' : '#306f96',
roads: isDark ? '#404040' : '#808080',
});
export const inactiveCountryOverlayStyling = (isDark: boolean) => ({
color: getColors(isDark).inactiveCountriesOverlay,
- fillOpacity: 0.5,
+ fillOpacity: isDark ? 0.2 : 0.5,
stroke: false,
});
diff --git a/src/styles/globals.css b/src/styles/globals.css
index e9317f23..c2bf4af0 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -83,7 +83,7 @@ path.leaflet-interactive:focus {
font-size: 0.875rem !important;
line-height: 1.25rem !important;
}
- /* shrink mapbox-popup tip triangle */
+/* shrink mapbox-popup tip triangle */
.mapbox-popup-transparent .mapboxgl-popup-tip {
width: 6px !important;
height: 12px !important;
@@ -126,8 +126,12 @@ path.leaflet-interactive:focus {
scrollbar-color: theme('colors.content3') transparent;
}
-.leaflet-interactive:hover {
- cursor: default !important;
+.leaflet-container .leaflet-interactive {
+ cursor: default;
+}
+
+.leaflet-container.interactive .leaflet-interactive {
+ cursor: pointer;
}
.map-marker-with-margin {
diff --git a/src/utils/tailwind-util.ts b/src/utils/tailwind-util.ts
index 2ee0a864..97c0f434 100644
--- a/src/utils/tailwind-util.ts
+++ b/src/utils/tailwind-util.ts
@@ -3,7 +3,10 @@
* @param colorVariable a TailwindCSS color parameter
* @returns the color in hex format, as a string
*/
-export const getTailwindColor = (colorVariable: string) => {
+export const getTailwindColor = (colorVariable: string): string => {
+ if (typeof window === 'undefined' || !document || !document.documentElement) {
+ return '';
+ }
const [hue, saturation, lightness] = getComputedStyle(document.documentElement)
.getPropertyValue(colorVariable)
.split(' ');
diff --git a/tailwind.config.js b/tailwind.config.js
index 4dad889d..2ade581e 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -62,6 +62,9 @@ const config = {
brand: '#157DBC',
brandHover: '#0F6396',
},
+ screens: {
+ sm700: '700px',
+ },
width: {
'215px': '215px',
'179px': '179px',
@@ -169,6 +172,11 @@ const config = {
hazarAdvisory: '#57D66F',
hazardInformation: '#0098EB',
hazardTermination: '#B4B5B7',
+
+ // charts
+ chartsLegendBackground: '#F5F5F5',
+ chartsXAxisLine: '#a6a6a6',
+ chartsGridLine: '#E6E6E6',
chartForecast: '#3896a2',
//fcs gradient
@@ -210,12 +218,14 @@ const config = {
nutritionPredicted: '#E3F2FD',
nutritionNotAnalyzed: '#D2D1D1',
- //animation
+ //animation
nutritionAnimation: '#F7B750',
- fcsAnimation: '#338ef7',
+ fcsAnimation: '#157dbc',
ipcAnimation: '#cd1919',
countriesBase: '#fefeff',
+ countryBorders: '#6f7475',
+ countryHover: '#8a8a8a',
ocean: '#91cccb',
fatalityAlert: '#742280',
climateWetAlert: '#4295D3',
@@ -225,9 +235,6 @@ const config = {
ipcPhase3: '#e88519',
ipcPhase4: '#cd1919',
ipcPhase5: '#731919',
- chartsLegendBackground: '#F5F5F5',
- chartsXAxisLine: '#a6a6a6',
- chartsGridLine: '#E6E6E6',
link: '#005999',
},
},
@@ -265,16 +272,19 @@ const config = {
conflictCivil: '#96badc',
conflictExplosion: '#eaaf75',
conflictStrategic: '#bec0c1',
- countriesBase: '#0e6397',
- ocean: '#111111',
+ countriesBase: '#002129',
+ ocean: '#002a38',
fatalityAlert: '#742280',
climateWetAlert: '#4295D3',
climateDryAlert: '#B95926',
+ nutritionNotAnalyzed: '#A69F9F',
+ fcsAnimation: '#014a5e',
+
+ // charts
chartsLegendBackground: '#2a2a2a',
chartsXAxisLine: '#757575',
chartsGridLine: '#424242',
chartForecast: '#0e6983',
- nutritionNotAnalyzed: '#A69F9F',
link: '#0A9BFF',
},
},
diff --git a/yarn.lock b/yarn.lock
index 7bcb548d..29686b62 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1240,16 +1240,6 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
-"@mapbox/jsonlint-lines-primitives@^2.0.2":
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz#ce56e539f83552b58d10d672ea4d6fc9adc7b234"
- integrity sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==
-
-"@mapbox/mapbox-gl-supported@^3.0.0":
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-3.0.0.tgz#bebd3d5da3c1fd988011bb79718a39f63f5e16ac"
- integrity sha512-2XghOwu16ZwPJLOFVuIOaLbN0iKMn867evzXFyf0P22dqugezfJwLmdanAgU25ITvz1TvOfVP4jsDImlDJzcWg==
-
"@mapbox/node-pre-gyp@^1.0.0":
version "1.0.11"
resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa"
@@ -1265,33 +1255,6 @@
semver "^7.3.5"
tar "^6.1.11"
-"@mapbox/point-geometry@0.1.0", "@mapbox/point-geometry@^0.1.0", "@mapbox/point-geometry@~0.1.0":
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz#8a83f9335c7860effa2eeeca254332aa0aeed8f2"
- integrity sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==
-
-"@mapbox/tiny-sdf@^2.0.6":
- version "2.0.6"
- resolved "https://registry.yarnpkg.com/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz#9a1d33e5018093e88f6a4df2343e886056287282"
- integrity sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA==
-
-"@mapbox/unitbezier@^0.0.1":
- version "0.0.1"
- resolved "https://registry.yarnpkg.com/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz#d32deb66c7177e9e9dfc3bbd697083e2e657ff01"
- integrity sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==
-
-"@mapbox/vector-tile@^1.3.1":
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz#d3a74c90402d06e89ec66de49ec817ff53409666"
- integrity sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==
- dependencies:
- "@mapbox/point-geometry" "~0.1.0"
-
-"@mapbox/whoots-js@^3.1.0":
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz#497c67a1cef50d1a2459ba60f315e448d2ad87fe"
- integrity sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==
-
"@next/env@14.2.10":
version "14.2.10"
resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.10.tgz#1d3178340028ced2d679f84140877db4f420333c"
@@ -5283,13 +5246,6 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50"
integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==
-"@types/geojson-vt@^3.2.5":
- version "3.2.5"
- resolved "https://registry.yarnpkg.com/@types/geojson-vt/-/geojson-vt-3.2.5.tgz#b6c356874991d9ab4207533476dfbcdb21e38408"
- integrity sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==
- dependencies:
- "@types/geojson" "*"
-
"@types/geojson@*", "@types/geojson@^7946.0.10", "@types/geojson@^7946.0.14":
version "7946.0.14"
resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.14.tgz#319b63ad6df705ee2a65a73ef042c8271e696613"
@@ -5338,20 +5294,6 @@
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.13.tgz#786e2d67cfd95e32862143abe7463a7f90c300eb"
integrity sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==
-"@types/mapbox__point-geometry@*", "@types/mapbox__point-geometry@^0.1.4":
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz#0ef017b75eedce02ff6243b4189210e2e6d5e56d"
- integrity sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==
-
-"@types/mapbox__vector-tile@^1.3.4":
- version "1.3.4"
- resolved "https://registry.yarnpkg.com/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.4.tgz#ad757441ef1d34628d9e098afd9c91423c1f8734"
- integrity sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg==
- dependencies:
- "@types/geojson" "*"
- "@types/mapbox__point-geometry" "*"
- "@types/pbf" "*"
-
"@types/mdast@^4.0.0":
version "4.0.4"
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6"
@@ -5376,11 +5318,6 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.5.7.tgz#4b8ecac87fbefbc92f431d09c30e176fc0a7c377"
integrity sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==
-"@types/pbf@*", "@types/pbf@^3.0.5":
- version "3.0.5"
- resolved "https://registry.yarnpkg.com/@types/pbf/-/pbf-3.0.5.tgz#a9495a58d8c75be4ffe9a0bd749a307715c07404"
- integrity sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==
-
"@types/prop-types@*", "@types/prop-types@^15.7.13":
version "15.7.13"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.13.tgz#2af91918ee12d9d32914feb13f5326658461b451"
@@ -5408,13 +5345,6 @@
"@types/prop-types" "*"
csstype "^3.0.2"
-"@types/supercluster@^7.1.3":
- version "7.1.3"
- resolved "https://registry.yarnpkg.com/@types/supercluster/-/supercluster-7.1.3.tgz#1a1bc2401b09174d9c9e44124931ec7874a72b27"
- integrity sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==
- dependencies:
- "@types/geojson" "*"
-
"@types/tinycolor2@^1.4.0":
version "1.4.6"
resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.6.tgz#670cbc0caf4e58dd61d1e3a6f26386e473087f06"
@@ -5943,11 +5873,6 @@ character-reference-invalid@^2.0.0:
resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9"
integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==
-cheap-ruler@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/cheap-ruler/-/cheap-ruler-4.0.0.tgz#bdc984de7e0e3f748bdfd2dbe23ec6b9dc820a09"
- integrity sha512-0BJa8f4t141BYKQyn9NSQt1PguFQXMXwZiA5shfoaBYHAb2fFk2RAX+tiWMoQU+Agtzt3mdt0JtuyshAXqZ+Vw==
-
chokidar@^3.5.3:
version "3.6.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
@@ -6209,11 +6134,6 @@ css-what@^6.1.0:
resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
-csscolorparser@~1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/csscolorparser/-/csscolorparser-1.0.3.tgz#b34f391eea4da8f3e98231e2ccd8df9c041f171b"
- integrity sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==
-
cssesc@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
@@ -6447,11 +6367,6 @@ earcut@^2.2.4:
resolved "https://registry.yarnpkg.com/earcut/-/earcut-2.2.4.tgz#6d02fd4d68160c114825d06890a92ecaae60343a"
integrity sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==
-earcut@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/earcut/-/earcut-3.0.0.tgz#a8d5bf891224eaea8287201b5e787c6c0318af89"
- integrity sha512-41Fs7Q/PLq1SDbqjsgcY7GA42T0jvaCNGXgGtsNdvg+Yv8eIu06bxv4/PoREkZ9nMDNwnUSG9OFB9+yv8eKhDg==
-
eastasianwidth@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
@@ -7109,11 +7024,6 @@ geojson-polygon-self-intersections@^1.2.1:
dependencies:
rbush "^2.0.1"
-geojson-vt@^4.0.2:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/geojson-vt/-/geojson-vt-4.0.2.tgz#1162f6c7d61a0ba305b1030621e6e111f847828a"
- integrity sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==
-
geojson@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/geojson/-/geojson-0.5.0.tgz#3cd6c96399be65b56ee55596116fe9191ce701c0"
@@ -7170,11 +7080,6 @@ git-raw-commits@^4.0.0:
meow "^12.0.1"
split2 "^4.0.0"
-gl-matrix@^3.4.3:
- version "3.4.3"
- resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-3.4.3.tgz#fc1191e8320009fd4d20e9339595c6041ddc22c9"
- integrity sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==
-
glob-parent@^5.1.2, glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
@@ -7265,11 +7170,6 @@ graphemer@^1.4.0:
resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
-grid-index@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/grid-index/-/grid-index-1.1.0.tgz#97f8221edec1026c8377b86446a7c71e79522ea7"
- integrity sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA==
-
has-bigints@^1.0.1, has-bigints@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
@@ -7393,11 +7293,6 @@ iconsax-react@^0.0.8:
dependencies:
prop-types "^15.7.2"
-ieee754@^1.1.12:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
- integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
-
ignore@^5.2.0, ignore@^5.3.1:
version "5.3.2"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
@@ -7840,11 +7735,6 @@ jsts@2.7.1:
object.assign "^4.1.4"
object.values "^1.1.6"
-kdbush@^4.0.2:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-4.0.2.tgz#2f7b7246328b4657dd122b6c7f025fbc2c868e39"
- integrity sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==
-
keyv@^4.5.3:
version "4.5.4"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
@@ -8074,45 +7964,6 @@ make-event-props@^1.6.0:
resolved "https://registry.yarnpkg.com/make-event-props/-/make-event-props-1.6.2.tgz#c8e0e48eb28b9b808730de38359f6341de7ec5a2"
integrity sha512-iDwf7mA03WPiR8QxvcVHmVWEPfMY1RZXerDVNCRYW7dUr2ppH3J58Rwb39/WG39yTZdRSxr3x+2v22tvI0VEvA==
-mapbox-gl-leaflet@^0.0.16:
- version "0.0.16"
- resolved "https://registry.yarnpkg.com/mapbox-gl-leaflet/-/mapbox-gl-leaflet-0.0.16.tgz#25208897bbffbe8e2c1f15d3710f253c19a0a533"
- integrity sha512-w4bpZrKHOWDZqUzhDOjIPL6Pc4tD10TVR/z8Iwp3hlUaf8PVqfxPINrcBLkcOg0+xFZSX3uka6Vl6NeO7KUYXw==
-
-mapbox-gl@^3.7.0:
- version "3.8.0"
- resolved "https://registry.yarnpkg.com/mapbox-gl/-/mapbox-gl-3.8.0.tgz#81330a033e34b8a3b62f01c3f5d853e2307ef755"
- integrity sha512-7iQ6wxAf8UedbNYTzNsyr2J25ozIBA4vmKY0xUDXQlHEokulzPENwjjmLxHQGRylDpOmR0c8kPEbtHCaQE2eMw==
- dependencies:
- "@mapbox/jsonlint-lines-primitives" "^2.0.2"
- "@mapbox/mapbox-gl-supported" "^3.0.0"
- "@mapbox/point-geometry" "^0.1.0"
- "@mapbox/tiny-sdf" "^2.0.6"
- "@mapbox/unitbezier" "^0.0.1"
- "@mapbox/vector-tile" "^1.3.1"
- "@mapbox/whoots-js" "^3.1.0"
- "@types/geojson" "^7946.0.14"
- "@types/geojson-vt" "^3.2.5"
- "@types/mapbox__point-geometry" "^0.1.4"
- "@types/mapbox__vector-tile" "^1.3.4"
- "@types/pbf" "^3.0.5"
- "@types/supercluster" "^7.1.3"
- cheap-ruler "^4.0.0"
- csscolorparser "~1.0.3"
- earcut "^3.0.0"
- geojson-vt "^4.0.2"
- gl-matrix "^3.4.3"
- grid-index "^1.1.0"
- kdbush "^4.0.2"
- murmurhash-js "^1.0.0"
- pbf "^3.2.1"
- potpack "^2.0.0"
- quickselect "^3.0.0"
- serialize-to-js "^3.1.2"
- supercluster "^8.0.1"
- tinyqueue "^3.0.0"
- vt-pbf "^3.1.3"
-
marchingsquares@^1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/marchingsquares/-/marchingsquares-1.3.3.tgz#67404af4b883ade3a589221f4e9dd010a1f706fc"
@@ -8690,11 +8541,6 @@ ms@^2.1.1, ms@^2.1.3:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
-murmurhash-js@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/murmurhash-js/-/murmurhash-js-1.0.0.tgz#b06278e21fc6c37fa5313732b0412bcb6ae15f51"
- integrity sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==
-
mz@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
@@ -9038,14 +8884,6 @@ pathe@1.1.2:
resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec"
integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==
-pbf@^3.2.1:
- version "3.3.0"
- resolved "https://registry.yarnpkg.com/pbf/-/pbf-3.3.0.tgz#1790f3d99118333cc7f498de816028a346ef367f"
- integrity sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==
- dependencies:
- ieee754 "^1.1.12"
- resolve-protobuf-schema "^2.1.0"
-
pdfjs-dist@4.4.168:
version "4.4.168"
resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-4.4.168.tgz#4487716376a33c68753ed37f782ae91d1c9ef8fa"
@@ -9178,11 +9016,6 @@ postcss@^8.4.23:
picocolors "^1.1.1"
source-map-js "^1.2.1"
-potpack@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/potpack/-/potpack-2.0.0.tgz#61f4dd2dc4b3d5e996e3698c0ec9426d0e169104"
- integrity sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw==
-
prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@@ -9214,11 +9047,6 @@ property-information@^6.0.0:
resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.5.0.tgz#6212fbb52ba757e92ef4fb9d657563b933b7ffec"
integrity sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==
-protocol-buffers-schema@^3.3.1:
- version "3.6.0"
- resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz#77bc75a48b2ff142c1ad5b5b90c94cd0fa2efd03"
- integrity sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==
-
punycode@^2.1.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
@@ -9239,11 +9067,6 @@ quickselect@^2.0.0:
resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018"
integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==
-quickselect@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-3.0.0.tgz#a37fc953867d56f095a20ac71c6d27063d2de603"
- integrity sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==
-
rbush@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/rbush/-/rbush-2.0.2.tgz#bb6005c2731b7ba1d5a9a035772927d16a614605"
@@ -9551,13 +9374,6 @@ resolve-pkg-maps@^1.0.0:
resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f"
integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==
-resolve-protobuf-schema@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz#9ca9a9e69cf192bbdaf1006ec1973948aa4a3758"
- integrity sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==
- dependencies:
- protocol-buffers-schema "^3.3.1"
-
resolve@^1.1.7, resolve@^1.14.2, resolve@^1.22.2, resolve@^1.22.4:
version "1.22.8"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
@@ -9675,11 +9491,6 @@ semver@^7.3.5, semver@^7.6.0, semver@^7.6.3:
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
-serialize-to-js@^3.1.2:
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/serialize-to-js/-/serialize-to-js-3.1.2.tgz#844b8a1c2d72412f68ea30da55090b3fc8e95790"
- integrity sha512-owllqNuDDEimQat7EPG0tH7JjO090xKNzUtYz6X+Sk2BXDnOCilDdNLwjWeFywG9xkJul1ULvtUQa9O4pUaY0w==
-
set-blocking@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
@@ -9980,13 +9791,6 @@ sucrase@^3.32.0:
pirates "^4.0.1"
ts-interface-checker "^0.1.9"
-supercluster@^8.0.1:
- version "8.0.1"
- resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-8.0.1.tgz#9946ba123538e9e9ab15de472531f604e7372df5"
- integrity sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==
- dependencies:
- kdbush "^4.0.2"
-
supports-color@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
@@ -10146,11 +9950,6 @@ tinyqueue@^2.0.0, tinyqueue@^2.0.3:
resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-2.0.3.tgz#64d8492ebf39e7801d7bd34062e29b45b2035f08"
integrity sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==
-tinyqueue@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-3.0.0.tgz#101ea761ccc81f979e29200929e78f1556e3661e"
- integrity sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==
-
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
@@ -10450,15 +10249,6 @@ vfile@^6.0.0:
"@types/unist" "^3.0.0"
vfile-message "^4.0.0"
-vt-pbf@^3.1.3:
- version "3.1.3"
- resolved "https://registry.yarnpkg.com/vt-pbf/-/vt-pbf-3.1.3.tgz#68fd150756465e2edae1cc5c048e063916dcfaac"
- integrity sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==
- dependencies:
- "@mapbox/point-geometry" "0.1.0"
- "@mapbox/vector-tile" "^1.3.1"
- pbf "^3.2.1"
-
warning@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"