Upgraded Math Operation Node

This commit is contained in:
Nicole Rappe 2025-05-30 01:02:45 -06:00
parent 84cd4b6a54
commit b6500b84da

View File

@ -1,179 +1,172 @@
////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Data/WebUI/src/nodes/General Purpose/Node_Math_Operations.jsx ////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Data/WebUI/src/nodes/General Purpose/Node_Math_Operations.jsx
/**
* ============================================
* Borealis - Math Operation Node (Multi-Input A/B)
* ============================================
*
* COMPONENT ROLE:
* Performs live math operations on *two grouped input sets* (A and B).
*
* FUNCTIONALITY:
* - Inputs connected to Handle A are summed
* - Inputs connected to Handle B are summed
* - Math operation is applied as: A &lt;operator&gt; B
* - Result pushed via BorealisValueBus[id]
*
* SUPPORTED OPERATORS:
* - Add, Subtract, Multiply, Divide, Average
*/
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { Handle, Position, useReactFlow, useStore } from "reactflow"; import { Handle, Position, useReactFlow, useStore } from "reactflow";
import { IconButton } from "@mui/material";
import SettingsIcon from "@mui/icons-material/Settings";
// Init shared memory bus if not already set
if (!window.BorealisValueBus) window.BorealisValueBus = {}; if (!window.BorealisValueBus) window.BorealisValueBus = {};
if (!window.BorealisUpdateRate) window.BorealisUpdateRate = 100; if (!window.BorealisUpdateRate) window.BorealisUpdateRate = 100;
const MathNode = ({ id, data }) => { const MathNode = ({ id, data }) => {
const { setNodes } = useReactFlow(); const { setNodes } = useReactFlow();
const edges = useStore(state => state.edges); const edges = useStore(state => state.edges);
const [renderResult, setRenderResult] = useState(data?.value || "0");
const resultRef = useRef(renderResult);
const [operator, setOperator] = useState(data?.operator || "Add"); useEffect(() => {
const [result, setResult] = useState("0"); let intervalId = null;
const resultRef = useRef(0); let currentRate = window.BorealisUpdateRate;
useEffect(() => { const runLogic = () => {
let intervalId = null; const operator = data?.operator || "Add";
let currentRate = window.BorealisUpdateRate; const inputsA = edges.filter(e => e.target === id && e.targetHandle === "a");
const inputsB = edges.filter(e => e.target === id && e.targetHandle === "b");
const runLogic = () => { const sum = (list) =>
const inputsA = edges.filter(e => e.target === id && e.targetHandle === "a"); list
const inputsB = edges.filter(e => e.target === id && e.targetHandle === "b"); .map(e => parseFloat(window.BorealisValueBus[e.source]) || 0)
.reduce((a, b) => a + b, 0);
const sum = (list) => const valA = sum(inputsA);
list.map(e => parseFloat(window.BorealisValueBus[e.source]) || 0).reduce((a, b) => a + b, 0); const valB = sum(inputsB);
const valA = sum(inputsA); let value = 0;
const valB = sum(inputsB); switch (operator) {
case "Add":
value = valA + valB;
break;
case "Subtract":
value = valA - valB;
break;
case "Multiply":
value = valA * valB;
break;
case "Divide":
value = valB !== 0 ? valA / valB : 0;
break;
case "Average":
const totalInputs = inputsA.length + inputsB.length;
const totalSum = valA + valB;
value = totalInputs > 0 ? totalSum / totalInputs : 0;
break;
}
let value = 0; resultRef.current = value;
switch (operator) { setRenderResult(value.toString());
case "Add": window.BorealisValueBus[id] = value.toString();
value = valA + valB;
break;
case "Subtract":
value = valA - valB;
break;
case "Multiply":
value = valA * valB;
break;
case "Divide":
value = valB !== 0 ? valA / valB : 0;
break;
case "Average":
const totalInputs = inputsA.length + inputsB.length;
const totalSum = valA + valB;
value = totalInputs > 0 ? totalSum / totalInputs : 0;
break;
}
resultRef.current = value; setNodes(nds =>
setResult(value.toString()); nds.map(n =>
window.BorealisValueBus[id] = value.toString(); n.id === id
? { ...n, data: { ...n.data, value: value.toString() } }
: n
)
);
};
setNodes(nds => intervalId = setInterval(runLogic, currentRate);
nds.map(n =>
n.id === id ? { ...n, data: { ...n.data, operator, value: value.toString() } } : n
)
);
};
// Watch for update rate changes
const monitor = setInterval(() => {
const newRate = window.BorealisUpdateRate;
if (newRate !== currentRate) {
clearInterval(intervalId);
currentRate = newRate;
intervalId = setInterval(runLogic, currentRate); intervalId = setInterval(runLogic, currentRate);
}
}, 250);
const monitor = setInterval(() => { return () => {
const newRate = window.BorealisUpdateRate; clearInterval(intervalId);
if (newRate !== currentRate) { clearInterval(monitor);
clearInterval(intervalId); };
currentRate = newRate; }, [id, edges, setNodes, data?.operator]);
intervalId = setInterval(runLogic, currentRate);
}
}, 250);
return () => { return (
clearInterval(intervalId); <div className="borealis-node">
clearInterval(monitor); <div style={{ position: "absolute", left: -16, top: 12, fontSize: "8px", color: "#ccc" }}>A</div>
}; <div style={{ position: "absolute", left: -16, top: 50, fontSize: "8px", color: "#ccc" }}>B</div>
}, [id, operator, edges, setNodes]); <Handle type="target" position={Position.Left} id="a" style={{ top: 12 }} className="borealis-handle" />
<Handle type="target" position={Position.Left} id="b" style={{ top: 50 }} className="borealis-handle" />
return ( <div className="borealis-node-header" style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<div className="borealis-node"> <span>{data?.label || "Math Operation"}</span>
<div style={{ position: "absolute", left: -16, top: 12, fontSize: "8px", color: "#ccc" }}>A</div> </div>
<div style={{ position: "absolute", left: -16, top: 50, fontSize: "8px", color: "#ccc" }}>B</div>
<Handle type="target" position={Position.Left} id="a" style={{ top: 12 }} className="borealis-handle" />
<Handle type="target" position={Position.Left} id="b" style={{ top: 50 }} className="borealis-handle" />
<div className="borealis-node-header"> <div className="borealis-node-content" style={{ fontSize: "9px", color: "#ccc", marginTop: 4 }}>
{data?.label || "Math Operation"} Result: {renderResult}
</div> </div>
<div className="borealis-node-content"> <Handle type="source" position={Position.Right} className="borealis-handle" />
<div style={{ marginBottom: "8px", fontSize: "9px", color: "#ccc" }}> </div>
Aggregates A and B inputs then performs operation. );
</div>
<label style={{ fontSize: "9px", display: "block", marginBottom: "4px" }}>
Operator:
</label>
<select
value={operator}
onChange={(e) => setOperator(e.target.value)}
style={dropdownStyle}
>
<option value="Add">Add</option>
<option value="Subtract">Subtract</option>
<option value="Multiply">Multiply</option>
<option value="Divide">Divide</option>
<option value="Average">Average</option>
</select>
<label style={{ fontSize: "9px", display: "block", marginBottom: "4px" }}>
Result:
</label>
<input
type="text"
value={result}
disabled
style={resultBoxStyle}
/>
</div>
<Handle type="source" position={Position.Right} className="borealis-handle" />
</div>
);
};
const dropdownStyle = {
fontSize: "9px",
padding: "4px",
background: "#1e1e1e",
color: "#ccc",
border: "1px solid #444",
borderRadius: "2px",
width: "100%",
marginBottom: "8px"
};
const resultBoxStyle = {
fontSize: "9px",
padding: "4px",
background: "#2a2a2a",
color: "#ccc",
border: "1px solid #444",
borderRadius: "2px",
width: "100%"
}; };
export default { export default {
type: "MathNode", type: "MathNode",
label: "Math Operation", label: "Math Operation",
description: ` description: `
Perform Math on Aggregated Inputs Live math node for computing on two grouped inputs.
- A and B groups are independently summed - Sums all A and B handle inputs separately
- Performs: Add, Subtract, Multiply, Divide, or Average - Performs selected math operation: Add, Subtract, Multiply, Divide, Average
- Result = A <op> B - Emits result as string via BorealisValueBus
- Emits result via BorealisValueBus every update tick - Updates at the global update rate
`.trim(),
content: "Perform Math Operations", **Common Uses:**
component: MathNode Live dashboard math, sensor fusion, calculation chains, dynamic thresholds
`.trim(),
content: "Perform Math Operations",
component: MathNode,
config: [
{
key: "operator",
label: "Operator",
type: "select",
options: [
"Add",
"Subtract",
"Multiply",
"Divide",
"Average"
]
}
],
usage_documentation: `
### Math Operation Node
Performs live math between two groups of inputs (**A** and **B**).
#### Usage
- Connect any number of nodes to the **A** and **B** input handles.
- The node **sums all values** from A and from B before applying the operator.
- Select the math operator in the sidebar config:
- **Add**: A + B
- **Subtract**: A - B
- **Multiply**: A * B
- **Divide**: A / B (0 if B=0)
- **Average**: (A + B) / total number of inputs
#### Output
- The computed result is pushed as a string to downstream nodes every update tick.
#### Input Handles
- **A** (Top Left)
- **B** (Middle Left)
#### Example
If three nodes outputting 5, 10, 15 are connected to A,
and one node outputs 2 is connected to B,
and operator is Multiply:
- **A** = 5 + 10 + 15 = 30
- **B** = 2
- **Result** = 30 * 2 = 60
`.trim()
}; };