From 8c2cc0126bf131ba3fdb5a34687687d1a95b6e50 Mon Sep 17 00:00:00 2001 From: Anton Wolter Date: Sun, 24 Nov 2024 21:06:23 +0100 Subject: [PATCH] implement the cluster in time transitions --- src/components/AggregatedClusterView.tsx | 88 +++++++++++++++++++----- src/lib/clustering.worker.ts | 2 + src/lib/clusteringOverTime.ts | 5 +- src/main.tsx | 6 -- src/routes/aggregated-clusters.tsx | 5 -- src/routes/multi-aggregated-line.tsx | 3 +- src/routes/root.tsx | 1 - src/store/useViewModelStore.ts | 46 +++++++++++-- src/store/useViewSettingsStore.ts | 4 +- 9 files changed, 118 insertions(+), 42 deletions(-) delete mode 100644 src/routes/aggregated-clusters.tsx diff --git a/src/components/AggregatedClusterView.tsx b/src/components/AggregatedClusterView.tsx index c403606..792545e 100644 --- a/src/components/AggregatedClusterView.tsx +++ b/src/components/AggregatedClusterView.tsx @@ -1,33 +1,45 @@ -import { ClusterView } from "@/lib/clusteringOverTime"; import { cn } from "@/lib/utils"; +import { useRawDataStore } from "@/store/useRawDataStore"; import { useViewModelStore } from "@/store/useViewModelStore"; +import { useViewSettingsStore } from "@/store/useViewSettingsStore"; +import { + utcDay, + utcFormat, + utcHour, + utcMinute, + utcMonth, + utcSecond, + utcWeek, + utcYear, +} from "d3"; +import { useEffect } from "react"; import { clusterColors } from "./clusterColors"; export const AggregatedClusterView = () => { - // const values = useRawDataStore((state) => state.values); - // // const dimensions = useRawDataStore((state) => state.dimensions); + const values = useRawDataStore((state) => state.values); - // const presentationSettings = useViewSettingsStore(); + const presentationSettings = useViewSettingsStore(); - // const { colsAccordingToAggregation, processData } = useViewModelStore(); + const { clustersInTime, processClustersInTimeData } = useViewModelStore(); - // useEffect(() => { - // processData(); - // }, [presentationSettings, values]); - - const { colsAccordingToAggregation } = useViewModelStore(); - - const clustersInTime: ClusterView[] = [ - { timestamp: "asdf", clusters: colsAccordingToAggregation }, - { timestamp: "asdf", clusters: colsAccordingToAggregation }, - { timestamp: "asdf", clusters: colsAccordingToAggregation }, + useEffect(() => { + processClustersInTimeData(); + }, [presentationSettings, values]); + const interestingTimestampIndizes = [ + 0, + Math.floor(clustersInTime.length / 4), + Math.floor(clustersInTime.length / 2), + Math.floor((clustersInTime.length * 3) / 4), + clustersInTime.length - 1, ]; + return ( -
+
{clustersInTime.map(({ timestamp, clusters }, groupIndex) => (
0 ? "border-l-[0.5px] border-white" : "rounded-l-sm" )} > @@ -41,9 +53,49 @@ export const AggregatedClusterView = () => { )} >
))} -
{timestamp}
+ + {!!interestingTimestampIndizes.includes(groupIndex) && ( +
+ {multiFormat(new Date(Number(timestamp)))} +
+ )}
))}
); }; + +const formatMillisecond = utcFormat(".%L"), + formatSecond = utcFormat(":%S"), + formatMinute = utcFormat("%I:%M"), + formatHour = utcFormat("%I %p"), + formatDay = utcFormat("%a %d"), + formatWeek = utcFormat("%b %d"), + formatMonth = utcFormat("%B"), + formatYear = utcFormat("%Y"); + +function multiFormat(date: Date) { + return ( + utcSecond(date) < date + ? formatMillisecond + : utcMinute(date) < date + ? formatSecond + : utcHour(date) < date + ? formatMinute + : utcDay(date) < date + ? formatHour + : utcMonth(date) < date + ? utcWeek(date) < date + ? formatDay + : formatWeek + : utcYear(date) < date + ? formatMonth + : formatYear + )(date); +} diff --git a/src/lib/clustering.worker.ts b/src/lib/clustering.worker.ts index 2f1c27b..a1f82be 100644 --- a/src/lib/clustering.worker.ts +++ b/src/lib/clustering.worker.ts @@ -1,9 +1,11 @@ import * as Comlink from "comlink"; import { aggregator } from "./clustering"; +import { clusteringOverTime } from "./clusteringOverTime"; // Define the functions that will be available in the worker const workerFunctions = { aggregator: aggregator, + clusteringOverTime: clusteringOverTime, }; export type ClusteringWorker = typeof workerFunctions; diff --git a/src/lib/clusteringOverTime.ts b/src/lib/clusteringOverTime.ts index 596bffa..ff342be 100644 --- a/src/lib/clusteringOverTime.ts +++ b/src/lib/clusteringOverTime.ts @@ -29,7 +29,9 @@ export const clusteringOverTime = ( // ---------------- // Clustering context window: The amount of data entries that should be taken into consideration for handling the clusters later on. // ---------------- - const clusteringContextWindow = settings.dataTicks ?? 20; // 20 just as a dump fallback value + const clusteringContextWindow = !!settings.dataTicks + ? settings.dataTicks + : 20; // 20 just as a dump fallback value for ( let dataEntryIndex = 0; @@ -52,6 +54,7 @@ export const clusteringOverTime = ( : ""; const aggregated = clusteringData(dataToBeClustered, dimensions, settings); + const clusters: [string, number][] = dimensions.map((val) => [ val, aggregated.findIndex((entries) => Object.keys(entries[0]).includes(val)), diff --git a/src/main.tsx b/src/main.tsx index 71d5301..87de693 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -3,7 +3,6 @@ import ReactDOM from "react-dom/client"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; import ErrorPage from "./error-page"; import "./index.css"; -import AggregatedClusters from "./routes/aggregated-clusters"; import AggrLine from "./routes/aggregated-line"; import Anu from "./routes/anu"; import Home from "./routes/home"; @@ -35,11 +34,6 @@ const router = createBrowserRouter([ element: , errorElement: , }, - { - path: "aggregated-clusters", - element: , - errorElement: , - }, ], }, ]); diff --git a/src/routes/aggregated-clusters.tsx b/src/routes/aggregated-clusters.tsx deleted file mode 100644 index 80a800c..0000000 --- a/src/routes/aggregated-clusters.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { AggregatedClusterView } from "@/components/AggregatedClusterView"; - -export default function AggregatedClusters() { - return ; -} diff --git a/src/routes/multi-aggregated-line.tsx b/src/routes/multi-aggregated-line.tsx index 7918d9a..5d52803 100644 --- a/src/routes/multi-aggregated-line.tsx +++ b/src/routes/multi-aggregated-line.tsx @@ -6,8 +6,7 @@ import { useViewSettingsStore } from "@/store/useViewSettingsStore"; export default function MutliAggregatedLine() { const mode = useViewSettingsStore((state) => state.mode); - - const { aggregated } = useViewModelStore(); + const aggregated = useViewModelStore((data) => data.aggregated); return (
diff --git a/src/routes/root.tsx b/src/routes/root.tsx index 01a0a64..5ff3669 100644 --- a/src/routes/root.tsx +++ b/src/routes/root.tsx @@ -8,7 +8,6 @@ export default function Root() { { title: "Multi Line", href: "multi-line" }, { title: "Aggregation", href: "aggregated-line" }, { title: "Multi Aggregation", href: "multi-aggregated-line" }, - { title: "Cluster Aggregation", href: "aggregated-clusters" }, ]; return ( diff --git a/src/store/useViewModelStore.ts b/src/store/useViewModelStore.ts index 7a2f239..016b102 100644 --- a/src/store/useViewModelStore.ts +++ b/src/store/useViewModelStore.ts @@ -5,6 +5,7 @@ import { create } from "zustand"; import { useRawDataStore } from "./useRawDataStore"; import { useViewSettingsStore } from "./useViewSettingsStore"; +import { ClusterView } from "@/lib/clusteringOverTime"; import Worker from "../lib/clustering.worker?worker"; interface DataStore { @@ -12,7 +13,13 @@ interface DataStore { yDomain: [number, number]; colsAccordingToAggregation: [string, number][]; + /** + * This data is needed only for certain views. It should only be calculated when needed. + */ + clustersInTime: ClusterView[]; + processData: () => void; + processClustersInTimeData: () => void; } const workerInstance = new Worker({ name: "aggregator" }); @@ -22,10 +29,9 @@ export const useViewModelStore = create((set) => { console.log("init view model store"); const throttledDataProcess = _.throttle(async () => { - console.count("Throttled process data"); const timerName = Date.now(); - console.time("ViewModel process duration " + String(timerName)); + console.time("ViewModel basic data process duration " + String(timerName)); const dimensions = useRawDataStore.getState().dimensions; const values = useRawDataStore.getState().values; const { updateSettings, ...presentationSettings } = @@ -37,19 +43,45 @@ export const useViewModelStore = create((set) => { dimensions, presentationSettings ); - console.timeEnd("ViewModel process duration " + String(timerName)); + console.timeEnd( + "ViewModel basic data process duration " + String(timerName) + ); set(aggregated); }, 2000); + const throttledClustersInTimeProcess = _.throttle(async () => { + const timerName = Date.now(); + + console.time( + "ViewModel cluster in time process duration " + String(timerName) + ); + const dimensions = useRawDataStore.getState().dimensions; + const values = useRawDataStore.getState().values; + const { updateSettings, ...presentationSettings } = + useViewSettingsStore.getState(); + console.log("Settings: ", presentationSettings); + + const { clustersInTime } = await workerApi.clusteringOverTime( + values, + dimensions, + presentationSettings + ); + + console.timeEnd( + "ViewModel cluster in time process duration " + String(timerName) + ); + + set({ clustersInTime }); + }, 10000); + return { aggregated: [], yDomain: [0, 10], colsAccordingToAggregation: [], + clustersInTime: [], - processData: async () => { - console.count("Process data with worker"); - throttledDataProcess(); - }, + processData: throttledDataProcess, + processClustersInTimeData: throttledClustersInTimeProcess, }; }); diff --git a/src/store/useViewSettingsStore.ts b/src/store/useViewSettingsStore.ts index facf567..1efc9a0 100644 --- a/src/store/useViewSettingsStore.ts +++ b/src/store/useViewSettingsStore.ts @@ -1,6 +1,6 @@ +import { ChartPresentationSettings } from "@/lib/ChartPresentationSettings"; import { create } from "zustand"; import { useRawDataStore } from "./useRawDataStore"; -import { ChartPresentationSettings } from "@/lib/ChartPresentationSettings"; export type ViewSettingsStore = ChartPresentationSettings & { updateSettings: ( @@ -9,7 +9,7 @@ export type ViewSettingsStore = ChartPresentationSettings & { }; export const useViewSettingsStore = create((set, get) => { - const dataTicks = useRawDataStore.getState().values.length ?? 20; + const dataTicks = Math.min(useRawDataStore.getState().values.length ?? 50); return { eps: 8, dataTicks,