145 lines
5.2 KiB
React
145 lines
5.2 KiB
React
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>
|
||
|
|
);
|
||
|
|
}
|