From abb2c2ba5c6377001580e89f501269a70a24bdeb Mon Sep 17 00:00:00 2001 From: "Kuchenik, Venessa J" Date: Mon, 10 Aug 2026 15:02:37 +0000 Subject: [PATCH] Added plot of seismic events count with date picker feature in the settings --- .../src/components/Widgets/SeismicEvents.jsx | 155 ++++++++++++++++++ .../settings/SeismicEventsSettings.jsx | 64 ++++++++ client/src/constants/widgetRegistry.jsx | 14 +- 3 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 client/src/components/Widgets/SeismicEvents.jsx create mode 100644 client/src/components/Widgets/settings/SeismicEventsSettings.jsx diff --git a/client/src/components/Widgets/SeismicEvents.jsx b/client/src/components/Widgets/SeismicEvents.jsx new file mode 100644 index 0000000..037ab48 --- /dev/null +++ b/client/src/components/Widgets/SeismicEvents.jsx @@ -0,0 +1,155 @@ +import VdapApi from '../../hooks/useVdapApi.js'; +import { useMemo } from 'react'; +import Plot from 'react-plotly.js'; +import {Box, Flex, Spinner, Text} from "@chakra-ui/react"; + +export default function SeismicEvents({ settings, widgetID}) { + const url = useMemo(() =>{ + if (settings?.startDate && settings?.endDate) + return '/api/v2/seismic_events?start_date=' + settings?.startDate + '&end_date=' + settings?.endDate; + else { + const base_url = '/api/v2/seismic_events'; + let today = new Date(); + let sevenDaysAgo = new Date(today); + sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7); + + sevenDaysAgo = sevenDaysAgo.toLocaleDateString('en-CA'); + today = today.toLocaleDateString('en-CA'); + + return base_url + '?start_date=' + sevenDaysAgo + '&end_date=' + today; + } + }, [settings?.startDate, settings?.endDate]); + + console.log(url); + const { data: apiData, isLoading, error } = VdapApi(url); + + const plotTraces = useMemo(() => { + if (!apiData || apiData.length === 0) return []; + + // Array of distinct Plotly shapes + const Symbolway = [ + 'circle', 'square', 'diamond', + 'triangle-up', 'triangle-down', + 'star', 'hexagram', 'pentagon', + 'hexagon', 'octagon', 'cross', 'x' + ]; + + // Sort data descending by seismic events count so smaller dots draw on top of larger ones + const sortedData = [...apiData].sort((a, b) => b.number_events - a.number_events); + + // Efficiently group into Plotly traces by volcano + const groupedData = sortedData.reduce((acc, curr) => { + const name = curr.volcano_name || 'Unknown'; + if (!acc[name]) acc[name] = { x: [], y: [] }; + + // Map the seismic-specific keys + acc[name].x.push(new Date(curr.ts_epoch)); + acc[name].y.push(curr.number_events); + return acc; + }, {}); + + return Object.keys(groupedData).map((volcanoName, index) => ({ + name: volcanoName, + x: groupedData[volcanoName].x, + y: groupedData[volcanoName].y, + type: 'scatter', + mode: 'markers', + marker: { + size: 7, + line: { width: .5, color: 'rgba(255, 255, 255, 0.7)' }, + symbol: Symbolway[index % Symbolway.length], + }, + // Replicating the clean D3/Observable tooltip + hovertemplate: + `${volcanoName}
` + + 'Date (UTC): %{x|%b %d %Y %H:%M}
' + + 'Number of Seismic Events: %{y}' + })); + }, [apiData]); + + const layout = useMemo(() => { + + return { + autosize: true, + paper_bgcolor: 'transparent', // Let Chakra UI background show through + plot_bgcolor: 'transparent', + font: { color: '#d1d5db' }, // gray.300 + margin: { t: 40, r: 20, l: 20, b: 20 }, + // title: { + // text: '', + // font: { size: 16, color: 'white' }, + // x: 0.05 + // }, + xaxis: { + title: { + text: 'Date (UTC)', + font: { color: 'white', size: 12 } + }, + type: 'date', + tickformat: '%b %d
%Y', // D3 requested %b %d \n %Y formatting + gridcolor: '#4b5563', + zerolinecolor: '#4b5563', + automargin: true, + }, + yaxis: { + title: { + text: 'Seismic Count', + font: { color: 'white', size: 12 } + }, + type: 'linear', + gridcolor: '#4b5563', + zerolinecolor: '#ffffff', + automargin: true, + }, + // legend: { + // orientation: 'h', + // y: -0.2, + // x: 0.5, + // xanchor: 'center' + // }, + hovermode: 'closest', + shapes: [ + { + type: 'line', + x0: 0, + x1: 1, + xref: 'paper', // Spans entire chart width + y0: 0, + y1: 0, + yref: 'y', + line: { color: 'white', width: 1 } + } + ], + }; + }, []); + + if (isLoading) return ( + + + + ); + + if (error) return ( + + {error} + + ); + + if (plotTraces.length === 0) return ( + + No data available for current filter. + + ); + + return ( + + + + ); +} \ No newline at end of file diff --git a/client/src/components/Widgets/settings/SeismicEventsSettings.jsx b/client/src/components/Widgets/settings/SeismicEventsSettings.jsx new file mode 100644 index 0000000..03bc0ba --- /dev/null +++ b/client/src/components/Widgets/settings/SeismicEventsSettings.jsx @@ -0,0 +1,64 @@ +import {Input, Field, Text, Flex} from "@chakra-ui/react"; +import {useDashboardStore} from "@/store/useDashboardStore.jsx"; +import { useState } from "react"; + +export default function SeismicEventsSettings({ widgetId, currentSettings }) { + const [startError, setStartError] = useState(null); + const [endError, setEndError] = useState(null); + const updateSettings = useDashboardStore(state => state.updateWidgetSettings); + + return ( + + + + + Start Date + + + { + const value = e.target.value; + updateSettings(widgetId, {...currentSettings, startDate: value}); + + const dateSelected = new Date(value); + const today = new Date(); + today.setHours(0, 0, 0, 0); + + if (value && (dateSelected > today)) setStartError("Can't Choose a Future Date"); + else setStartError(""); + }} + /> + {startError && {startError}} + + + + + End Date + + + { + const value = e.target.value; + updateSettings(widgetId, {...currentSettings, endDate: value}); + + const dateSelected = new Date(value); + const today = new Date(); + today.setHours(0, 0, 0, 0); + + if (value && (dateSelected > today)) setEndError("Can't Choose a Future Date"); + else setEndError(""); + }} + /> + {endError && {endError}} + + + + + ) +} \ No newline at end of file diff --git a/client/src/constants/widgetRegistry.jsx b/client/src/constants/widgetRegistry.jsx index 39b63f1..912c5f8 100644 --- a/client/src/constants/widgetRegistry.jsx +++ b/client/src/constants/widgetRegistry.jsx @@ -53,7 +53,7 @@ export const WIDGET_REGISTRY = { allowedSizes: { small: { w: 3, h: 6 }, medium: { w: 3, h: 9 }, - large: { w: 6, h: 9 }, + large: { w: 3, h: 12 }, } }, @@ -106,6 +106,18 @@ export const WIDGET_REGISTRY = { } }, + "seismic_events": { + name: "Seismic Events", + category: "Daily Volcanic Activity", + description: "Plot of number of seismic events over a weeks time.", + component: lazy(() => import('../components/Widgets/SeismicEvents.jsx')), + settingsComponent: lazy(() => import('../components/Widgets/settings/SeismicEventsSettings.jsx')), + defaultSettings: {}, + allowedSizes: { + medium: { w: 6, h: 12 } + } + }, + "llm_al_changes_quad_plot": { name: "Alert Level Changes Quad Plot", category: "Computer Science",