Update static/js/solar_animation.js
Browse files- static/js/solar_animation.js +63 -50
static/js/solar_animation.js
CHANGED
@@ -6,13 +6,13 @@ const ctx = canvas.getContext('2d');
|
|
6 |
let animationFrameId;
|
7 |
let energyFlowOffset = 0; // Dịch chuyển để tạo hiệu ứng dòng chảy của mũi tên
|
8 |
|
9 |
-
// Kích thước và vị trí các thành phần
|
10 |
const solarPanel = { x: 50, y: 50, width: 120, height: 90 };
|
11 |
-
const inverter = { x: 280, y: 150, width: 90, height: 70 };
|
12 |
-
const battery = { x:
|
13 |
-
const homeLoad = { x: 650, y: 150, width: 80, height: 70 };
|
14 |
-
const grid = { x:
|
15 |
-
const generator = { x:
|
16 |
|
17 |
// Định nghĩa màu sắc và thuộc tính
|
18 |
const colors = {
|
@@ -93,16 +93,18 @@ function drawInverterIcon(x, y, width, height) {
|
|
93 |
|
94 |
// Các cổng kết nối
|
95 |
ctx.fillStyle = colors.inverterAccent;
|
96 |
-
ctx.fillRect(x - 5, y + height * 0.2, 5, 10); // Cổng vào DC
|
97 |
-
ctx.fillRect(x + width, y + height * 0.7, 5, 10); // Cổng ra AC
|
|
|
|
|
98 |
|
99 |
// Ký hiệu DC/AC
|
100 |
ctx.fillStyle = '#FFF';
|
101 |
ctx.font = 'bold 12px Arial';
|
102 |
ctx.textAlign = 'center';
|
103 |
ctx.textBaseline = 'middle';
|
104 |
-
ctx.fillText('DC', x + width * 0.
|
105 |
-
ctx.fillText('AC', x + width * 0.
|
106 |
|
107 |
ctx.fillStyle = colors.text;
|
108 |
ctx.fillText('Biến tần', x + width / 2, y + height + 20);
|
@@ -216,7 +218,7 @@ function drawGeneratorIcon(x, y, width, height) {
|
|
216 |
|
217 |
// --- Hàm vẽ các đường thẳng/ngang và mũi tên di chuyển ---
|
218 |
|
219 |
-
function drawLinearFlow(points, offset, arrowSize = 10) {
|
220 |
ctx.strokeStyle = colors.flowPrimary;
|
221 |
ctx.lineWidth = 4;
|
222 |
ctx.beginPath();
|
@@ -232,49 +234,57 @@ function drawLinearFlow(points, offset, arrowSize = 10) {
|
|
232 |
|
233 |
// Tính toán tổng chiều dài đường dẫn
|
234 |
let totalLength = 0;
|
|
|
235 |
for (let i = 0; i < points.length - 1; i++) {
|
236 |
-
|
237 |
Math.pow(points[i+1].x - points[i].x, 2) +
|
238 |
Math.pow(points[i+1].y - points[i].y, 2)
|
239 |
);
|
|
|
|
|
240 |
}
|
241 |
|
242 |
const numArrows = Math.floor(totalLength / (arrowSize * 2.5));
|
243 |
const arrowSpacing = totalLength / numArrows;
|
244 |
|
245 |
for (let i = 0; i < numArrows; i++) {
|
246 |
-
let currentPathPos = (offset + i * arrowSpacing)
|
|
|
|
|
|
|
|
|
247 |
if (currentPathPos < 0) currentPathPos += totalLength;
|
248 |
|
249 |
-
let
|
250 |
let p1 = null, p2 = null;
|
251 |
let segmentIndex = -1;
|
252 |
|
253 |
// Tìm đoạn đường mà mũi tên đang ở trên
|
254 |
for (let j = 0; j < points.length - 1; j++) {
|
255 |
-
const segLen =
|
256 |
-
|
257 |
-
Math.pow(points[j+1].y - points[j].y, 2)
|
258 |
-
);
|
259 |
-
if (currentPathPos >= segmentLength && currentPathPos <= segmentLength + segLen) {
|
260 |
p1 = points[j];
|
261 |
p2 = points[j+1];
|
262 |
segmentIndex = j;
|
263 |
break;
|
264 |
}
|
265 |
-
|
266 |
}
|
267 |
|
268 |
if (p1 && p2) {
|
269 |
const dx = p2.x - p1.x;
|
270 |
const dy = p2.y - p1.y;
|
271 |
-
const segmentRelativePos = currentPathPos -
|
272 |
const ratio = segLen > 0 ? segmentRelativePos / segLen : 0;
|
273 |
|
274 |
const arrowX = p1.x + dx * ratio;
|
275 |
const arrowY = p1.y + dy * ratio;
|
276 |
|
277 |
-
|
|
|
|
|
|
|
|
|
278 |
|
279 |
ctx.save();
|
280 |
ctx.translate(arrowX, arrowY);
|
@@ -303,57 +313,60 @@ function draw() {
|
|
303 |
drawInverterIcon(inverter.x, inverter.y, inverter.width, inverter.height);
|
304 |
drawBatteryIcon(battery.x, battery.y, battery.width, battery.height);
|
305 |
drawHomeIcon(homeLoad.x, homeLoad.y, homeLoad.width, homeLoad.height);
|
306 |
-
drawGridIcon(grid.x, grid.y, grid.width, grid.height);
|
307 |
-
drawGeneratorIcon(generator.x, generator.y, generator.width, generator.height);
|
308 |
|
309 |
// Vẽ luồng năng lượng bằng đường thẳng/ngang
|
310 |
// 1. Tấm pin -> Biến tần (DC)
|
311 |
drawLinearFlow([
|
312 |
-
{ x: solarPanel.x + solarPanel.width / 2, y: solarPanel.y + solarPanel.height },
|
313 |
-
{ x: solarPanel.x + solarPanel.width / 2, y: inverter.y + inverter.height
|
314 |
-
{ x: inverter.x, y: inverter.y + inverter.height
|
315 |
], energyFlowOffset);
|
316 |
|
317 |
// 2. Biến tần -> Tải nhà (AC)
|
318 |
drawLinearFlow([
|
319 |
-
{ x: inverter.x + inverter.width, y: inverter.y + inverter.height
|
320 |
-
{ x: homeLoad.x, y: homeLoad.y + homeLoad.height / 2 }
|
321 |
], energyFlowOffset);
|
322 |
|
323 |
// 3. Biến tần <-> Lưới điện (AC)
|
|
|
324 |
drawLinearFlow([
|
325 |
-
{ x: inverter.x + inverter.width
|
326 |
-
{ x: inverter.x + inverter.width
|
327 |
-
{ x: grid.x, y: grid.y + grid.height / 2 }
|
328 |
-
], energyFlowOffset);
|
329 |
|
|
|
330 |
drawLinearFlow([
|
331 |
-
{ x: grid.x, y: grid.y + grid.height / 2 }, //
|
332 |
-
{ x: inverter.x + inverter.width
|
333 |
-
{ x: inverter.x + inverter.width
|
334 |
-
],
|
335 |
-
|
336 |
|
337 |
// 4. Biến tần <-> Pin lưu trữ (AC/DC)
|
|
|
338 |
drawLinearFlow([
|
339 |
-
{ x: inverter.x + inverter.width
|
340 |
-
{ x: inverter.x + inverter.width
|
341 |
-
{ x: battery.x + battery.width, y: battery.y + battery.height / 2 } //
|
342 |
-
], energyFlowOffset);
|
343 |
|
|
|
344 |
drawLinearFlow([
|
345 |
-
{ x: battery.x + battery.width, y: battery.y + battery.height / 2 },
|
346 |
-
{ x: inverter.x + inverter.width
|
347 |
-
{ x: inverter.x + inverter.width
|
348 |
-
],
|
349 |
|
350 |
-
|
|
|
351 |
drawLinearFlow([
|
352 |
-
{ x: generator.x + generator.width, y: generator.y + generator.height / 2 },
|
353 |
-
{ x: inverter.x, y: inverter.y + inverter.height
|
354 |
], energyFlowOffset);
|
355 |
|
356 |
-
|
357 |
// Cập nhật offset để tạo hoạt hình dòng chảy
|
358 |
energyFlowOffset = (energyFlowOffset + 2) % 100; // Thay đổi tốc độ và khoảng cách mũi tên
|
359 |
|
|
|
6 |
let animationFrameId;
|
7 |
let energyFlowOffset = 0; // Dịch chuyển để tạo hiệu ứng dòng chảy của mũi tên
|
8 |
|
9 |
+
// Kích thước và vị trí các thành phần (đã điều chỉnh để có chỗ cho các luồng mới)
|
10 |
const solarPanel = { x: 50, y: 50, width: 120, height: 90 };
|
11 |
+
const inverter = { x: 280, y: 150, width: 90, height: 70 }; // Biến tần là trung tâm
|
12 |
+
const battery = { x: 50, y: 280, width: 80, height: 110 }; // Pin ở dưới bên trái
|
13 |
+
const homeLoad = { x: 650, y: 150, width: 80, height: 70 }; // Tải nhà bên phải
|
14 |
+
const grid = { x: 480, y: 50, width: 100, height: 70 }; // Lưới điện ở trên bên phải
|
15 |
+
const generator = { x: 280, y: 320, width: 100, height: 70 }; // Máy phát điện ở dưới biến tần
|
16 |
|
17 |
// Định nghĩa màu sắc và thuộc tính
|
18 |
const colors = {
|
|
|
93 |
|
94 |
// Các cổng kết nối
|
95 |
ctx.fillStyle = colors.inverterAccent;
|
96 |
+
ctx.fillRect(x - 5, y + height * 0.2, 5, 10); // Cổng vào DC (từ Solar)
|
97 |
+
ctx.fillRect(x + width, y + height * 0.7, 5, 10); // Cổng ra AC (tới Home Load)
|
98 |
+
ctx.fillRect(x + width * 0.25, y - 5, 10, 5); // Cổng lên Grid
|
99 |
+
ctx.fillRect(x + width * 0.75, y + height, 10, 5); // Cổng xuống Battery/Generator
|
100 |
|
101 |
// Ký hiệu DC/AC
|
102 |
ctx.fillStyle = '#FFF';
|
103 |
ctx.font = 'bold 12px Arial';
|
104 |
ctx.textAlign = 'center';
|
105 |
ctx.textBaseline = 'middle';
|
106 |
+
ctx.fillText('DC', x + width * 0.15, y + height / 2); // gần cổng vào DC
|
107 |
+
ctx.fillText('AC', x + width * 0.85, y + height / 2); // gần cổng ra AC
|
108 |
|
109 |
ctx.fillStyle = colors.text;
|
110 |
ctx.fillText('Biến tần', x + width / 2, y + height + 20);
|
|
|
218 |
|
219 |
// --- Hàm vẽ các đường thẳng/ngang và mũi tên di chuyển ---
|
220 |
|
221 |
+
function drawLinearFlow(points, offset, arrowSize = 10, reverse = false) {
|
222 |
ctx.strokeStyle = colors.flowPrimary;
|
223 |
ctx.lineWidth = 4;
|
224 |
ctx.beginPath();
|
|
|
234 |
|
235 |
// Tính toán tổng chiều dài đường dẫn
|
236 |
let totalLength = 0;
|
237 |
+
const segmentLengths = [];
|
238 |
for (let i = 0; i < points.length - 1; i++) {
|
239 |
+
const segLen = Math.sqrt(
|
240 |
Math.pow(points[i+1].x - points[i].x, 2) +
|
241 |
Math.pow(points[i+1].y - points[i].y, 2)
|
242 |
);
|
243 |
+
segmentLengths.push(segLen);
|
244 |
+
totalLength += segLen;
|
245 |
}
|
246 |
|
247 |
const numArrows = Math.floor(totalLength / (arrowSize * 2.5));
|
248 |
const arrowSpacing = totalLength / numArrows;
|
249 |
|
250 |
for (let i = 0; i < numArrows; i++) {
|
251 |
+
let currentPathPos = (offset + i * arrowSpacing);
|
252 |
+
if (reverse) {
|
253 |
+
currentPathPos = totalLength - currentPathPos; // Đảo ngược hướng cho mũi tên
|
254 |
+
}
|
255 |
+
currentPathPos = currentPathPos % totalLength;
|
256 |
if (currentPathPos < 0) currentPathPos += totalLength;
|
257 |
|
258 |
+
let segmentLengthAccumulated = 0;
|
259 |
let p1 = null, p2 = null;
|
260 |
let segmentIndex = -1;
|
261 |
|
262 |
// Tìm đoạn đường mà mũi tên đang ở trên
|
263 |
for (let j = 0; j < points.length - 1; j++) {
|
264 |
+
const segLen = segmentLengths[j];
|
265 |
+
if (currentPathPos >= segmentLengthAccumulated && currentPathPos <= segmentLengthAccumulated + segLen) {
|
|
|
|
|
|
|
266 |
p1 = points[j];
|
267 |
p2 = points[j+1];
|
268 |
segmentIndex = j;
|
269 |
break;
|
270 |
}
|
271 |
+
segmentLengthAccumulated += segLen;
|
272 |
}
|
273 |
|
274 |
if (p1 && p2) {
|
275 |
const dx = p2.x - p1.x;
|
276 |
const dy = p2.y - p1.y;
|
277 |
+
const segmentRelativePos = currentPathPos - segmentLengthAccumulated;
|
278 |
const ratio = segLen > 0 ? segmentRelativePos / segLen : 0;
|
279 |
|
280 |
const arrowX = p1.x + dx * ratio;
|
281 |
const arrowY = p1.y + dy * ratio;
|
282 |
|
283 |
+
let angle = Math.atan2(dy, dx);
|
284 |
+
if (reverse) {
|
285 |
+
angle += Math.PI; // Xoay mũi tên 180 độ nếu ngược chiều
|
286 |
+
}
|
287 |
+
|
288 |
|
289 |
ctx.save();
|
290 |
ctx.translate(arrowX, arrowY);
|
|
|
313 |
drawInverterIcon(inverter.x, inverter.y, inverter.width, inverter.height);
|
314 |
drawBatteryIcon(battery.x, battery.y, battery.width, battery.height);
|
315 |
drawHomeIcon(homeLoad.x, homeLoad.y, homeLoad.width, homeLoad.height);
|
316 |
+
drawGridIcon(grid.x, grid.y, grid.width, grid.height);
|
317 |
+
drawGeneratorIcon(generator.x, generator.y, generator.width, generator.height);
|
318 |
|
319 |
// Vẽ luồng năng lượng bằng đường thẳng/ngang
|
320 |
// 1. Tấm pin -> Biến tần (DC)
|
321 |
drawLinearFlow([
|
322 |
+
{ x: solarPanel.x + solarPanel.width / 2, y: solarPanel.y + solarPanel.height }, // Dưới pin
|
323 |
+
{ x: solarPanel.x + solarPanel.width / 2, y: inverter.y + inverter.height * 0.2 }, // Điểm uốn ngang
|
324 |
+
{ x: inverter.x, y: inverter.y + inverter.height * 0.2 } // Vào biến tần (cổng DC)
|
325 |
], energyFlowOffset);
|
326 |
|
327 |
// 2. Biến tần -> Tải nhà (AC)
|
328 |
drawLinearFlow([
|
329 |
+
{ x: inverter.x + inverter.width, y: inverter.y + inverter.height * 0.7 }, // Ra biến tần (cổng AC)
|
330 |
+
{ x: homeLoad.x, y: homeLoad.y + homeLoad.height / 2 } // Vào tải nhà
|
331 |
], energyFlowOffset);
|
332 |
|
333 |
// 3. Biến tần <-> Lưới điện (AC)
|
334 |
+
// Biến tần -> Lưới (Xuất điện)
|
335 |
drawLinearFlow([
|
336 |
+
{ x: inverter.x + inverter.width * 0.25, y: inverter.y }, // Từ biến tần (cổng lên Grid)
|
337 |
+
{ x: inverter.x + inverter.width * 0.25, y: grid.y + grid.height / 2 }, // Điểm uốn ngang
|
338 |
+
{ x: grid.x, y: grid.y + grid.height / 2 } // Vào lưới
|
339 |
+
], energyFlowOffset);
|
340 |
|
341 |
+
// Lưới -> Biến tần (Nhập điện)
|
342 |
drawLinearFlow([
|
343 |
+
{ x: grid.x, y: grid.y + grid.height / 2 }, // Từ lưới
|
344 |
+
{ x: inverter.x + inverter.width * 0.25, y: grid.y + grid.height / 2 }, // Điểm uốn ngang
|
345 |
+
{ x: inverter.x + inverter.width * 0.25, y: inverter.y } // Vào biến tần (cổng lên Grid)
|
346 |
+
], energyFlowOffset, 10, true); // true để đảo ngược hướng mũi tên
|
|
|
347 |
|
348 |
// 4. Biến tần <-> Pin lưu trữ (AC/DC)
|
349 |
+
// Biến tần -> Pin (Sạc)
|
350 |
drawLinearFlow([
|
351 |
+
{ x: inverter.x + inverter.width * 0.75, y: inverter.y + inverter.height }, // Từ biến tần (cổng xuống Battery)
|
352 |
+
{ x: inverter.x + inverter.width * 0.75, y: battery.y + battery.height / 2 }, // Điểm uốn ngang
|
353 |
+
{ x: battery.x + battery.width, y: battery.y + battery.height / 2 } // Vào pin
|
354 |
+
], energyFlowOffset);
|
355 |
|
356 |
+
// Pin -> Biến tần (Xả)
|
357 |
drawLinearFlow([
|
358 |
+
{ x: battery.x + battery.width, y: battery.y + battery.height / 2 }, // Từ pin
|
359 |
+
{ x: inverter.x + inverter.width * 0.75, y: battery.y + battery.height / 2 }, // Điểm uốn ngang
|
360 |
+
{ x: inverter.x + inverter.width * 0.75, y: inverter.y + inverter.height } // Vào biến tần (cổng xuống Battery)
|
361 |
+
], energyFlowOffset, 10, true); // true để đảo ngược hướng mũi tên
|
362 |
|
363 |
+
|
364 |
+
// 5. Máy phát điện -> Biến tần (AC)
|
365 |
drawLinearFlow([
|
366 |
+
{ x: generator.x + generator.width, y: generator.y + generator.height / 2 }, // Từ máy phát
|
367 |
+
{ x: inverter.x, y: inverter.y + inverter.height * 0.7 } // Vào biến tần (cổng AC)
|
368 |
], energyFlowOffset);
|
369 |
|
|
|
370 |
// Cập nhật offset để tạo hoạt hình dòng chảy
|
371 |
energyFlowOffset = (energyFlowOffset + 2) % 100; // Thay đổi tốc độ và khoảng cách mũi tên
|
372 |
|