Added staging for new widget.

This commit is contained in:
2026-07-13 14:28:33 -07:00
parent df8bcd376e
commit e0de9800c4
3 changed files with 201 additions and 0 deletions
@@ -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:
`<b>${volcanoName}</b><br>` +
'Date: %{x|%b %d %Y %H:%M}<br>' +
'Flight Level: %{y}<extra></extra>'
}));
}, [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<br>%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 (
<Flex w="100%" h="100%" bg="gray.800" borderRadius="md" align="center" justify="center">
<Spinner size="xl" color="blue.400" thickness="4px" />
</Flex>
);
if (error) return (
<Flex w="100%" h="100%" bg="gray.800" borderRadius="md" align="center" justify="center" p={4}>
<Text color="red.400">{error}</Text>
</Flex>
);
if (plotTraces.length === 0) return (
<Flex height="100%" width="100%" align="center" justify="center" bg="gray.800" borderRadius="md">
<Text color="gray.400">No flight level data available.</Text>
</Flex>
);
return (
<Box height="100%" width="100%" bg="gray.800" borderRadius="md" p={2} overflow="hidden">
<Plot
key={plotYAxisType}
data={plotTraces}
layout={layout}
useResizeHandler={true} // Essential for dashboard grid resizing!
style={{ width: '100%', height: '100%' }}
config={{ responsive: true, displayModeBar: true, displaylogo: false }}
/>
</Box>
);
}
@@ -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 (
<Box>
<Text fontWeight="bold" mb={3} fontSize="sm">Y-Axis Settings</Text>
<Switch.Root
checked={isSymlog}
onCheckedChange={handleToggle}
colorPalette="blue"
>
<Switch.HiddenInput />
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
<Switch.Label>Use Symlog Scale</Switch.Label>
</Switch.Root>
</Box>
);
}
+19
View File
@@ -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 }
}
}
};