feat: Add voltage drop calculation and update button in ObjectFlowDesigner
This commit is contained in:
@@ -11,6 +11,9 @@
|
|||||||
<header class="header">
|
<header class="header">
|
||||||
<h1>Power System Designer</h1>
|
<h1>Power System Designer</h1>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
|
<button id="calculateBtn" class="btn btn-primary">
|
||||||
|
Calculate Voltage Drops
|
||||||
|
</button>
|
||||||
<button id="clearBtn" class="btn btn-danger">Clear All</button>
|
<button id="clearBtn" class="btn btn-danger">Clear All</button>
|
||||||
<button id="exportBtn" class="btn btn-secondary">Export Data</button>
|
<button id="exportBtn" class="btn btn-secondary">Export Data</button>
|
||||||
<button id="importBtn" class="btn btn-secondary">Import Data</button>
|
<button id="importBtn" class="btn btn-secondary">Import Data</button>
|
||||||
|
|||||||
216
script.js
216
script.js
@@ -71,7 +71,13 @@ class ObjectFlowDesigner {
|
|||||||
.getElementById("fileInput")
|
.getElementById("fileInput")
|
||||||
.addEventListener("change", this.importData.bind(this));
|
.addEventListener("change", this.importData.bind(this));
|
||||||
|
|
||||||
// Window resize
|
// Calculate button (if it exists)
|
||||||
|
const calculateBtn = document.getElementById("calculateBtn");
|
||||||
|
if (calculateBtn) {
|
||||||
|
calculateBtn.addEventListener("click", () => {
|
||||||
|
this.updateCalculations();
|
||||||
|
});
|
||||||
|
}
|
||||||
window.addEventListener("resize", this.resizeCanvas.bind(this));
|
window.addEventListener("resize", this.resizeCanvas.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -824,6 +830,35 @@ class ObjectFlowDesigner {
|
|||||||
yOffset += 13;
|
yOffset += 13;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5. Voltage information (if calculated)
|
||||||
|
const hasVoltageData =
|
||||||
|
obj.data.voltage !== null && obj.data.voltage !== undefined;
|
||||||
|
if (hasVoltageData) {
|
||||||
|
// Show voltage level
|
||||||
|
const voltage = Math.round(obj.data.voltage * 10) / 10; // Round to 1 decimal
|
||||||
|
this.ctx.fillText(`${voltage}V`, obj.x + obj.width / 2, yOffset);
|
||||||
|
yOffset += 13;
|
||||||
|
|
||||||
|
// Show voltage drop percentage with color coding
|
||||||
|
const dropPercentage = this.getVoltageDropPercentage(obj);
|
||||||
|
const dropText = `${Math.round(dropPercentage * 10) / 10}%`;
|
||||||
|
|
||||||
|
// Color code based on voltage drop severity
|
||||||
|
if (dropPercentage > 10) {
|
||||||
|
this.ctx.fillStyle = "#e53e3e"; // Red for excessive drop (>10%)
|
||||||
|
} else if (dropPercentage > 7) {
|
||||||
|
this.ctx.fillStyle = "#fbb040"; // Orange for warning (7-10%)
|
||||||
|
} else {
|
||||||
|
this.ctx.fillStyle = "#38a169"; // Green for acceptable (<7%)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ctx.fillText(`-${dropText}`, obj.x + obj.width / 2, yOffset);
|
||||||
|
yOffset += 13;
|
||||||
|
|
||||||
|
// Reset color for next text
|
||||||
|
this.ctx.fillStyle = isSelected ? "#fbb040" : "#2d3748";
|
||||||
|
}
|
||||||
|
|
||||||
// Type description (moved down to accommodate other info)
|
// Type description (moved down to accommodate other info)
|
||||||
if (obj.data.boxPoleType) {
|
if (obj.data.boxPoleType) {
|
||||||
this.ctx.fillText(
|
this.ctx.fillText(
|
||||||
@@ -920,6 +955,22 @@ class ObjectFlowDesigner {
|
|||||||
textLines.push(`${cableTypeDisplay} ${connection.data.crossSection}mm²`);
|
textLines.push(`${cableTypeDisplay} ${connection.data.crossSection}mm²`);
|
||||||
textLines.push(`${connection.data.length}m`);
|
textLines.push(`${connection.data.length}m`);
|
||||||
|
|
||||||
|
// Add electrical calculations if available
|
||||||
|
if (
|
||||||
|
connection.data.sectionCurrent !== null &&
|
||||||
|
connection.data.voltageDrop !== null
|
||||||
|
) {
|
||||||
|
textLines.push(
|
||||||
|
`${Math.round(connection.data.sectionCurrent * 10) / 10}A/φ`
|
||||||
|
);
|
||||||
|
textLines.push(
|
||||||
|
`ΔU: ${Math.round(connection.data.voltageDrop * 1000) / 1000}V`
|
||||||
|
);
|
||||||
|
textLines.push(
|
||||||
|
`R: ${Math.round(connection.data.sectionResistance * 1000) / 1000}Ω`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (textLines.length > 0) {
|
if (textLines.length > 0) {
|
||||||
this.ctx.font = "11px Arial";
|
this.ctx.font = "11px Arial";
|
||||||
this.ctx.textAlign = "center";
|
this.ctx.textAlign = "center";
|
||||||
@@ -1249,6 +1300,11 @@ class ObjectFlowDesigner {
|
|||||||
const isOverheadCable =
|
const isOverheadCable =
|
||||||
conn.data.cableType === "AL" || conn.data.cableType === "AsXSn";
|
conn.data.cableType === "AL" || conn.data.cableType === "AsXSn";
|
||||||
|
|
||||||
|
// Calculate electrical properties for display
|
||||||
|
const hasElectricalData =
|
||||||
|
conn.data.sectionCurrent !== null &&
|
||||||
|
conn.data.sectionCurrent !== undefined;
|
||||||
|
|
||||||
panel.innerHTML = `
|
panel.innerHTML = `
|
||||||
<div class="property-group">
|
<div class="property-group">
|
||||||
<h4>Power Cable Properties</h4>
|
<h4>Power Cable Properties</h4>
|
||||||
@@ -1287,19 +1343,38 @@ class ObjectFlowDesigner {
|
|||||||
<p><strong>Type:</strong> ${conn.data.cableType} ${
|
<p><strong>Type:</strong> ${conn.data.cableType} ${
|
||||||
isOverheadCable ? "(Overhead)" : "(Ground)"
|
isOverheadCable ? "(Overhead)" : "(Ground)"
|
||||||
}</p>
|
}</p>
|
||||||
<p><strong>From:</strong> ${conn.from.data.name} (${
|
<p><strong>From:</strong> ${conn.from.data.number || conn.from.data.name} (${
|
||||||
conn.from.type === "triangle"
|
conn.from.type === "triangle"
|
||||||
? "Transformer"
|
? "Transformer"
|
||||||
: this.getNodeDisplayType(conn.from.data.nodeType)
|
: this.getNodeDisplayType(conn.from.data.nodeType)
|
||||||
})</p>
|
})</p>
|
||||||
<p><strong>To:</strong> ${conn.to.data.name} (${
|
<p><strong>To:</strong> ${conn.to.data.number || conn.to.data.name} (${
|
||||||
conn.to.type === "triangle"
|
conn.to.type === "triangle"
|
||||||
? "Transformer"
|
? "Transformer"
|
||||||
: this.getNodeDisplayType(conn.to.data.nodeType)
|
: this.getNodeDisplayType(conn.to.data.nodeType)
|
||||||
})</p>
|
})</p>
|
||||||
<p><strong>Capacity:</strong> ${conn.data.crossSection} mm² × ${
|
<p><strong>Specifications:</strong> ${conn.data.crossSection}mm² × ${
|
||||||
conn.data.length
|
conn.data.length
|
||||||
}m</p>
|
}m</p>
|
||||||
|
${
|
||||||
|
hasElectricalData
|
||||||
|
? `
|
||||||
|
<p><strong>Section Resistance:</strong> ${
|
||||||
|
Math.round(conn.data.sectionResistance * 1000) / 1000
|
||||||
|
} Ω</p>
|
||||||
|
<p><strong>Section Current:</strong> ${
|
||||||
|
Math.round(conn.data.sectionCurrent * 10) / 10
|
||||||
|
} A (per phase)</p>
|
||||||
|
<p><strong>Voltage Drop:</strong> ${
|
||||||
|
Math.round(conn.data.voltageDrop * 1000) / 1000
|
||||||
|
} V</p>
|
||||||
|
<p><strong>Total Consumers:</strong> ${this.getTotalConsumers(conn.to)}</p>
|
||||||
|
<p><strong>Diversity Factor:</strong> ${(
|
||||||
|
this.getDiversityFactor(this.getTotalConsumers(conn.to)) * 100
|
||||||
|
).toFixed(0)}%</p>
|
||||||
|
`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
${
|
${
|
||||||
isOverheadCable
|
isOverheadCable
|
||||||
? '<p class="warning"><strong>⚠️ Overhead cables can only connect to poles or end connections!</strong></p>'
|
? '<p class="warning"><strong>⚠️ Overhead cables can only connect to poles or end connections!</strong></p>'
|
||||||
@@ -1309,6 +1384,7 @@ class ObjectFlowDesigner {
|
|||||||
|
|
||||||
<div class="property-group">
|
<div class="property-group">
|
||||||
<h4>Actions</h4>
|
<h4>Actions</h4>
|
||||||
|
<button class="btn btn-primary" onclick="designer.updateCalculations()">Calculate Voltage Drops</button>
|
||||||
<button class="btn btn-danger" onclick="designer.deleteConnection(${
|
<button class="btn btn-danger" onclick="designer.deleteConnection(${
|
||||||
conn.id
|
conn.id
|
||||||
})">Delete Cable</button>
|
})">Delete Cable</button>
|
||||||
@@ -1684,6 +1760,138 @@ class ObjectFlowDesigner {
|
|||||||
|
|
||||||
return power3Phase + power1Phase + customPower;
|
return power3Phase + power1Phase + customPower;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate voltage drop for the entire system
|
||||||
|
calculateVoltageDrops() {
|
||||||
|
// Reset all voltage calculations
|
||||||
|
this.objects.forEach((obj) => {
|
||||||
|
if (obj.data) {
|
||||||
|
obj.data.voltage = null;
|
||||||
|
obj.data.voltageDrop = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.connections.forEach((conn) => {
|
||||||
|
if (conn.data) {
|
||||||
|
conn.data.sectionResistance = null;
|
||||||
|
conn.data.sectionCurrent = null;
|
||||||
|
conn.data.voltageDrop = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find transformer and set its voltage to 230V base
|
||||||
|
// Note: All calculations use 230V as reference voltage (Polish LV standard)
|
||||||
|
// regardless of transformer's rated bottom voltage
|
||||||
|
const transformer = this.objects.find((obj) => obj.type === "triangle");
|
||||||
|
if (!transformer) return;
|
||||||
|
|
||||||
|
transformer.data.voltage = 230; // Start calculations from 230V base
|
||||||
|
transformer.data.voltageDrop = 0; // No drop at transformer level for 230V base
|
||||||
|
|
||||||
|
// Calculate voltage drops working from transformer outward
|
||||||
|
this.calculateVoltageDropRecursive(transformer);
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateVoltageDropRecursive(node) {
|
||||||
|
// Process all outgoing connections from this node
|
||||||
|
for (const connection of node.connections.outputs) {
|
||||||
|
const toNode = connection.to;
|
||||||
|
|
||||||
|
// Calculate current flowing through this section
|
||||||
|
const sectionCurrent = this.calculateSectionCurrent(toNode);
|
||||||
|
|
||||||
|
// Calculate section resistance = length / (crossSection * 35)
|
||||||
|
const sectionResistance =
|
||||||
|
connection.data.length / (connection.data.crossSection * 35);
|
||||||
|
|
||||||
|
// Calculate voltage drop = resistance * current
|
||||||
|
const voltageDrop = sectionResistance * sectionCurrent;
|
||||||
|
|
||||||
|
// Store calculation results in connection data
|
||||||
|
connection.data.sectionResistance = sectionResistance;
|
||||||
|
connection.data.sectionCurrent = sectionCurrent;
|
||||||
|
connection.data.voltageDrop = voltageDrop;
|
||||||
|
// Calculate voltage at destination node
|
||||||
|
const sourceVoltage = node.data.voltage || 230; // Always use 230V as base
|
||||||
|
toNode.data.voltage = sourceVoltage - voltageDrop;
|
||||||
|
toNode.data.voltageDrop = 230 - toNode.data.voltage; // Total drop from base 230V
|
||||||
|
|
||||||
|
// Continue recursively to downstream nodes
|
||||||
|
this.calculateVoltageDropRecursive(toNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateSectionCurrent(node) {
|
||||||
|
// Calculate total current flowing to this node and all downstream nodes
|
||||||
|
let totalPower = this.calculateDownstreamPower(node);
|
||||||
|
|
||||||
|
// Apply diversity factor based on total number of consumers
|
||||||
|
const totalConsumers = this.getTotalConsumers(node);
|
||||||
|
const diversityFactor = this.getDiversityFactor(totalConsumers);
|
||||||
|
const adjustedPower = totalPower * diversityFactor;
|
||||||
|
|
||||||
|
// Calculate current per phase using single-phase approach: I = P / (3 * 230V)
|
||||||
|
// This gives current per phase in a 3-phase system
|
||||||
|
const current = (adjustedPower * 1000) / (3 * 230);
|
||||||
|
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get diversity factor based on total number of consumers
|
||||||
|
getDiversityFactor(totalConsumers) {
|
||||||
|
if (totalConsumers === 1) return 1.0; // 100%
|
||||||
|
if (totalConsumers === 2) return 0.59; // 59%
|
||||||
|
return 0.45; // 45% for 3+
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate total number of consumers downstream from a node
|
||||||
|
getTotalConsumers(node) {
|
||||||
|
let totalConsumers = 0;
|
||||||
|
const visited = new Set();
|
||||||
|
|
||||||
|
const countRecursive = (currentNode) => {
|
||||||
|
if (visited.has(currentNode.id)) return 0;
|
||||||
|
visited.add(currentNode.id);
|
||||||
|
|
||||||
|
// Count consumers at this node
|
||||||
|
let consumers = 0;
|
||||||
|
if (currentNode.data.consumers3Phase)
|
||||||
|
consumers += currentNode.data.consumers3Phase;
|
||||||
|
if (currentNode.data.consumers1Phase)
|
||||||
|
consumers += Math.ceil(currentNode.data.consumers1Phase / 3); // Convert 1-phase to equivalent 3-phase units
|
||||||
|
|
||||||
|
// Count downstream consumers
|
||||||
|
for (const connection of currentNode.connections.outputs) {
|
||||||
|
consumers += countRecursive(connection.to);
|
||||||
|
}
|
||||||
|
return consumers;
|
||||||
|
};
|
||||||
|
|
||||||
|
totalConsumers = countRecursive(node);
|
||||||
|
return totalConsumers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get voltage drop percentage for a node
|
||||||
|
getVoltageDropPercentage(node) {
|
||||||
|
if (!node.data.voltage) return 0;
|
||||||
|
|
||||||
|
const baseVoltage = 230; // Base voltage for drop calculations
|
||||||
|
const actualVoltage = node.data.voltage;
|
||||||
|
|
||||||
|
return ((baseVoltage - actualVoltage) / baseVoltage) * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if voltage drop exceeds acceptable limits
|
||||||
|
isVoltageDropExcessive(node) {
|
||||||
|
const dropPercentage = this.getVoltageDropPercentage(node);
|
||||||
|
return dropPercentage > 10; // 10% is the limit for 230V (23V)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update calculations and trigger re-render
|
||||||
|
updateCalculations() {
|
||||||
|
this.calculateVoltageDrops();
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the application
|
// Initialize the application
|
||||||
|
|||||||
Reference in New Issue
Block a user