diff --git a/app/src/components/SimulationDashboard/AnalyzerManager.tsx b/app/src/components/SimulationDashboard/AnalyzerManager.tsx
index ffcba26..c181cf4 100644
--- a/app/src/components/SimulationDashboard/AnalyzerManager.tsx
+++ b/app/src/components/SimulationDashboard/AnalyzerManager.tsx
@@ -1,4 +1,4 @@
-import { useEffect } from "react";
+import { useEffect, useRef } from "react";
import { useSceneContext } from "../../modules/scene/sceneContext";
const AnalyzerManager: React.FC = () => {
@@ -22,12 +22,37 @@ const AnalyzerManager: React.FC = () => {
return String(value ?? "-");
};
+ // Keep blocks ref updated to access latest blocks in analysis effect without triggering it
+ const blocksRef = useRef(blocks);
+ useEffect(() => {
+ blocksRef.current = blocks;
+ }, [blocks]);
+
+ // Clear default graph data on mount
+ useEffect(() => {
+ blocksRef.current.forEach((block) => {
+ block.elements.forEach((element) => {
+ if (element.type === "graph" && element.dataBinding?.dataType === "single-machine") {
+ // Check for default data (ElementContent uses Jan, Feb etc as fallback)
+ // If graphData is undefined, ElementContent shows defaults. We explicitly set to [] to show empty.
+ // If graphData has "Jan" data, we also clear it.
+ const hasDefaultData = element.graphData && element.graphData.length > 0 && element.graphData[0].name === "Jan";
+
+ if (!element.graphData || hasDefaultData) {
+ updateGraphData(block.blockUuid, element.elementUuid, []);
+ }
+ }
+ });
+ });
+ }, [updateGraphData]);
+
useEffect(() => {
if (!analysis) return;
+ // 1. Handle Label-Value (Direct DOM Manipulation)
+ // Safe to depend on blocks here as we don't update store/blocks
blocks.forEach((block) => {
block.elements.forEach((element) => {
- // 1. Handle Label-Value (Direct DOM Manipulation)
if (element.type === "label-value") {
if (!element.dataBinding?.dataSource || !element.dataBinding?.dataValue) return;
@@ -39,23 +64,13 @@ const AnalyzerManager: React.FC = () => {
if (typeof dataValueStr !== "string") return;
if (element.dataBinding.dataSource === "global") {
- // Handle global metrics
- // Expected format: "global.systemPerformance.overallOEE" -> we need last part relative to metrics
- // But dataValue usually comes as "systemPerformance.overallOEE" if dataSource is global?
- // Let's check ElementEditor:
- // It passes "global.systemPerformance.overallOEE" as the ID.
- // We need to parse this.
-
const metrics = getSystemMetrics();
if (metrics) {
// If the ID starts with global., strip it.
- const path = dataValueStr.startsWith("global.")
- ? dataValueStr.replace("global.", "")
- : dataValueStr;
+ const path = dataValueStr.startsWith("global.") ? dataValueStr.replace("global.", "") : dataValueStr;
value = resolvePath(metrics, path);
}
-
} else {
// Handle Asset Specific
const assetAnalysis = getAssetAnalysis(element.dataBinding.dataSource as string);
@@ -70,99 +85,72 @@ const AnalyzerManager: React.FC = () => {
valueElement.textContent = getFormattedValue(value);
}
}
+ });
+ });
+ }, [analysis, blocks, getAssetAnalysis, getSystemMetrics]);
- // 2. Handle Graph (Store Update)
- // We shouldn't update store on every frame if data hasn't changed or simpler approach?
- // For graphs, we usually push new data points.
- // Assuming we want to visualize historical data or current state trends.
- else if (element.type === "graph") {
+ // 2. Handle Graph (Store Update)
+ // This effect MUST NOT depend on 'blocks' to avoid infinite loop (updateGraphData modifies blocks)
+ useEffect(() => {
+ if (!analysis) return;
+ blocksRef.current.forEach((block) => {
+ block.elements.forEach((element) => {
+ if (element.type === "graph") {
if (element.dataBinding?.dataType === "single-machine") {
if (!element.dataBinding.dataSource || !element.dataBinding.dataValue) return;
- // Single machine, multiple values potentially?
- // element.dataValue is string[] for single-machine graph
const assetId = element.dataBinding.dataSource as string;
- const dataKeys = element.dataBinding.dataValue as string[]; // e.g. ["efficiency.overallEffectiveness"]
-
- // For now support getting the FIRST value as the main value to plot?
- // Or if we track history?
- // The requirement says: "update the dom ... to show the value".
- // UseAnalysisStore has 'historicalData'.
- // But usually graphs update by appending new points.
- // Here we might just pull the latest historical data or current value.
+ // Ensure dataValue is an array
+ const rawDataValue = element.dataBinding.dataValue;
+ const dataKeys = Array.isArray(rawDataValue) ? rawDataValue : [rawDataValue as string];
const assetAnalysis = getAssetAnalysis(assetId);
if (assetAnalysis) {
- // If it's a historical plot, we might want to read from assetAnalysis.historicalData (if it exists on client)
- // or just push current value to the graph element's local state.
- // But here we are asked to manage values using AnalyzerManager.
-
- // Let's construct a data point.
- // GraphDataPoint = { name: string, value: number, ... }
-
- // We use analysis.lastUpdateTime or similar as 'name' (X-axis)
const timeLabel = new Date().toLocaleTimeString();
-
- // We need to construct a new GraphDataPoint
- const newPoint: any = { name: timeLabel };
+ const newPoint: GraphDataPoint = { name: timeLabel, value: 0 };
let hasValidData = false;
- dataKeys.forEach((key, index) => {
+ dataKeys.forEach((key) => {
const val = resolvePath(assetAnalysis, key);
if (typeof val === "number") {
- newPoint["value"] = val; // Primary value
- // If we have multiple lines, we might need value1, value2 etc, but Recharts usually takes keys.
- // The ElementContent uses 'value' key hardcoded for single line?
- // Let's check ElementContent.tsx...
- // It renders
- // So it only supports ONE series called 'value' currently for default graphs?
- // Or we can map index to keys?
-
- // For now, let's assume single generic 'value' or update if strictly array.
-
+ newPoint["value"] = val;
hasValidData = true;
}
});
if (hasValidData) {
- // We need to update the element's graphData.
- // This is a store update, triggers re-render.
- // BE CAREFUL: varying this too fast will kill performance.
- // Maybe throttle this? useAnalysisStore updates often?
-
const currentGraphData = element.graphData || [];
- const newGraphData = [...currentGraphData, newPoint].slice(-20); // Keep last 20 points
+ const newGraphData = [...currentGraphData, newPoint].slice(-20);
+ // Always update for single-machine as we are appending time-series data
updateGraphData(block.blockUuid, element.elementUuid, newGraphData);
}
}
-
} else if (element.dataBinding?.dataType === "multiple-machine") {
- // Multiple machines, single common value
const assetIds = element.dataBinding.dataSource as string[];
const commonValueKey = element.dataBinding.commonValue as string;
if (assetIds && commonValueKey) {
- // Bar chart comparing machines?
- // Data point per machine.
- const newGraphData = assetIds.map(assetId => {
+ const newGraphData = assetIds.map((assetId) => {
const assetAnalysis = getAssetAnalysis(assetId);
const val = assetAnalysis ? resolvePath(assetAnalysis, commonValueKey) : 0;
return {
name: assetAnalysis?.assetName || assetId,
- value: typeof val === "number" ? val : 0
+ value: typeof val === "number" ? val : 0,
};
});
- updateGraphData(block.blockUuid, element.elementUuid, newGraphData);
+ // Deep check to avoid unnecessary updates
+ if (JSON.stringify(newGraphData) !== JSON.stringify(element.graphData)) {
+ updateGraphData(block.blockUuid, element.elementUuid, newGraphData);
+ }
}
}
}
});
});
-
- }, [analysis, blocks, getAssetAnalysis, getSystemMetrics, updateGraphData]);
+ }, [analysis, getAssetAnalysis, updateGraphData]);
return null;
};
diff --git a/app/src/components/SimulationDashboard/components/element/ElementContent.tsx b/app/src/components/SimulationDashboard/components/element/ElementContent.tsx
index e81208b..aac5e73 100644
--- a/app/src/components/SimulationDashboard/components/element/ElementContent.tsx
+++ b/app/src/components/SimulationDashboard/components/element/ElementContent.tsx
@@ -20,6 +20,7 @@ import {
Cell,
} from "recharts";
import { UIElement } from "../../../../types/exportedTypes";
+import { usePlayButtonStore } from "../../../../store/ui/usePlayButtonStore";
interface ElementContentProps {
element: UIElement;
@@ -29,16 +30,18 @@ interface ElementContentProps {
const COLORS = ["#6f42c1", "#c4abf1", "#a07ad8", "#d2baff", "#b992e2", "#c4abf1"];
const ElementContent: React.FC = ({ element, resolvedData }) => {
+ const { isPlaying } = usePlayButtonStore();
const chartData =
- resolvedData.graphData ||
- element.graphData || [
- { name: "Jan", value: 400 },
- { name: "Feb", value: 300 },
- { name: "Mar", value: 600 },
- { name: "Apr", value: 800 },
- { name: "May", value: 500 },
- { name: "Jun", value: 900 },
- ];
+ isPlaying || !resolvedData.graphData
+ ? resolvedData.graphData ?? []
+ : [
+ { name: "Jan", value: 400 },
+ { name: "Feb", value: 300 },
+ { name: "Mar", value: 600 },
+ { name: "Apr", value: 800 },
+ { name: "May", value: 500 },
+ { name: "Jun", value: 900 },
+ ];
const tooltipStyle = {
contentStyle: {
@@ -88,14 +91,7 @@ const ElementContent: React.FC = ({ element, resolvedData }
-
+
) : element.graphType === "pie" ? (
@@ -121,14 +117,7 @@ const ElementContent: React.FC = ({ element, resolvedData }
-
+
) : (
diff --git a/app/src/store/simulation/useSimulationDashBoardStore.ts b/app/src/store/simulation/useSimulationDashBoardStore.ts
index adc8abd..c10806b 100644
--- a/app/src/store/simulation/useSimulationDashBoardStore.ts
+++ b/app/src/store/simulation/useSimulationDashBoardStore.ts
@@ -1,7 +1,6 @@
import { MathUtils } from "three";
import { create } from "zustand";
import { immer } from "zustand/middleware/immer";
-import { defaultGraphData } from "../../components/SimulationDashboard/data/defaultGraphData";
import { Block, UIElement, ExtendedCSSProperties } from "../../types/exportedTypes";
interface SimulationDashboardStore {
@@ -291,7 +290,7 @@ export const createSimulationDashboardStore = () => {
borderRadius: "4px",
padding: "8px",
},
- graphData: defaultGraphData,
+ graphData: [],
size: { width: 400, height: 200 },
};
@@ -482,7 +481,7 @@ export const createSimulationDashboardStore = () => {
if (element && element.type === "graph") {
element.graphType = graphType;
element.graphTitle = `${graphType.charAt(0).toUpperCase() + graphType.slice(1)} Chart`;
- element.graphData = defaultGraphData;
+ element.graphData = [];
}
}
});
@@ -655,7 +654,7 @@ export const createSimulationDashboardStore = () => {
borderRadius: "4px",
padding: "8px",
},
- graphData: defaultGraphData,
+ graphData: [],
size: { width: 400, height: 200 },
};
@@ -841,7 +840,7 @@ export const createSimulationDashboardStore = () => {
if (element && element.type === "graph") {
element.graphType = graphType;
element.graphTitle = `${graphType.charAt(0).toUpperCase() + graphType.slice(1)} Chart`;
- element.graphData = defaultGraphData;
+ element.graphData = [];
}
}
return blocks;