Spaces:
Runtime error
Runtime error
Update utils/diagram_generator.py
Browse files- utils/diagram_generator.py +368 -1
utils/diagram_generator.py
CHANGED
@@ -142,4 +142,371 @@ class DiagramGenerator:
|
|
142 |
svg += self._draw_sequence_network(150, 100, "Z1", "#059669")
|
143 |
|
144 |
# Negative sequence network
|
145 |
-
svg += f'<text x="450" y="80" text-anchor="middle" class="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
142 |
svg += self._draw_sequence_network(150, 100, "Z1", "#059669")
|
143 |
|
144 |
# Negative sequence network
|
145 |
+
svg += f'<text x="450" y="80" text-anchor="middle" class="title">Negative Sequence</text>\n'
|
146 |
+
svg += self._draw_sequence_network(450, 100, "Z2", "#dc2626")
|
147 |
+
|
148 |
+
# Zero sequence network
|
149 |
+
svg += f'<text x="750" y="80" text-anchor="middle" class="title">Zero Sequence</text>\n'
|
150 |
+
svg += self._draw_sequence_network(750, 100, "Z0", "#7c3aed")
|
151 |
+
|
152 |
+
# Connection diagram
|
153 |
+
svg += f'<text x="450" y="320" text-anchor="middle" class="title">Network Connection</text>\n'
|
154 |
+
|
155 |
+
# Series connection for L-G fault
|
156 |
+
svg += f'<line x1="400" y1="350" x2="400" y2="450" class="line"/>\n'
|
157 |
+
svg += f'<line x1="400" y1="370" x2="500" y2="370" class="line"/>\n'
|
158 |
+
svg += f'<line x1="400" y1="410" x2="500" y2="410" class="line"/>\n'
|
159 |
+
svg += f'<line x1="400" y1="450" x2="500" y2="450" class="line"/>\n'
|
160 |
+
|
161 |
+
# Impedance boxes
|
162 |
+
svg += f'<rect x="480" y="360" width="40" height="20" class="component"/>\n'
|
163 |
+
svg += f'<text x="500" y="375" text-anchor="middle" class="text">Z1</text>\n'
|
164 |
+
|
165 |
+
svg += f'<rect x="480" y="400" width="40" height="20" class="component"/>\n'
|
166 |
+
svg += f'<text x="500" y="415" text-anchor="middle" class="text">Z2</text>\n'
|
167 |
+
|
168 |
+
svg += f'<rect x="480" y="440" width="40" height="20" class="component"/>\n'
|
169 |
+
svg += f'<text x="500" y="455" text-anchor="middle" class="text">Z0</text>\n'
|
170 |
+
|
171 |
+
# Voltage source
|
172 |
+
svg += f'<circle cx="370" cy="350" r="15" class="component"/>\n'
|
173 |
+
svg += f'<text x="370" y="355" text-anchor="middle" class="text">Ea</text>\n'
|
174 |
+
|
175 |
+
# Fault point
|
176 |
+
svg += f'<use href="#fault-symbol" transform="translate(530,410)"/>\n'
|
177 |
+
svg += f'<text x="530" y="440" text-anchor="middle" class="text">Fault</text>\n'
|
178 |
+
|
179 |
+
# Current equation
|
180 |
+
svg += f'<text x="450" y="520" text-anchor="middle" class="title">Fault Current Calculation</text>\n'
|
181 |
+
svg += f'<text x="450" y="550" text-anchor="middle" class="text">I_fault = 3 × Ea / (Z1 + Z2 + Z0)</text>\n'
|
182 |
+
|
183 |
+
elif fault_type == "line_to_line":
|
184 |
+
# L-L fault diagram
|
185 |
+
svg += self._draw_ll_fault_diagram()
|
186 |
+
|
187 |
+
elif fault_type == "three_phase":
|
188 |
+
# 3-phase fault diagram
|
189 |
+
svg += self._draw_three_phase_fault_diagram()
|
190 |
+
|
191 |
+
svg += self.create_svg_footer()
|
192 |
+
return svg
|
193 |
+
|
194 |
+
def _draw_sequence_network(self, x: int, y: int, impedance: str, color: str) -> str:
|
195 |
+
"""Draw a sequence network"""
|
196 |
+
svg = ""
|
197 |
+
|
198 |
+
# Voltage source
|
199 |
+
svg += f'<circle cx="{x-50}" cy="{y+50}" r="15" stroke="{color}" stroke-width="2" fill="white"/>\n'
|
200 |
+
svg += f'<text x="{x-50}" y="{y+55}" text-anchor="middle" class="text">E</text>\n'
|
201 |
+
|
202 |
+
# Impedance
|
203 |
+
svg += f'<rect x="{x-10}" y="{y+40}" width="40" height="20" stroke="{color}" stroke-width="2" fill="white"/>\n'
|
204 |
+
svg += f'<text x="{x+10}" y="{y+55}" text-anchor="middle" class="text">{impedance}</text>\n'
|
205 |
+
|
206 |
+
# Connecting lines
|
207 |
+
svg += f'<line x1="{x-35}" y1="{y+50}" x2="{x-10}" y2="{y+50}" stroke="{color}" stroke-width="2"/>\n'
|
208 |
+
svg += f'<line x1="{x+30}" y1="{y+50}" x2="{x+60}" y2="{y+50}" stroke="{color}" stroke-width="2"/>\n'
|
209 |
+
|
210 |
+
# Ground/neutral
|
211 |
+
svg += f'<line x1="{x+60}" y1="{y+50}" x2="{x+60}" y2="{y+80}" stroke="{color}" stroke-width="2"/>\n'
|
212 |
+
svg += f'<line x1="{x+50}" y1="{y+80}" x2="{x+70}" y2="{y+80}" stroke="{color}" stroke-width="2"/>\n'
|
213 |
+
svg += f'<line x1="{x+52}" y1="{y+85}" x2="{x+68}" y2="{y+85}" stroke="{color}" stroke-width="2"/>\n'
|
214 |
+
svg += f'<line x1="{x+54}" y1="{y+90}" x2="{x+66}" y2="{y+90}" stroke="{color}" stroke-width="2"/>\n'
|
215 |
+
|
216 |
+
return svg
|
217 |
+
|
218 |
+
def _draw_ll_fault_diagram(self) -> str:
|
219 |
+
"""Draw line-to-line fault diagram"""
|
220 |
+
svg = ""
|
221 |
+
|
222 |
+
# Positive and negative sequence in parallel
|
223 |
+
svg += f'<text x="300" y="80" text-anchor="middle" class="title">L-L Fault: Z1 and Z2 in Parallel</text>\n'
|
224 |
+
|
225 |
+
# Parallel connection
|
226 |
+
svg += f'<circle cx="200" cy="150" r="15" class="component"/>\n'
|
227 |
+
svg += f'<text x="200" y="155" text-anchor="middle" class="text">Ea</text>\n'
|
228 |
+
|
229 |
+
# Upper branch (Z1)
|
230 |
+
svg += f'<line x1="215" y1="140" x2="300" y2="140" class="line"/>\n'
|
231 |
+
svg += f'<rect x="300" y="130" width="40" height="20" class="component"/>\n'
|
232 |
+
svg += f'<text x="320" y="145" text-anchor="middle" class="text">Z1</text>\n'
|
233 |
+
svg += f'<line x1="340" y1="140" x2="400" y2="140" class="line"/>\n'
|
234 |
+
|
235 |
+
# Lower branch (Z2)
|
236 |
+
svg += f'<line x1="215" y1="160" x2="300" y2="160" class="line"/>\n'
|
237 |
+
svg += f'<rect x="300" y="150" width="40" height="20" class="component"/>\n'
|
238 |
+
svg += f'<text x="320" y="165" text-anchor="middle" class="text">Z2</text>\n'
|
239 |
+
svg += f'<line x1="340" y1="160" x2="400" y2="160" class="line"/>\n'
|
240 |
+
|
241 |
+
# Connection
|
242 |
+
svg += f'<line x1="400" y1="140" x2="400" y2="160" class="line"/>\n'
|
243 |
+
|
244 |
+
# Fault
|
245 |
+
svg += f'<use href="#fault-symbol" transform="translate(420,150)"/>\n'
|
246 |
+
|
247 |
+
# Current equation
|
248 |
+
svg += f'<text x="300" y="250" text-anchor="middle" class="title">I_fault = √3 × Ea / (Z1 + Z2)</text>\n'
|
249 |
+
|
250 |
+
return svg
|
251 |
+
|
252 |
+
def _draw_three_phase_fault_diagram(self) -> str:
|
253 |
+
"""Draw three-phase fault diagram"""
|
254 |
+
svg = ""
|
255 |
+
|
256 |
+
svg += f'<text x="400" y="80" text-anchor="middle" class="title">Three-Phase Fault: Positive Sequence Only</text>\n'
|
257 |
+
|
258 |
+
# Simple circuit
|
259 |
+
svg += f'<circle cx="250" cy="150" r="15" class="component"/>\n'
|
260 |
+
svg += f'<text x="250" y="155" text-anchor="middle" class="text">Ea</text>\n'
|
261 |
+
|
262 |
+
svg += f'<line x1="265" y1="150" x2="350" y2="150" class="line"/>\n'
|
263 |
+
svg += f'<rect x="350" y="140" width="40" height="20" class="component"/>\n'
|
264 |
+
svg += f'<text x="370" y="155" text-anchor="middle" class="text">Z1</text>\n'
|
265 |
+
|
266 |
+
svg += f'<line x1="390" y1="150" x2="450" y2="150" class="line"/>\n'
|
267 |
+
svg += f'<use href="#fault-symbol" transform="translate(470,150)"/>\n'
|
268 |
+
|
269 |
+
# Current equation
|
270 |
+
svg += f'<text x="400" y="220" text-anchor="middle" class="title">I_fault = Ea / Z1</text>\n'
|
271 |
+
|
272 |
+
return svg
|
273 |
+
|
274 |
+
def generate_protection_coordination_diagram(self) -> str:
|
275 |
+
"""Generate time-current coordination curves"""
|
276 |
+
svg = self.create_svg_header(800, 600)
|
277 |
+
|
278 |
+
# Title
|
279 |
+
svg += f'<text x="400" y="30" text-anchor="middle" class="title">Protection Coordination Curves</text>\n'
|
280 |
+
|
281 |
+
# Axes
|
282 |
+
svg += f'<line x1="100" y1="500" x2="700" y2="500" class="line marker-end="url(#arrowhead)"/>\n'
|
283 |
+
svg += f'<line x1="100" y1="500" x2="100" y2="100" class="line marker-end="url(#arrowhead)"/>\n'
|
284 |
+
|
285 |
+
# Axis labels
|
286 |
+
svg += f'<text x="400" y="530" text-anchor="middle" class="text">Current (A)</text>\n'
|
287 |
+
svg += f'<text x="50" y="300" text-anchor="middle" class="text" transform="rotate(-90 50 300)">Time (s)</text>\n'
|
288 |
+
|
289 |
+
# Grid lines
|
290 |
+
for i in range(2, 7):
|
291 |
+
x = 100 + i * 100
|
292 |
+
svg += f'<line x1="{x}" y1="100" x2="{x}" y2="500" stroke="#e5e7eb" stroke-width="1"/>\n'
|
293 |
+
svg += f'<text x="{x}" y="520" text-anchor="middle" class="text">{10**(i-1)}</text>\n'
|
294 |
+
|
295 |
+
for i in range(1, 5):
|
296 |
+
y = 500 - i * 80
|
297 |
+
svg += f'<line x1="100" y1="{y}" x2="700" y2="{y}" stroke="#e5e7eb" stroke-width="1"/>\n'
|
298 |
+
svg += f'<text x="85" y="{y+5}" text-anchor="end" class="text">{10**(i-1)}</text>\n'
|
299 |
+
|
300 |
+
# Relay curves
|
301 |
+
# Primary relay (closer to load)
|
302 |
+
svg += self._draw_relay_curve(200, "Primary Relay", "#059669", "inverse")
|
303 |
+
|
304 |
+
# Backup relay
|
305 |
+
svg += self._draw_relay_curve(300, "Backup Relay", "#dc2626", "very_inverse")
|
306 |
+
|
307 |
+
# Fuse curve
|
308 |
+
svg += self._draw_relay_curve(150, "Fuse", "#7c3aed", "fuse")
|
309 |
+
|
310 |
+
# Legend
|
311 |
+
svg += f'<rect x="550" y="120" width="180" height="120" stroke="#374151" stroke-width="1" fill="white"/>\n'
|
312 |
+
svg += f'<text x="640" y="140" text-anchor="middle" class="text">Legend</text>\n'
|
313 |
+
|
314 |
+
svg += f'<line x1="560" y1="160" x2="590" y2="160" stroke="#7c3aed" stroke-width="3"/>\n'
|
315 |
+
svg += f'<text x="600" y="165" class="text">Fuse</text>\n'
|
316 |
+
|
317 |
+
svg += f'<line x1="560" y1="180" x2="590" y2="180" stroke="#059669" stroke-width="3"/>\n'
|
318 |
+
svg += f'<text x="600" y="185" class="text">Primary Relay</text>\n'
|
319 |
+
|
320 |
+
svg += f'<line x1="560" y1="200" x2="590" y2="200" stroke="#dc2626" stroke-width="3"/>\n'
|
321 |
+
svg += f'<text x="600" y="205" class="text">Backup Relay</text>\n'
|
322 |
+
|
323 |
+
svg += f'<text x="640" y="225" text-anchor="middle" class="text">Coordination Interval: 0.3s</text>\n'
|
324 |
+
|
325 |
+
svg += self.create_svg_footer()
|
326 |
+
return svg
|
327 |
+
|
328 |
+
def _draw_relay_curve(self, x_offset: int, label: str, color: str, curve_type: str) -> str:
|
329 |
+
"""Draw a time-current curve for a relay"""
|
330 |
+
svg = ""
|
331 |
+
|
332 |
+
points = []
|
333 |
+
|
334 |
+
if curve_type == "inverse":
|
335 |
+
# Standard inverse curve
|
336 |
+
for i in range(50):
|
337 |
+
current = 10 ** (i / 10.0)
|
338 |
+
time = 0.14 / ((current/100) ** 0.02 - 1)
|
339 |
+
if time > 0 and time < 1000:
|
340 |
+
x = 100 + (i * 12)
|
341 |
+
y = 500 - (time * 40)
|
342 |
+
if 100 <= x <= 700 and 100 <= y <= 500:
|
343 |
+
points.append(f"{x},{y}")
|
344 |
+
|
345 |
+
elif curve_type == "very_inverse":
|
346 |
+
# Very inverse curve (higher up)
|
347 |
+
for i in range(50):
|
348 |
+
current = 10 ** (i / 10.0)
|
349 |
+
time = 13.5 / ((current/100) ** 1.0 - 1)
|
350 |
+
if time > 0 and time < 1000:
|
351 |
+
x = 100 + (i * 12)
|
352 |
+
y = 500 - (time * 20)
|
353 |
+
if 100 <= x <= 700 and 100 <= y <= 500:
|
354 |
+
points.append(f"{x},{y}")
|
355 |
+
|
356 |
+
elif curve_type == "fuse":
|
357 |
+
# Fuse curve (faster, lower)
|
358 |
+
for i in range(40):
|
359 |
+
current = 10 ** (i / 8.0)
|
360 |
+
time = 0.01 / ((current/50) ** 2.0 - 1) if current > 50 else 1000
|
361 |
+
if time > 0 and time < 1000:
|
362 |
+
x = 100 + (i * 15)
|
363 |
+
y = 500 - (time * 60)
|
364 |
+
if 100 <= x <= 700 and 100 <= y <= 500:
|
365 |
+
points.append(f"{x},{y}")
|
366 |
+
|
367 |
+
if points:
|
368 |
+
path = f'<polyline points="{" ".join(points)}" stroke="{color}" stroke-width="3" fill="none"/>\n'
|
369 |
+
svg += path
|
370 |
+
|
371 |
+
# Label
|
372 |
+
if len(points) > 10:
|
373 |
+
mid_point = points[len(points)//2].split(',')
|
374 |
+
x, y = int(mid_point[0]), int(mid_point[1])
|
375 |
+
svg += f'<text x="{x+10}" y="{y-5}" class="text" fill="{color}">{label}</text>\n'
|
376 |
+
|
377 |
+
return svg
|
378 |
+
|
379 |
+
def generate_phasor_diagram(self, fault_type: str = "balanced") -> str:
|
380 |
+
"""Generate phasor diagrams for different fault conditions"""
|
381 |
+
svg = self.create_svg_header(600, 400)
|
382 |
+
|
383 |
+
# Title
|
384 |
+
svg += f'<text x="300" y="30" text-anchor="middle" class="title">Phasor Diagram - {fault_type.title()} Conditions</text>\n'
|
385 |
+
|
386 |
+
center_x, center_y = 300, 200
|
387 |
+
radius = 80
|
388 |
+
|
389 |
+
if fault_type == "balanced":
|
390 |
+
# Three balanced phasors 120° apart
|
391 |
+
angles = [0, 120, 240]
|
392 |
+
colors = ["#dc2626", "#059669", "#2563eb"]
|
393 |
+
labels = ["Va", "Vb", "Vc"]
|
394 |
+
|
395 |
+
for i, (angle, color, label) in enumerate(zip(angles, colors, labels)):
|
396 |
+
x_end = center_x + radius * cos(radians(angle))
|
397 |
+
y_end = center_y - radius * sin(radians(angle))
|
398 |
+
|
399 |
+
svg += f'<line x1="{center_x}" y1="{center_y}" x2="{x_end}" y2="{y_end}" '
|
400 |
+
svg += f'stroke="{color}" stroke-width="3" marker-end="url(#arrowhead)"/>\n'
|
401 |
+
|
402 |
+
# Label
|
403 |
+
label_x = center_x + (radius + 20) * cos(radians(angle))
|
404 |
+
label_y = center_y - (radius + 20) * sin(radians(angle))
|
405 |
+
svg += f'<text x="{label_x}" y="{label_y}" text-anchor="middle" class="text" fill="{color}">{label}</text>\n'
|
406 |
+
|
407 |
+
elif fault_type == "unbalanced":
|
408 |
+
# Unbalanced phasors showing fault condition
|
409 |
+
# Phase A (affected by fault) - reduced magnitude
|
410 |
+
svg += f'<line x1="{center_x}" y1="{center_y}" x2="{center_x + 40}" y2="{center_y}" '
|
411 |
+
svg += f'stroke="#dc2626" stroke-width="3" marker-end="url(#arrowhead)"/>\n'
|
412 |
+
svg += f'<text x="{center_x + 60}" y="{center_y}" class="text" fill="#dc2626">Va (faulted)</text>\n'
|
413 |
+
|
414 |
+
# Phase B - normal
|
415 |
+
x_b = center_x + radius * cos(radians(120))
|
416 |
+
y_b = center_y - radius * sin(radians(120))
|
417 |
+
svg += f'<line x1="{center_x}" y1="{center_y}" x2="{x_b}" y2="{y_b}" '
|
418 |
+
svg += f'stroke="#059669" stroke-width="3" marker-end="url(#arrowhead)"/>\n'
|
419 |
+
svg += f'<text x="{x_b-20}" y="{y_b-10}" class="text" fill="#059669">Vb</text>\n'
|
420 |
+
|
421 |
+
# Phase C - normal
|
422 |
+
x_c = center_x + radius * cos(radians(240))
|
423 |
+
y_c = center_y - radius * sin(radians(240))
|
424 |
+
svg += f'<line x1="{center_x}" y1="{center_y}" x2="{x_c}" y2="{y_c}" '
|
425 |
+
svg += f'stroke="#2563eb" stroke-width="3" marker-end="url(#arrowhead)"/>\n'
|
426 |
+
svg += f'<text x="{x_c-20}" y="{y_c+20}" class="text" fill="#2563eb">Vc</text>\n'
|
427 |
+
|
428 |
+
# Center point
|
429 |
+
svg += f'<circle cx="{center_x}" cy="{center_y}" r="3" fill="#374151"/>\n'
|
430 |
+
|
431 |
+
# Reference circle
|
432 |
+
svg += f'<circle cx="{center_x}" cy="{center_y}" r="{radius}" stroke="#e5e7eb" stroke-width="1" fill="none" stroke-dasharray="5,5"/>\n'
|
433 |
+
|
434 |
+
svg += self.create_svg_footer()
|
435 |
+
return svg
|
436 |
+
|
437 |
+
def generate_impedance_diagram(self) -> str:
|
438 |
+
"""Generate impedance diagram for distance protection"""
|
439 |
+
svg = self.create_svg_header(600, 500)
|
440 |
+
|
441 |
+
# Title
|
442 |
+
svg += f'<text x="300" y="30" text-anchor="middle" class="title">Distance Protection - R-X Diagram</text>\n'
|
443 |
+
|
444 |
+
center_x, center_y = 300, 250
|
445 |
+
|
446 |
+
# Axes
|
447 |
+
svg += f'<line x1="100" y1="{center_y}" x2="500" y2="{center_y}" class="line" marker-end="url(#arrowhead)"/>\n'
|
448 |
+
svg += f'<line x1="{center_x}" y1="400" x2="{center_x}" y2="100" class="line" marker-end="url(#arrowhead)"/>\n'
|
449 |
+
|
450 |
+
# Axis labels
|
451 |
+
svg += f'<text x="520" y="{center_y+5}" class="text">R (Ω)</text>\n'
|
452 |
+
svg += f'<text x="{center_x-10}" y="90" class="text">X (Ω)</text>\n'
|
453 |
+
|
454 |
+
# Mho circle (Zone 1)
|
455 |
+
svg += f'<circle cx="{center_x+50}" cy="{center_y}" r="80" stroke="#059669" stroke-width="2" fill="none"/>\n'
|
456 |
+
svg += f'<text x="{center_x+90}" y="{center_y-90}" class="text" fill="#059669">Zone 1 (Mho)</text>\n'
|
457 |
+
|
458 |
+
# Zone 2 (larger circle)
|
459 |
+
svg += f'<circle cx="{center_x+70}" cy="{center_y}" r="120" stroke="#dc2626" stroke-width="2" fill="none" stroke-dasharray="5,5"/>\n'
|
460 |
+
svg += f'<text x="{center_x+140}" y="{center_y-130}" class="text" fill="#dc2626">Zone 2</text>\n'
|
461 |
+
|
462 |
+
# Load impedance area
|
463 |
+
svg += f'<path d="M{center_x+20},{center_y-20} Q{center_x+80},{center_y-40} {center_x+120},{center_y-10}" '
|
464 |
+
svg += f'stroke="#7c3aed" stroke-width="2" fill="none"/>\n'
|
465 |
+
svg += f'<text x="{center_x+70}" y="{center_y-50}" class="text" fill="#7c3aed">Load Region</text>\n'
|
466 |
+
|
467 |
+
# Grid marks
|
468 |
+
for i in range(1, 5):
|
469 |
+
x = center_x + i * 40
|
470 |
+
svg += f'<line x1="{x}" y1="{center_y-5}" x2="{x}" y2="{center_y+5}" stroke="#374151" stroke-width="1"/>\n'
|
471 |
+
svg += f'<text x="{x}" y="{center_y+20}" text-anchor="middle" class="text">{i*5}</text>\n'
|
472 |
+
|
473 |
+
y = center_y - i * 40
|
474 |
+
svg += f'<line x1="{center_x-5}" y1="{y}" x2="{center_x+5}" y2="{y}" stroke="#374151" stroke-width="1"/>\n'
|
475 |
+
svg += f'<text x="{center_x-20}" y="{y+5}" text-anchor="middle" class="text">{i*5}</text>\n'
|
476 |
+
|
477 |
+
svg += self.create_svg_footer()
|
478 |
+
return svg
|
479 |
+
|
480 |
+
# Helper functions for calculations
|
481 |
+
def cos(angle_deg):
|
482 |
+
import math
|
483 |
+
return math.cos(math.radians(angle_deg))
|
484 |
+
|
485 |
+
def sin(angle_deg):
|
486 |
+
import math
|
487 |
+
return math.sin(math.radians(angle_deg))
|
488 |
+
|
489 |
+
def radians(angle_deg):
|
490 |
+
import math
|
491 |
+
return math.radians(angle_deg)
|
492 |
+
|
493 |
+
# Example usage
|
494 |
+
if __name__ == "__main__":
|
495 |
+
generator = DiagramGenerator()
|
496 |
+
|
497 |
+
# Test diagram generation
|
498 |
+
diagrams = {
|
499 |
+
"single_line": generator.generate_single_line_diagram({}),
|
500 |
+
"fault_analysis": generator.generate_fault_analysis_diagram("line_to_ground"),
|
501 |
+
"protection_coordination": generator.generate_protection_coordination_diagram(),
|
502 |
+
"phasor": generator.generate_phasor_diagram("balanced"),
|
503 |
+
"impedance": generator.generate_impedance_diagram()
|
504 |
+
}
|
505 |
+
|
506 |
+
# Save diagrams
|
507 |
+
for name, svg_content in diagrams.items():
|
508 |
+
with open(f"{name}_diagram.svg", "w") as f:
|
509 |
+
f.write(svg_content)
|
510 |
+
print(f"Generated {name} diagram")
|
511 |
+
|
512 |
+
print("All diagrams generated successfully!")
|