Update Explosion plot widget with event counts and add theme support
This commit is contained in:
@@ -1,79 +0,0 @@
|
||||
import Widget from "./Widget.jsx";
|
||||
import { Fieldset, NativeSelect, For, Box, Field, Input, Button, Text, Flex } from "@chakra-ui/react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useState } from "react";
|
||||
import { Asterisk } from 'lucide-react';
|
||||
|
||||
export default function AlertLevel({id, isEditable, isDelete, DeleteWidget}) {
|
||||
const { register, handleSubmit, reset, formState: {errors} } = useForm();
|
||||
const [isSubmit, setIsSubmit] = useState(false);
|
||||
|
||||
const onSubmit = (data) => {
|
||||
setIsSubmit(true);
|
||||
reset();
|
||||
}
|
||||
|
||||
// Prop Forwarding
|
||||
const widgetProps = { id, isEditable, isDelete, DeleteWidget, isSubmit, setIsSubmit }
|
||||
|
||||
return (
|
||||
<Widget title="Alert Level" {...widgetProps} >
|
||||
{!isSubmit ? (
|
||||
// DISPLAYING INPUT FIElDS AND LABELS
|
||||
<Box
|
||||
as="form"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
width="100%"
|
||||
p={3}
|
||||
display="flex"
|
||||
flexDir="column"
|
||||
>
|
||||
<Fieldset.Root size="md" maxW="md">
|
||||
<Fieldset.Content>
|
||||
<Field.Root>
|
||||
<Field.Label fontWeight="semibold">Volcano</Field.Label>
|
||||
<Input name="volcanoname" />
|
||||
</Field.Root>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label fontWeight="semibold">Country</Field.Label>
|
||||
<NativeSelect.Root>
|
||||
<NativeSelect.Field name="country">
|
||||
<For each={["United Kingdom", "Canada", "United States"]}>
|
||||
{(item) => (
|
||||
<option key={item} value={item}>
|
||||
{item}
|
||||
</option>
|
||||
)}
|
||||
</For>
|
||||
</NativeSelect.Field>
|
||||
<NativeSelect.Indicator />
|
||||
</NativeSelect.Root>
|
||||
</Field.Root>
|
||||
</Fieldset.Content>
|
||||
|
||||
<Button type="submit" size="sm">Submit</Button>
|
||||
</Fieldset.Root>
|
||||
</Box>
|
||||
):(
|
||||
// DISPLAYING ALERT LEVEL DATA
|
||||
<Box p={1} mt={1} ml="auto" mr="auto" width="200px">
|
||||
<Box
|
||||
bg="bg"
|
||||
shadow="md"
|
||||
borderRadius="md"
|
||||
p={8}
|
||||
width="200px"
|
||||
height="10px"
|
||||
textAlign="center"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Text fontsize="2xl" fontWeight="bold" color="gray.700">Alert Level Data Coming Soon.</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Widget>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Box, Flex, Spinner, Text } from '@chakra-ui/react';
|
||||
import { useColorMode } from "@/components/ui/color-mode.jsx";
|
||||
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';
|
||||
const plotYAxisType = settings?.yAxisScale === 'log' ? 'log' : 'linear';
|
||||
|
||||
// 🎨 Let's check color mode so we can make people happy with light and dark
|
||||
const { colorMode } = useColorMode();
|
||||
const isDark = colorMode === 'dark';
|
||||
|
||||
// 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');
|
||||
@@ -23,16 +27,17 @@ export default function ExplosionEvents({ settings }) {
|
||||
'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);
|
||||
// Sort data descending by number of events 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.volc_name || 'Unknown';
|
||||
const name = curr.volcano_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);
|
||||
// Map the explosion-specific keys
|
||||
acc[name].x.push(new Date(curr.ts_epoch));
|
||||
acc[name].y.push(curr.number_events);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
@@ -47,11 +52,11 @@ export default function ExplosionEvents({ settings }) {
|
||||
line: { width: .5, color: 'rgba(255, 255, 255, 0.7)' },
|
||||
symbol: Symbolway[index % Symbolway.length],
|
||||
},
|
||||
// Replicating the clean Observable tooltip
|
||||
// Replicating the clean D3/Observable tooltip
|
||||
hovertemplate:
|
||||
`<b>${volcanoName}</b><br>` +
|
||||
'Date: %{x|%b %d %Y %H:%M}<br>' +
|
||||
'Flight Level: %{y}<extra></extra>'
|
||||
'Date (UTC): %{x|%b %d %Y %H:%M}<br>' +
|
||||
'Number of Explosions: %{y}<extra></extra>'
|
||||
}));
|
||||
}, [apiData]);
|
||||
|
||||
@@ -75,14 +80,14 @@ export default function ExplosionEvents({ settings }) {
|
||||
font: { color: 'white', size: 12 }
|
||||
},
|
||||
type: 'date',
|
||||
tickformat: '%b %d<br>%Y %H:%M', // Multiline ticks from Observable
|
||||
tickformat: '%b %d<br>%Y', // D3 requested %b %d \n %Y formatting
|
||||
gridcolor: '#4b5563',
|
||||
zerolinecolor: '#4b5563',
|
||||
automargin: true,
|
||||
},
|
||||
yaxis: {
|
||||
title: {
|
||||
text: 'Flight Level',
|
||||
text: 'Number of Explosions',
|
||||
font: { color: 'white', size: 12 }
|
||||
},
|
||||
type: plotYAxisType,
|
||||
@@ -126,12 +131,12 @@ export default function ExplosionEvents({ settings }) {
|
||||
|
||||
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>
|
||||
<Text color="gray.400">No data available for current filter.</Text>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
return (
|
||||
<Box height="100%" width="100%" bg="gray.800" borderRadius="md" p={2} overflow="hidden">
|
||||
<Box height="100%" width="100%" bg="transparent" borderRadius="md" p={2} overflow="hidden">
|
||||
<Plot
|
||||
key={plotYAxisType}
|
||||
data={plotTraces}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import { useSlotRecipe, chakra, Flex, CloseButton, Text, IconButton } from "@chakra-ui/react"
|
||||
import { Undo } from 'lucide-react';
|
||||
|
||||
export default function Widget(props) {
|
||||
// deconstructing props
|
||||
const { id, title, isEditable, isDelete, DeleteWidget, isSubmit, setIsSubmit, children } = props;
|
||||
|
||||
const recipe = useSlotRecipe({ key: "widget" });
|
||||
const styles = recipe();
|
||||
|
||||
return (
|
||||
<Flex flexDirection="column" w="100%" h="auto">
|
||||
{/* HEADER */}
|
||||
<Flex css={styles.header} cursor={isEditable ? "move":"default"} flexDirection="row" w="100%" h="auto" justifyContent="space-between" alignItems="center">
|
||||
<Text className="widget-handle" w="100%" display="flex" alignItems="center" height="32px" fontWeight="bold">{title}</Text>
|
||||
{isDelete ? (
|
||||
<CloseButton onClick={() => DeleteWidget(id)} size="xs" _hover={{bg: "tomato", color: "white"}}/>
|
||||
):(
|
||||
isSubmit ? (
|
||||
<IconButton onClick={() => setIsSubmit(false)} size="xs" bg="base300" cursor="pointer"><Undo fill="text" /></IconButton>
|
||||
):null
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
{/* CHILDREN */}
|
||||
<Flex w="100%" h="auto">
|
||||
{children}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@ 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 isSymlog = currentSettings?.yAxisScale === 'log';
|
||||
|
||||
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';
|
||||
const newScale = details.checked ? 'log' : 'normal';
|
||||
|
||||
updateSettings(widgetId, { yAxisScale: newScale });
|
||||
};
|
||||
@@ -30,7 +30,7 @@ export default function ExplosionEventsSettings({ widgetId, currentSettings }) {
|
||||
<Switch.Control>
|
||||
<Switch.Thumb />
|
||||
</Switch.Control>
|
||||
<Switch.Label>Use Symlog Scale</Switch.Label>
|
||||
<Switch.Label>Use Log Scale</Switch.Label>
|
||||
</Switch.Root>
|
||||
</Box>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user