| 484 | function escCloseOnce(e){ if(e.key === 'Escape') closeDetail(); } |
| 485 | |
| 486 | function drawLineChart(id, series, emptyText, unit = ''){ |
| 487 | const canvas = $(id); |
| 488 | if(!canvas) return; |
| 489 | const ctx = canvas.getContext('2d'); |
| 490 | const W = canvas.clientWidth || canvas.width; |
| 491 | const H = canvas.height; |
| 492 | canvas.width = W; |
| 493 | ctx.clearRect(0,0,W,H); |
| 494 | const all = series.flatMap(s => s.data).filter(v => Number.isFinite(v)); |
| 495 | if(all.length < 2){ |
| 496 | ctx.fillStyle = getComputedStyle(document.body).getPropertyValue('--text-dim') || '#7a899d'; |
| 497 | ctx.font = '12px system-ui'; |
| 498 | ctx.fillText(emptyText, Math.max(12, W / 2 - 42), H / 2); |
| 499 | return; |
| 500 | } |
| 501 | const padL = unit ? 50 : 42, padR = 10, padT = 12, padB = 20; |
| 502 | let min = Math.min(0, ...all), max = Math.max(...all); |
| 503 | if(max - min < 1) max = min + 1; |
| 504 | const range = max - min; |
| 505 | const n = Math.max(...series.map(s => s.data.length)); |
| 506 | const xStep = (W - padL - padR) / Math.max(1, n - 1); |
| 507 | const light = document.body.classList.contains('light'); |
| 508 | const axis = light ? 'rgba(0,0,0,.22)' : 'rgba(255,255,255,.18)'; |
| 509 | const grid = light ? 'rgba(0,0,0,.08)' : 'rgba(255,255,255,.10)'; |
| 510 | const text = light ? 'rgba(30,41,59,.7)' : 'rgba(226,232,240,.82)'; |
| 511 | ctx.strokeStyle = axis; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(padL,padT); ctx.lineTo(padL,H-padB); ctx.lineTo(W-padR,H-padB); ctx.stroke(); |
| 512 | ctx.fillStyle = text; ctx.font = '10px system-ui'; |
| 513 | for(let i=0;i<=4;i++){ |
| 514 | const y = padT + (H-padT-padB) * i / 4; |
| 515 | const val = max - range * i / 4; |
| 516 | ctx.fillText(val.toFixed(max < 10 ? 1 : 0) + unit, 4, y + 3); |
| 517 | ctx.strokeStyle = grid; ctx.beginPath(); ctx.moveTo(padL,y); ctx.lineTo(W-padR,y); ctx.stroke(); |
| 518 | } |
| 519 | series.forEach(item => { |
| 520 | if(item.data.length < 2) return; |
| 521 | ctx.strokeStyle = item.color; ctx.lineWidth = 1.7; ctx.beginPath(); |
| 522 | item.data.forEach((v, i) => { |
| 523 | const x = padL + xStep * i; |
| 524 | const y = padT + (H-padT-padB) * (1 - (v - min) / range); |
| 525 | if(i === 0) ctx.moveTo(x,y); else ctx.lineTo(x,y); |
| 526 | }); |
| 527 | ctx.stroke(); |
| 528 | }); |
| 529 | } |
| 530 | |
| 531 | function updateTime(){ |
| 532 | const last = $('lastUpdate'); |