diff --git a/client/src/components/Widgets/ExplosionEvents.jsx b/client/src/components/Widgets/ExplosionEvents.jsx new file mode 100644 index 0000000..e5b14af --- /dev/null +++ b/client/src/components/Widgets/ExplosionEvents.jsx @@ -0,0 +1,145 @@ +import { useMemo } from 'react'; +import { Box, Flex, Spinner, Text } from '@chakra-ui/react'; +import Plot from 'react-plotly.js'; +import VdapApi from '../../hooks/useVdapApi.js'; // Adjust path as needed + +export default function ExplosionEvents({ settings }) { + console.log("3. Chart received settings:", settings); + // Plotly expects 'linear' for normal axes + const plotYAxisType = settings?.yAxisScale === 'symlog' ? 'symlog' : 'linear'; + + // Clean, single-line data fetching + const { data: apiData, isLoading, error } = VdapApi('/api/v2/explosion_events?start_date=2025-05-01&end_date=2025-05-30'); + + // 1. Build the Traces (Data parsing) + 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 flight level (matching Observable behavior) + const sortedData = [...apiData].sort((a, b) => b.obs_fl - a.obs_fl); + + // Efficiently group into Plotly traces by volcano + const groupedData = sortedData.reduce((acc, curr) => { + const name = curr.volc_name || 'Unknown'; + if (!acc[name]) acc[name] = { x: [], y: [] }; + + acc[name].x.push(new Date(curr.obs_va_epoch)); + acc[name].y.push(curr.obs_fl); + 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 Observable tooltip + hovertemplate: + `${volcanoName}
` + + 'Date: %{x|%b %d %Y %H:%M}
' + + 'Flight Level: %{y}' + })); + }, [apiData]); + + // 2. Build the Layout (Styling and Grid) + 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: '7 Day Consolidated Flight Levels', + // font: { size: 16, color: 'white' }, + // x: 0.05 + // }, + xaxis: { + title: { + text: 'Date (UTC)', + font: { color: 'white', size: 12 } + }, + type: 'date', + tickformat: '%b %d
%Y %H:%M', // Multiline ticks from Observable + gridcolor: '#4b5563', + zerolinecolor: '#4b5563', + automargin: true, + }, + yaxis: { + title: { + text: 'Flight Level', + font: { color: 'white', size: 12 } + }, + type: plotYAxisType, + 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 } + } + ], + }; + }, [apiData, plotYAxisType]); + + if (isLoading) return ( + + + + ); + + if (error) return ( + + {error} + + ); + + if (plotTraces.length === 0) return ( + + No flight level data available. + + ); + + return ( + + + + ); +} \ No newline at end of file diff --git a/client/src/components/Widgets/settings/ExplosionEventsSettings.jsx b/client/src/components/Widgets/settings/ExplosionEventsSettings.jsx new file mode 100644 index 0000000..5d6509d --- /dev/null +++ b/client/src/components/Widgets/settings/ExplosionEventsSettings.jsx @@ -0,0 +1,37 @@ +import { Box, Text, Switch } from "@chakra-ui/react"; +import { useDashboardStore } from "@/store/useDashboardStore.jsx"; + +export default function ExplosionEventsSettings({ widgetId, currentSettings }) { + const updateSettings = useDashboardStore(state => state.updateWidgetSettings); + + // 1. Convert the saved string into a boolean for the toggle UI + const isSymlog = currentSettings?.yAxisScale === 'symlog'; + + const handleToggle = (details) => { + // 2. Chakra v3 passes { checked: boolean } in the details object. + // Convert it back to the string Plotly expects. + console.log("1. Toggle Clicked! Raw details from Chakra:", details); + + const newScale = details.checked ? 'symlog' : 'normal'; + + updateSettings(widgetId, { yAxisScale: newScale }); + }; + + return ( + + Y-Axis Settings + + + + + + + Use Symlog Scale + + + ); +} \ No newline at end of file diff --git a/client/src/constants/widgetRegistry.jsx b/client/src/constants/widgetRegistry.jsx index d0193b3..7da17e0 100644 --- a/client/src/constants/widgetRegistry.jsx +++ b/client/src/constants/widgetRegistry.jsx @@ -64,5 +64,24 @@ export const WIDGET_REGISTRY = { small: { w: 3, h: 12 }, large: { w: 9, h: 12 } } + }, + + "explosion_events": { + name: "Explosion Events", + category: "Daily Volcanic Activity", + description: "List of explosion events over a specified time period.", + + component: lazy(() => import('../components/Widgets/ExplosionEvents.jsx')), + + // The specific settings UI for this widget + settingsComponent: lazy(() => import('../components/widgets/settings/ExplosionEventsSettings.jsx')), + + // The baseline data every new VAA widget starts with + defaultSettings: { + yAxisScale: 'normal' // Options: 'normal' | 'symlog' + }, + allowedSizes: { + medium: { w: 6, h: 12 } + } } }; \ No newline at end of file