From f7e944301b21fa8708d1cfa6bcd9410ebf7c4561 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 1 Nov 2025 08:51:21 +0000 Subject: [PATCH 1/4] Refactor: Implement Zustand for state management and optimize charts This commit introduces Zustand for centralized state management, replacing local component state. It also includes optimizations to chart components, such as using `useMemo` for data processing and improving data handling logic. The `zustand` package is added to dependencies. Co-authored-by: car85 --- frontend/bats-dashboar-tp/package.json | 3 +- .../src/Components/Barchart/BarChart.tsx | 119 +++++++++++------- .../src/Components/BoxPlot/Boxplot.tsx | 48 +++++-- .../src/Components/LineChart/LineChart.tsx | 74 ++++++----- frontend/bats-dashboar-tp/src/pages/App.tsx | 103 ++++++--------- .../src/state/dashboardStore.ts | 53 ++++++++ 6 files changed, 243 insertions(+), 157 deletions(-) create mode 100644 frontend/bats-dashboar-tp/src/state/dashboardStore.ts diff --git a/frontend/bats-dashboar-tp/package.json b/frontend/bats-dashboar-tp/package.json index 2a242c01..68ca3d65 100644 --- a/frontend/bats-dashboar-tp/package.json +++ b/frontend/bats-dashboar-tp/package.json @@ -30,7 +30,8 @@ "simple-statistics": "^7.8.7", "uuid": "^11.0.3", "vite-plugin-electron": "^0.29.0", - "wait-on": "^8.0.2" + "wait-on": "^8.0.2", + "zustand": "^4.5.4" }, "devDependencies": { "@eslint/js": "^9.15.0", diff --git a/frontend/bats-dashboar-tp/src/Components/Barchart/BarChart.tsx b/frontend/bats-dashboar-tp/src/Components/Barchart/BarChart.tsx index 0a628cbe..55bbdbab 100644 --- a/frontend/bats-dashboar-tp/src/Components/Barchart/BarChart.tsx +++ b/frontend/bats-dashboar-tp/src/Components/Barchart/BarChart.tsx @@ -3,7 +3,7 @@ import { BarChartState, BarLayout } from '../../types/Types'; import useBarChartState from './useBarChartState'; import Plot from 'react-plotly.js'; -import { useEffect } from 'react'; +import { useEffect, useMemo } from 'react'; const BarChart = ({ data, onStateChange }: @@ -23,8 +23,8 @@ const BarChart = ({ data, onStateChange }: } - const headers = data[0]; - const rows = data.slice(1); + const headers = useMemo(() => (Array.isArray(data) && data.length > 0 ? data[0] : []), [data]); + const rows = useMemo(() => (Array.isArray(data) && data.length > 1 ? data.slice(1) : []), [data]); const { @@ -37,75 +37,102 @@ const BarChart = ({ data, onStateChange }: handleAdditionalColumnChange, } = useBarChartState(headers); - const barChartData = (): BarChartState[] => { + const barChartData = useMemo(() => { + if (!categoricalColumn || !numericColumn || !additionalColumn) { + return []; + } + + const categoricalIndex = headers.indexOf(categoricalColumn); + const numericIndex = headers.indexOf(numericColumn); + const additionalIndex = headers.indexOf(additionalColumn); - if (!categoricalColumn || !numericColumn || !additionalColumn) return []; + if (categoricalIndex === -1 || numericIndex === -1 || additionalIndex === -1) { + return []; + } const groupedData: { [key: string]: { values: number[]; tooltips: string[] } } = {}; rows.forEach((row) => { - const category = row[headers.indexOf(categoricalColumn)] as string; - const numericValue = Number(row[headers.indexOf(numericColumn)]); - const additionalValue = row[headers.indexOf(additionalColumn)]; + const category = String(row[categoricalIndex] ?? ''); + const numericValue = Number(row[numericIndex]); + const additionalValue = row[additionalIndex]; + + if (!Number.isFinite(numericValue)) { + return; + } if (!selectedCategories.length || selectedCategories.includes(category)) { if (!groupedData[category]) { groupedData[category] = { values: [], tooltips: [] }; } groupedData[category].values.push(numericValue); - groupedData[category].tooltips.push(`${category}, ${numericValue}, ${additionalColumn}: ${additionalValue}`); + groupedData[category].tooltips.push(`${category}, ${numericValue}, ${additionalColumn}: ${additionalValue ?? ''}`); } }); - const sortedGroupedData: BarChartState[] = Object.entries(groupedData) - .sort(([, a], [, b]) => { + return Object.entries(groupedData) + .sort(([, a], [, b]) => { const sumA = a.values.reduce((acc, val) => acc + val, 0); const sumB = b.values.reduce((acc, val) => acc + val, 0); return sumB - sumA; - }) - .map(([key, { values, tooltips }]) => ({ + }) + .map(([key, { values, tooltips }]) => ({ y: values, - x: Array.from({ length: values.length }, (_, i) => i + 1), + x: Array.from({ length: values.length }, (_, i) => i + 1), type: 'bar' as const, name: key, text: tooltips, hoverinfo: 'text', textposition: 'none', - })); - - return sortedGroupedData; - }; + })); + }, [additionalColumn, headers, numericColumn, rows, selectedCategories, categoricalColumn]); useEffect(() => { - if (onStateChange) { - const numericValues = data.map((d: any) => d[numericColumn]); - const minNumeric = Math.min(...numericValues); - const maxNumeric = Math.max(...numericValues); - const layout: BarLayout = { + if (!onStateChange || !categoricalColumn || !numericColumn || !additionalColumn) { + return; + } + + const numericIndex = headers.indexOf(numericColumn); + const categoricalIndex = headers.indexOf(categoricalColumn); + + if (numericIndex === -1 || categoricalIndex === -1) { + return; + } + + const numericValues = rows + .map((row) => Number(row[numericIndex])) + .filter((value) => Number.isFinite(value)); + + const minNumeric = numericValues.length ? Math.min(...numericValues) : 0; + const maxNumeric = numericValues.length ? Math.max(...numericValues) : 0; + + const layout: BarLayout = { + title: { + text: 'Bar Chart: Dynamic Analysis', + }, + yaxis: { title: { - text: 'Bar Chart: Dynamic Analysis', + text: numericColumn, }, - yaxis: { title: { - text: numericColumn }, type: 'linear', range: [minNumeric, maxNumeric], - autorange: true, }, - - xaxis: { - title: { - text: categoricalColumn }, - type: 'category', - range: [0, data.length - 1], - autorange: true, + autorange: !numericValues.length, + }, + xaxis: { + title: { + text: categoricalColumn, }, - }; - - onStateChange({ - data: barChartData(), - layout, - }); - } - }, [categoricalColumn, numericColumn, additionalColumn, selectedCategories]); + type: 'category', + range: [0, Math.max(rows.length - 1, 0)], + autorange: rows.length === 0, + }, + }; + + onStateChange({ + data: barChartData, + layout, + }); + }, [additionalColumn, barChartData, categoricalColumn, headers, numericColumn, onStateChange, rows.length]); return ( @@ -119,7 +146,7 @@ const BarChart = ({ data, onStateChange }: value={categoricalColumn || ''} onChange={handleCategoricalChange} > - + {headers.map((header) => ( + {headers.map((header) => ( + {headers.map((header) => (