diff --git a/docs/programming/logging.md b/docs/programming/logging.md index a39eec8a0b3..3dd9200d39f 100644 --- a/docs/programming/logging.md +++ b/docs/programming/logging.md @@ -11,7 +11,7 @@ The logger class can be found here: [https://github.com/Canadian-Geospatial-Pla The `logger` provides functions for high-level logging abstraction following best-practices concepts and the following constants: ```ts // The most detailed messages. Disabled by default. Only shows if actually running in dev environment, never shown otherwise. -export const LOG_TRACE_DETAILED 1; +export const LOG_TRACE_DETAILED = 1; // For tracing useEffect unmounting. Disabled by default. Only shows if running in dev environment or GEOVIEW_LOG_ACTIVE key is set in local storage. export const LOG_TRACE_USE_EFFECT_UNMOUNT = 2; // For tracing rendering. Disabled by default. Only shows if running in dev environment or GEOVIEW_LOG_ACTIVE key is set in local storage. diff --git a/packages/geoview-core/public/configs/package-footer-panel-geochart-map1-config-geochart.json b/packages/geoview-core/public/configs/package-footer-panel-geochart-map1-config-geochart.json index cfb75decb96..9bc3a7637b5 100644 --- a/packages/geoview-core/public/configs/package-footer-panel-geochart-map1-config-geochart.json +++ b/packages/geoview-core/public/configs/package-footer-panel-geochart-map1-config-geochart.json @@ -137,7 +137,7 @@ { "layers": [ { - "layerId": "geojsonLYR5/polygons.json" + "layerId": "geojsonLYR5/geojsonLYR5/polygons.json" } ], "chart": "pie", @@ -232,7 +232,7 @@ { "layers": [ { - "layerId": "geojsonLYR5/point-feature-group/points.json" + "layerId": "geojsonLYR5/geojsonLYR5/point-feature-group/points.json" } ], "chart": "bar", diff --git a/packages/geoview-core/public/configs/package-footer-panel-geochart-map2-config-geochart.json b/packages/geoview-core/public/configs/package-footer-panel-geochart-map2-config-geochart.json index cfb75decb96..9bc3a7637b5 100644 --- a/packages/geoview-core/public/configs/package-footer-panel-geochart-map2-config-geochart.json +++ b/packages/geoview-core/public/configs/package-footer-panel-geochart-map2-config-geochart.json @@ -137,7 +137,7 @@ { "layers": [ { - "layerId": "geojsonLYR5/polygons.json" + "layerId": "geojsonLYR5/geojsonLYR5/polygons.json" } ], "chart": "pie", @@ -232,7 +232,7 @@ { "layers": [ { - "layerId": "geojsonLYR5/point-feature-group/points.json" + "layerId": "geojsonLYR5/geojsonLYR5/point-feature-group/points.json" } ], "chart": "bar", diff --git a/packages/geoview-core/public/templates/package-footer-panel-geochart.html b/packages/geoview-core/public/templates/package-footer-panel-geochart.html index d9dbcecdd29..6c88e8c5fcd 100644 --- a/packages/geoview-core/public/templates/package-footer-panel-geochart.html +++ b/packages/geoview-core/public/templates/package-footer-panel-geochart.html @@ -40,7 +40,7 @@

Package Footer Panel With Geo-Chart



-
+
diff --git a/packages/geoview-core/src/api/event-processors/event-processor-children/geochart-event-processor.ts b/packages/geoview-core/src/api/event-processors/event-processor-children/geochart-event-processor.ts new file mode 100644 index 00000000000..359e658b64c --- /dev/null +++ b/packages/geoview-core/src/api/event-processors/event-processor-children/geochart-event-processor.ts @@ -0,0 +1,59 @@ +import { GeoviewStoreType } from '@/core/stores/geoview-store'; +import { AbstractEventProcessor } from '../abstract-event-processor'; +import { getGeoViewStore } from '@/core/stores/stores-managers'; +import { TypeJsonObject } from '@/core/types/global-types'; +import { GeoChartStoreByLayerPath } from '@/core/stores/store-interface-and-intial-values/geochart-state'; + +export class GeochartEventProcessor extends AbstractEventProcessor { + onInitialize(store: GeoviewStoreType) { + store.getState(); + + // add to arr of subscriptions so it can be destroyed later + this.subscriptionArr.push(); + } + + // ********************************************************** + // Static functions for Typescript files to access store actions + // ********************************************************** + //! Typescript MUST always use store action to modify store - NEVER use setState! + //! Some action does state modifications AND map actions. + //! ALWAYS use map event processor when an action modify store and IS NOT trap by map state event handler + + // #region + + /** + * Set the default layers from configuration. + * In the store, the GeoChart configurations are stored in an object with layerPath as its property name + * (to retrieve the configuration per layer faster). + * + * @param {string} mapId the map id + * @param {TypeJsonObject} charts The array of JSON configuration for geochart + */ + static setGeochartCharts(mapId: string, charts: TypeJsonObject[]): void { + // The store object representation + const chartData: GeoChartStoreByLayerPath = {}; + + // Loop on the charts + // eslint-disable-next-line @typescript-eslint/no-explicit-any + charts.forEach((chartInfo: any) => { + // For each layer path + // eslint-disable-next-line @typescript-eslint/no-explicit-any + chartInfo.layers.forEach((layer: any) => { + // Get the layer path + const layerPath = layer.layerId; + chartData[layerPath] = chartInfo; + }); + }); + + // set store charts config + getGeoViewStore(mapId).getState().geochartState.actions.setGeochartCharts(chartData); + } + + // #endregion + + // ********************************************************** + // Static functions for Store Map State to action on API + // ********************************************************** + //! NEVER add a store action who does set state AND map action at a same time. + //! Review the action in store state to make sure +} diff --git a/packages/geoview-core/src/api/event-processors/index.ts b/packages/geoview-core/src/api/event-processors/index.ts deleted file mode 100644 index 2034644e339..00000000000 --- a/packages/geoview-core/src/api/event-processors/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { AppEventProcessor } from '@/api/event-processors/event-processor-children/app-event-processor'; -import { FeatureInfoEventProcessor } from '@/api/event-processors/event-processor-children/feature-info-event-processor'; -import { GeoviewStoreType } from '@/core/stores/geoview-store'; -import { LegendEventProcessor } from '@/api/event-processors/event-processor-children/legend-event-processor'; -import { MapEventProcessor } from '@/api/event-processors/event-processor-children/map-event-processor'; -import { TimeSliderEventProcessor } from '@/api/event-processors/event-processor-children/time-slider-event-processor'; - -const appEventProcessor = new AppEventProcessor(); -const featureInfoEventProcessor = new FeatureInfoEventProcessor(); -const legendEventProcessor = new LegendEventProcessor(); -const mapEventProcessor = new MapEventProcessor(); -const timeSliderEventProcessor = new TimeSliderEventProcessor(); - -export function initializeEventProcessors(store: GeoviewStoreType) { - // core stores - appEventProcessor.onInitialize(store); - featureInfoEventProcessor.onInitialize(store); - legendEventProcessor.onInitialize(store); - mapEventProcessor.onInitialize(store); - - // package stores, only create if needed - // TODO: Change this check for something more generic that checks in appBarTabs too - if (store.getState().mapConfig!.footerTabs?.tabs.core.includes('time-slider')) timeSliderEventProcessor.onInitialize(store); -} - -export function destroyEventProcessors(store: GeoviewStoreType) { - // core stores - appEventProcessor.onDestroy(store); - featureInfoEventProcessor.onDestroy(store); - legendEventProcessor.onDestroy(store); - mapEventProcessor.onDestroy(store); - - // package stores, only destroy if created - // TODO: Change this check for something more generic that checks in appBarTabs too - if (store.getState().mapConfig!.footerTabs?.tabs.core.includes('time-slider')) timeSliderEventProcessor.onDestroy(store); -} diff --git a/packages/geoview-core/src/core/app-start.tsx b/packages/geoview-core/src/core/app-start.tsx index 7f44ec483d8..7a33e895e17 100644 --- a/packages/geoview-core/src/core/app-start.tsx +++ b/packages/geoview-core/src/core/app-start.tsx @@ -1,4 +1,4 @@ -import React, { Suspense, useMemo } from 'react'; +import { createContext, Suspense, useMemo } from 'react'; import './translation/i18n'; import i18n from 'i18next'; @@ -15,7 +15,7 @@ import { api, useAppDisplayLanguageById, useAppDisplayThemeById } from '@/app'; // create a state that will hold map config information // TODO: use store, only keep map id on context for store manager to gather right store on hooks -export const MapContext = React.createContext({ +export const MapContext = createContext({ mapId: '', mapFeaturesConfig: undefined, }); @@ -76,7 +76,9 @@ function AppStart(props: AppStartProps): JSX.Element { + {/* */} + {/* */} diff --git a/packages/geoview-core/src/core/components/common/layer-list.tsx b/packages/geoview-core/src/core/components/common/layer-list.tsx index e167d5bf794..683b8aa8904 100644 --- a/packages/geoview-core/src/core/components/common/layer-list.tsx +++ b/packages/geoview-core/src/core/components/common/layer-list.tsx @@ -7,9 +7,9 @@ import { IconStack } from '@/app'; export interface LayerListEntry { layerName: string; layerPath: string; - layerFeatures?: ReactNode | undefined; - mapFilteredIcon?: ReactNode | undefined; - tooltip?: ReactNode | undefined; + layerFeatures?: ReactNode; + mapFilteredIcon?: ReactNode; + tooltip?: ReactNode; numOffeatures?: number; } diff --git a/packages/geoview-core/src/core/components/common/layout.tsx b/packages/geoview-core/src/core/components/common/layout.tsx index ffa991475c0..6530d882df8 100644 --- a/packages/geoview-core/src/core/components/common/layout.tsx +++ b/packages/geoview-core/src/core/components/common/layout.tsx @@ -57,7 +57,7 @@ export function Layout({ children, layerList, handleLayerList, selectedLayerPath /> ); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedLayerPath, isEnlargeDataTable]); + }, [selectedLayerPath, isEnlargeDataTable, layerList]); return ( diff --git a/packages/geoview-core/src/core/stores/geoview-store.ts b/packages/geoview-core/src/core/stores/geoview-store.ts index 9a476f1d156..e71dcc4a0be 100644 --- a/packages/geoview-core/src/core/stores/geoview-store.ts +++ b/packages/geoview-core/src/core/stores/geoview-store.ts @@ -10,6 +10,7 @@ import { ILayerState, initializeLayerState } from './store-interface-and-intial- import { IMapState, initializeMapState } from './store-interface-and-intial-values/map-state'; import { IMapDataTableState, initialDataTableState } from './store-interface-and-intial-values/data-table-state'; import { ITimeSliderState, initializeTimeSliderState } from './store-interface-and-intial-values/time-slider-state'; +import { IGeochartState, initializeGeochartState } from './store-interface-and-intial-values/geochart-state'; import { IUIState, initializeUIState } from './store-interface-and-intial-values/ui-state'; import { TypeMapFeaturesConfig } from '@/core/types/global-types'; @@ -26,14 +27,17 @@ export interface IGeoviewState { mapId: string; setMapConfig: (config: TypeMapFeaturesConfig) => void; - // state interfaces + // core state interfaces appState: IAppState; detailsState: IDetailsState; dataTableState: IMapDataTableState; layerState: ILayerState; mapState: IMapState; - timeSliderState: ITimeSliderState; uiState: IUIState; + + // packages state interface + geochartState: IGeochartState; + timeSliderState: ITimeSliderState; } export const geoviewStoreDefinition = (set: TypeSetStore, get: TypeGetStore) => { @@ -56,6 +60,7 @@ export const geoviewStoreDefinition = (set: TypeSetStore, get: TypeGetStore) => // packages states, only create if needed // TODO: Change this check for something more generic that checks in appBarTabs too if (config.footerTabs?.tabs.core.includes('time-slider')) set({ timeSliderState: initializeTimeSliderState(set, get) }); + if (config.footerTabs?.tabs.core.includes('geochart')) set({ geochartState: initializeGeochartState(set, get) }); }, // core states diff --git a/packages/geoview-core/src/core/stores/store-interface-and-intial-values/geochart-state.ts b/packages/geoview-core/src/core/stores/store-interface-and-intial-values/geochart-state.ts new file mode 100644 index 00000000000..812453f1795 --- /dev/null +++ b/packages/geoview-core/src/core/stores/store-interface-and-intial-values/geochart-state.ts @@ -0,0 +1,55 @@ +import { useStore } from 'zustand'; +import { useGeoViewStore } from '../stores-managers'; +import { TypeGetStore, TypeSetStore } from '../geoview-store'; + +export type GeoChartStoreByLayerPath = { + [layerPath: string]: ChartInfo; +}; + +export type ChartInfo = unknown; // unknown, because the definition is in the external package + +// #region INTERFACES +export interface IGeochartState { + geochartChartsConfig: GeoChartStoreByLayerPath; + // geochartLayers: TypeJsonObject[]; + // visibleGeochartLayers: string[]; + + actions: { + setGeochartCharts: (charts: GeoChartStoreByLayerPath) => void; + // setGeochartLayers: (layers: TypeJsonObject) => void; + }; +} +// #endregion INTERFACES + +export function initializeGeochartState(set: TypeSetStore, get: TypeGetStore): IGeochartState { + const init = { + geochartChartsConfig: {}, + // geochartLayers: {}, + // geochartChartsConfig: [], + // visibleGeochartLayers: [], + + // #region ACTIONS + actions: { + setGeochartCharts(charts: GeoChartStoreByLayerPath): void { + set({ + geochartState: { + ...get().geochartState, + geochartChartsConfig: charts, + }, + }); + }, + // #endregion ACTIONS + }, + } as IGeochartState; + + return init; +} + +// ********************************************************** +// Layer state selectors +// ********************************************************** +export const useGeochartConfigs = () => useStore(useGeoViewStore(), (state) => state.geochartState.geochartChartsConfig); +// export const useGeochartLayers = () => useStore(useGeoViewStore(), (state) => state.geochartState.geochartLayers); +// export const useVisibleGeochartLayers = () => useStore(useGeoViewStore(), (state) => state.geochartState.visibleGeochartLayers); + +export const useGeochartStoreActions = () => useStore(useGeoViewStore(), (state) => state.geochartState.actions); diff --git a/packages/geoview-core/src/core/stores/store-interface-and-intial-values/layer-state.ts b/packages/geoview-core/src/core/stores/store-interface-and-intial-values/layer-state.ts index 4c777589af6..3ae53a368d7 100644 --- a/packages/geoview-core/src/core/stores/store-interface-and-intial-values/layer-state.ts +++ b/packages/geoview-core/src/core/stores/store-interface-and-intial-values/layer-state.ts @@ -303,7 +303,7 @@ function findLayerByPath(layers: TypeLegendLayer[], layerPath: string): TypeLege if (layerPath === l.layerPath) { return l; } - if (layerPath.startsWith(l.layerPath) && l.children?.length > 0) { + if (layerPath?.startsWith(l.layerPath) && l.children?.length > 0) { const result: TypeLegendLayer | undefined = findLayerByPath(l.children, layerPath); if (result) { return result; diff --git a/packages/geoview-core/src/core/stores/store-interface-and-intial-values/time-slider-state.ts b/packages/geoview-core/src/core/stores/store-interface-and-intial-values/time-slider-state.ts index 086c711ccbd..aa421d9b7f7 100644 --- a/packages/geoview-core/src/core/stores/store-interface-and-intial-values/time-slider-state.ts +++ b/packages/geoview-core/src/core/stores/store-interface-and-intial-values/time-slider-state.ts @@ -3,6 +3,7 @@ import { useGeoViewStore } from '../stores-managers'; import { TypeGetStore, TypeSetStore } from '../geoview-store'; import { TimeSliderEventProcessor } from '@/api/event-processors/event-processor-children/time-slider-event-processor'; +// #region INTERFACES export interface TypeTimeSliderValues { title?: string; description?: string; @@ -23,6 +24,7 @@ export interface TypeTimeSliderValues { export interface ITimeSliderState { timeSliderLayers: { [index: string]: TypeTimeSliderValues }; visibleTimeSliderLayers: string[]; + actions: { addTimeSliderLayer: (newLayer: { [index: string]: TypeTimeSliderValues }) => void; applyFilters: (layerPath: string, values: number[]) => void; @@ -38,11 +40,14 @@ export interface ITimeSliderState { setVisibleTimeSliderLayers: (visibleLayerPaths: string[]) => void; }; } +// #endregion INTERFACES export function initializeTimeSliderState(set: TypeSetStore, get: TypeGetStore): ITimeSliderState { const init = { timeSliderLayers: {}, visibleTimeSliderLayers: [], + + // #region ACTIONS actions: { addTimeSliderLayer(newLayer: { [index: string]: TypeTimeSliderValues }): void { set({ @@ -157,6 +162,7 @@ export function initializeTimeSliderState(set: TypeSetStore, get: TypeGetStore): }, }); }, + // #endregion ACTIONS }, } as ITimeSliderState; @@ -168,4 +174,5 @@ export function initializeTimeSliderState(set: TypeSetStore, get: TypeGetStore): // ********************************************************** export const useTimeSliderLayers = () => useStore(useGeoViewStore(), (state) => state.timeSliderState.timeSliderLayers); export const useVisibleTimeSliderLayers = () => useStore(useGeoViewStore(), (state) => state.timeSliderState.visibleTimeSliderLayers); + export const useTimeSliderStoreActions = () => useStore(useGeoViewStore(), (state) => state.timeSliderState.actions); diff --git a/packages/geoview-core/src/ui/autocomplete/autocomplete.tsx b/packages/geoview-core/src/ui/autocomplete/autocomplete.tsx index a3d9942833e..5ed8482a48c 100644 --- a/packages/geoview-core/src/ui/autocomplete/autocomplete.tsx +++ b/packages/geoview-core/src/ui/autocomplete/autocomplete.tsx @@ -9,10 +9,7 @@ export interface TypeAutocompleteProps< DisableClearable extends boolean | undefined = undefined, FreeSolo extends boolean | undefined = undefined > extends AutocompleteProps { - // eslint-disable-next-line react/require-default-props - mapId?: string; - // eslint-disable-next-line react/require-default-props - fullWidth?: boolean; + fullWidth: boolean; } /** diff --git a/packages/geoview-geochart/src/geochart-panel.tsx b/packages/geoview-geochart/src/geochart-panel.tsx index e816fa7c034..bc4315c82e6 100644 --- a/packages/geoview-geochart/src/geochart-panel.tsx +++ b/packages/geoview-geochart/src/geochart-panel.tsx @@ -1,21 +1,24 @@ +import { useTranslation } from 'react-i18next'; +import { useTheme } from '@mui/material/styles'; import { TypeWindow } from 'geoview-core'; import { ChartType, SchemaValidator } from 'geochart'; import { LayerListEntry, Layout } from 'geoview-core/src/core/components/common'; +import { TypeArrayOfLayerData, TypeLayerData } from 'geoview-core/src/api/events/payloads'; +import { Typography } from 'geoview-core/src/ui/typography/typography'; +import { Paper } from 'geoview-core/src/ui'; +import { logger } from 'geoview-core/src/core/utils/logger'; +import { useMapVisibleLayers } from 'geoview-core/src/core/stores/store-interface-and-intial-values/map-state'; +import { useDetailsStoreLayerDataArray } from 'geoview-core/src/core/stores'; +import { useGeochartConfigs } from 'geoview-core/src/core/stores/store-interface-and-intial-values/geochart-state'; import { GeoChart } from './geochart'; -import { PluginGeoChartConfig } from './geochart-types'; +import { GeoViewGeoChartConfig } from './geochart-types'; + +import { getSxClasses } from './geochart-style'; interface GeoChartPanelProps { mapId: string; - configObj: PluginGeoChartConfig; - layerList: LayerListEntry[]; } -const { cgpv } = window as TypeWindow; - -type GeoChartLayerChartConfig = { - [layerPath: string]: PluginGeoChartConfig; -}; - /** * Geo Chart tab * @@ -23,41 +26,130 @@ type GeoChartLayerChartConfig = { * @returns {JSX.Element} Geo Chart tab */ export function GeoChartPanel(props: GeoChartPanelProps): JSX.Element { - const { mapId, configObj, layerList } = props; - const { react, ui } = cgpv; - const { useState } = react; - const { Box } = ui.elements; + // Log + logger.logTraceRender('geochart/geochart-panel'); + + const { cgpv } = window as TypeWindow; + const { mapId } = props; + const { react } = cgpv; + const { useState, useCallback, useEffect } = react; + + const { t } = useTranslation(); + const theme = useTheme(); + const sxClasses = getSxClasses(theme); + + // Prepare the states const [selectedLayerPath, setSelectedLayerPath] = useState(''); + const [geoChartLayersList, setGeoChartLayersList] = useState([]); - // For a GeoChart to be used in a footer panel with a layer selection happening independently from the GeoChart itself, - // we want 1 chart per layer, so this splits the main config in multiple chunks for each chart layer - const layerChartConfigsState: GeoChartLayerChartConfig = {}; - configObj.charts.forEach((chart) => { - // Retrieve a chart config for each layer - const specificChartConfig = { charts: [chart] }; - const lyrPaths = chart.layers?.map((lyr) => { - return lyr.layerId; - }); - lyrPaths?.forEach((lyrPath) => { - if (!(lyrPath in layerChartConfigsState)) { - layerChartConfigsState[lyrPath] = specificChartConfig; - } - }); - }); - const [layerChartConfigs] = useState(layerChartConfigsState); + // get store geochart info + const configObj = useGeochartConfigs(); + const visibleLayers = useMapVisibleLayers() as string[]; + const arrayOfLayerData = useDetailsStoreLayerDataArray() as TypeArrayOfLayerData; // Create the validator shared for all the charts in the footer const [schemaValidator] = useState(new SchemaValidator()); + /** + * Get number of features of a layer. + * @returns string + */ + const getFeaturesOfLayer = useCallback( + (layer: TypeLayerData): string => { + const numOfFeatures = layer.features?.length ?? 0; + return `${numOfFeatures} ${t('details.feature')}${numOfFeatures > 1 ? 's' : ''}`; + }, + [t] + ); + /** * Handles clicks to layers in left panel. Sets selected layer. * * @param {LayerListEntry} layer The data of the selected layer */ - const handleLayerChange = (layer: LayerListEntry): void => { + const handleLayerChange = useCallback((layer: LayerListEntry): void => { + // Log + logger.logTraceUseCallback('GEOCHART-PANEL - layer', layer); + + // Set the selected layer path setSelectedLayerPath(layer.layerPath); - }; + + // Don't redraw for now, it's a bit overkill and annoying. + // We don't need to redraw on every change of the layer. + // We need to redraw when the canvas isn't 'ready' in the DOM. The canvas is essentially + // not ready upon first load and when the user resizes the canvas placeholder. + // cgpv.api.maps[mapId].plugins.geochart.redrawChart(); + }, []); + + useEffect(() => { + // Log, leaving the logDebug here, because there's something funky going on across branches and this helps figuring it out + logger.logDebug('selected layer path changed', selectedLayerPath); + }, [selectedLayerPath]); + + // Reacts when the array of layer data updates + useEffect(() => { + // Log + logger.logTraceUseEffect('GEOCHART-PANEL - ArrayOfLayerData', arrayOfLayerData); + + // Update the layers list information + const layerListEntry: LayerListEntry[] = []; + arrayOfLayerData.forEach((layer) => { + // If the layer is visible and has a chart in the config + if (visibleLayers.includes(layer.layerPath) && configObj[layer.layerPath]) { + layerListEntry.push({ + layerName: layer.layerName ?? '', + layerPath: layer.layerPath, + numOffeatures: layer.features?.length ?? 0, + layerFeatures: getFeaturesOfLayer(layer), + tooltip: `${layer.layerName}, ${getFeaturesOfLayer(layer)}`, + }); + } + }); + + // Set the list + setGeoChartLayersList(layerListEntry); + }, [arrayOfLayerData, configObj, getFeaturesOfLayer, visibleLayers]); + + // Reacts when the list of layers being officially listed changed + useEffect(() => { + // Log + logger.logTraceUseEffect('GEOCHART-PANEL - GeoChartLayersList', geoChartLayersList, selectedLayerPath); + + // If there was a selected layer path already + let changeSelectedLayerPath = false; + if (selectedLayerPath) { + // Get the layer list entry for that layer path + const geoChartLayerEntry = geoChartLayersList.find((geoChartLayer: LayerListEntry) => geoChartLayer.layerPath === selectedLayerPath); + + // If found + if (geoChartLayerEntry) { + // Check if there's nothing currently selected on that layer path + if (!geoChartLayerEntry.numOffeatures) { + changeSelectedLayerPath = true; + } else { + // There is something, stay on it. + } + } + } else { + // Find another layer with features + changeSelectedLayerPath = true; + } + + // If changing + if (changeSelectedLayerPath) { + // Find another layer with features + const anotherGeoChartLayerEntry = geoChartLayersList.find((geoChartLayer: LayerListEntry) => geoChartLayer.numOffeatures); + // If found + if (anotherGeoChartLayerEntry) { + // Select that one + setSelectedLayerPath(anotherGeoChartLayerEntry.layerPath); + } else { + // None found, select none + setSelectedLayerPath(''); + } + } + }, [geoChartLayersList, selectedLayerPath]); /** * Renders a single GeoChart component @@ -65,31 +157,50 @@ export function GeoChartPanel(props: GeoChartPanelProps): JSX.Element { * @param sx CSSProperties Styling to apply (basically if the GeoChart should be visible or not depending on the selected layer) * @returns JSX.Element */ - const renderChart = (chartConfig: PluginGeoChartConfig, sx: React.CSSProperties, key: string) => { - return ; + const renderChart = (chartConfig: GeoViewGeoChartConfig, sx: React.CSSProperties, key: string) => { + return ; }; - // Loop on the layers to find out which is selected and grab the chart associated with the selected layer by moving its position to 0px - // TODO: Remove the TEMP REDRAW button, obviously. Maybe think of a better way to handle the chart rendering? Leaving it like this for now. - return ( - - - { - cgpv.api.maps[mapId].plugins.geochart.redrawChart(); - }} - /> - - {Object.entries(layerChartConfigs).map(([layerPath, layerChartConfig], index) => { - const sx: React.CSSProperties = { position: 'absolute', top: '-5000px' }; - if (layerPath === selectedLayerPath) { - sx.top = '0px'; - } - return renderChart(layerChartConfig, sx, index.toString()); - })} - - ); + /** + * Renders the complete GeoChart Panel component + * @returns JSX.Element + */ + const renderComplete = () => { + if (geoChartLayersList) { + if (geoChartLayersList.length > 0) { + return ( + + {selectedLayerPath && + Object.entries(configObj).map(([layerPath, layerChartConfig], index) => { + const sx: React.CSSProperties = { position: 'absolute', top: '-5000px' }; + if (layerPath === selectedLayerPath) { + sx.top = '0px'; + } + return renderChart(layerChartConfig as GeoViewGeoChartConfig, sx, index.toString()); + })} + {!selectedLayerPath && ( + + + {t('geochart.chartPanel.noLayers')} + + + {t('geochart.chartPanel.noLayers')} + + + )} + + ); + } + + // No layers + return {t('chartPanel.panel.noLayers')}; + } + + // Loading UI + return {t('chartPanel.panel.loadingUI')}; + // return ; + }; + + // Render + return renderComplete(); } diff --git a/packages/geoview-geochart/src/geochart-style.ts b/packages/geoview-geochart/src/geochart-style.ts new file mode 100644 index 00000000000..0b3d98775f4 --- /dev/null +++ b/packages/geoview-geochart/src/geochart-style.ts @@ -0,0 +1,11 @@ +import { Theme } from '@mui/material/styles'; + +export const getSxClasses = (theme: Theme) => ({ + detailsInstructionsTitle: { + font: theme.footerPanel.titleFont, + fontSize: '1.5rem', + }, + detailsInstructionsBody: { + fontSize: '1rem', + }, +}); diff --git a/packages/geoview-geochart/src/geochart.tsx b/packages/geoview-geochart/src/geochart.tsx index e96ae62f7e5..1ef6dfe63ed 100644 --- a/packages/geoview-geochart/src/geochart.tsx +++ b/packages/geoview-geochart/src/geochart.tsx @@ -46,7 +46,7 @@ export function GeoChart(props: GeoChartProps): JSX.Element { // Tweak the default colors based on the theme const defaultColors: GeoChartDefaultColors = { backgroundColor: theme.palette.background.default, - borderColor: theme.palette.border.primary, + borderColor: 'black', // theme.palette.primary, color: theme.palette.primary.main, }; diff --git a/packages/geoview-geochart/src/index-appbar.tsx b/packages/geoview-geochart/src/index-appbar.tsx index a57b48c537c..990b5af7366 100644 --- a/packages/geoview-geochart/src/index-appbar.tsx +++ b/packages/geoview-geochart/src/index-appbar.tsx @@ -1,12 +1,11 @@ import { Cast, AnySchemaObject, TypeIconButtonProps, TypePanelProps, toJsonObject, TypeJsonObject } from 'geoview-core'; import { AppBarPlugin } from 'geoview-core/src/api/plugin/appbar-plugin'; -import { ChartType, SchemaValidator } from 'geochart'; +import { ChartType } from 'geochart'; import { ChartIcon } from 'geoview-core/src/ui/icons'; import { PayloadBaseClassChart, EVENT_CHART_REDRAW } from './geochart-event-base'; import { PayloadChartConfig } from './geochart-event-config'; import { PluginGeoChartConfig } from './geochart-types'; -import { GeoChart } from './geochart'; import schema from '../schema.json'; import defaultConfig from '../default-config-geochart.json'; @@ -70,7 +69,9 @@ export class GeoChartAppBarPlugin extends AppBarPlugin { onCreateContent(): JSX.Element { // Fetch cgpv - return ; + // TODO: Create a geochart-appbar-panel equivalent to geochart-panel to hold the GeoChart itself and hook on the useGeochartConfigs store the same way geochart-panel does it + // return ; + return
Not implemented
; } /** diff --git a/packages/geoview-geochart/src/index.tsx b/packages/geoview-geochart/src/index.tsx index 7622432789c..7d4ffe0714b 100644 --- a/packages/geoview-geochart/src/index.tsx +++ b/packages/geoview-geochart/src/index.tsx @@ -2,9 +2,9 @@ import { Cast, AnySchemaObject, toJsonObject, TypeJsonObject } from 'geoview-cor import { FooterPlugin } from 'geoview-core/src/api/plugin/footer-plugin'; import { TypeTabs } from 'geoview-core/src/ui/tabs/tabs'; import { ChartType } from 'geochart'; -import { LayerListEntry } from 'geoview-core/src/core/components/common'; import { ChartIcon } from 'geoview-core/src/ui/icons'; +import { GeochartEventProcessor } from 'geoview-core/src/api/event-processors/event-processor-children/geochart-event-processor'; import { PayloadBaseClassChart, EVENT_CHART_REDRAW } from './geochart-event-base'; import { PayloadChartConfig } from './geochart-event-config'; import { PluginGeoChartConfig } from './geochart-types'; @@ -37,54 +37,32 @@ class GeoChartFooterPlugin extends FooterPlugin { en: { chartPanel: { title: 'Chart', + panel: { + noLayers: 'No layers with chart data', + }, }, }, fr: { chartPanel: { title: 'Graphique', + panel: { + noLayers: 'Pas de couches avec des données graphiques', + }, }, }, }); - onCreateContentProps(): TypeTabs { - // Cast the config - const chartConfig: PluginGeoChartConfig | undefined = this.configObj; - - // Create content - const layerList = chartConfig?.charts - .map((chart) => { - const layerIds = - chart.layers?.map((layer) => { - return layer.layerId; - }) ?? []; - - return layerIds; - }) - .flat() - .reduce((acc, curr) => { - if (this.api.maps[this.pluginProps.mapId].layer.registeredLayers[curr]) { - const currLayer = this.api.maps[this.pluginProps.mapId].layer.registeredLayers[curr]; - const layerName = - currLayer.layerName && this.displayLanguage() in currLayer.layerName - ? currLayer.layerName[this.displayLanguage()] - : currLayer.layerName; - const layerData = { - layerName, - layerPath: curr, - tooltip: layerName, - }; - acc.push(layerData); - } + onAdd(): void { + // Initialize the store with geochart provided configuration + GeochartEventProcessor.setGeochartCharts(this.pluginProps.mapId, this.configObj.charts); - return acc; - }, [] as LayerListEntry[]); + // Call parent + super.onAdd(); + } - // If any layers list - let content =
No layers in config
; - if (layerList) { - // Create element - content = ; - } + onCreateContentProps(): TypeTabs { + // Create element + const content = ; return { id: 'geochart', diff --git a/packages/geoview-time-slider/src/time-slider-panel.tsx b/packages/geoview-time-slider/src/time-slider-panel.tsx index 2d69a9e928c..699a589be74 100644 --- a/packages/geoview-time-slider/src/time-slider-panel.tsx +++ b/packages/geoview-time-slider/src/time-slider-panel.tsx @@ -2,6 +2,7 @@ import { TypeWindow } from 'geoview-core'; import { LayerListEntry, Layout } from 'geoview-core/src/core/components/common'; import { useVisibleTimeSliderLayers, useTimeSliderLayers } from 'geoview-core/src/core/stores'; import { getLocalizedMessage } from 'geoview-core/src/core/utils/utilities'; +import { Typography } from 'geoview-core/src/ui'; import { TimeSlider } from './time-slider'; import { ConfigProps } from './time-slider-types'; @@ -20,9 +21,8 @@ const { cgpv } = window as TypeWindow; */ export function TimeSliderPanel(props: TypeTimeSliderProps): JSX.Element { const { mapId, configObj } = props; - const { react, ui } = cgpv; - const { useState, useEffect } = react; - const { Box } = ui.elements; + const { react } = cgpv; + const { useState, useEffect, useCallback } = react; // internal state const [selectedLayerPath, setSelectedLayerPath] = useState(); @@ -44,18 +44,19 @@ export function TimeSliderPanel(props: TypeTimeSliderProps): JSX.Element { // eslint-disable-next-line react-hooks/exhaustive-deps }, [visibleTimeSliderLayers]); - if (!visibleTimeSliderLayers.length) return {getLocalizedMessage(mapId, 'timeSlider.panel.noLayers')}; + const renderLayerList = useCallback(() => { + const array = visibleTimeSliderLayers.map((layerPath: string) => { + return { layerName: timeSliderLayers[layerPath].name, layerPath, tooltip: timeSliderLayers[layerPath].name }; + }); + + return array; + }, [timeSliderLayers, visibleTimeSliderLayers]); + return selectedLayerPath ? ( - { - return { layerName: timeSliderLayers[layerPath].name, layerPath, tooltip: timeSliderLayers[layerPath].name }; - })} - > + ) : ( - + {getLocalizedMessage(mapId, 'timeSlider.panel.noLayers')} ); }