feat: Add voltage drop calculation and update button in ObjectFlowDesigner
This commit is contained in:
@@ -11,6 +11,9 @@
|
||||
<header class="header">
|
||||
<h1>Power System Designer</h1>
|
||||
<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="exportBtn" class="btn btn-secondary">Export Data</button>
|
||||
<button id="importBtn" class="btn btn-secondary">Import Data</button>
|
||||
|
||||
218
script.js
218
script.js
@@ -71,7 +71,13 @@ class ObjectFlowDesigner {
|
||||
.getElementById("fileInput")
|
||||
.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));
|
||||
}
|
||||
|
||||
@@ -824,6 +830,35 @@ class ObjectFlowDesigner {
|
||||
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)
|
||||
if (obj.data.boxPoleType) {
|
||||
this.ctx.fillText(
|
||||
@@ -920,6 +955,22 @@ class ObjectFlowDesigner {
|
||||
textLines.push(`${cableTypeDisplay} ${connection.data.crossSection}mm²`);
|
||||
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) {
|
||||
this.ctx.font = "11px Arial";
|
||||
this.ctx.textAlign = "center";
|
||||
@@ -1249,6 +1300,11 @@ class ObjectFlowDesigner {
|
||||
const isOverheadCable =
|
||||
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 = `
|
||||
<div class="property-group">
|
||||
<h4>Power Cable Properties</h4>
|
||||
@@ -1287,19 +1343,38 @@ class ObjectFlowDesigner {
|
||||
<p><strong>Type:</strong> ${conn.data.cableType} ${
|
||||
isOverheadCable ? "(Overhead)" : "(Ground)"
|
||||
}</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"
|
||||
? "Transformer"
|
||||
: this.getNodeDisplayType(conn.from.data.nodeType)
|
||||
})</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"
|
||||
? "Transformer"
|
||||
: this.getNodeDisplayType(conn.to.data.nodeType)
|
||||
})</p>
|
||||
<p><strong>Capacity:</strong> ${conn.data.crossSection} mm² × ${
|
||||
<p><strong>Specifications:</strong> ${conn.data.crossSection}mm² × ${
|
||||
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
|
||||
? '<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">
|
||||
<h4>Actions</h4>
|
||||
<button class="btn btn-primary" onclick="designer.updateCalculations()">Calculate Voltage Drops</button>
|
||||
<button class="btn btn-danger" onclick="designer.deleteConnection(${
|
||||
conn.id
|
||||
})">Delete Cable</button>
|
||||
@@ -1684,6 +1760,138 @@ class ObjectFlowDesigner {
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user