{"spec_id":"line-load-duration","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// line-load-duration: Load Duration Curve for Energy Systems\n// Library: chartjs 4.4.7 | JavaScript 22.22.3\n// Quality: 91/100 | Created: 2026-06-10\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (deterministic synthetic annual load profile) ---\nconst N = 876; // 876 points, each representing 10 hours (876 × 10 = 8,760 h/year)\nconst hours = Array.from({ length: N }, (_, i) => i * 10);\n\nconst loads = hours.map((_, i) => {\n  const r = i / (N - 1); // normalized 0–1\n  let mw;\n  if (r < 0.1) {\n    mw = 1200 - 300 * (r / 0.1);          // Peak:         1200 → 900 MW\n  } else if (r < 0.6) {\n    mw = 900 - 380 * ((r - 0.1) / 0.5);   // Intermediate:  900 → 520 MW\n  } else {\n    mw = 520 - 120 * ((r - 0.6) / 0.4);   // Base:          520 → 400 MW\n  }\n  return Math.round(mw);\n});\n\n// Region boundary indices (load sorted descending, hours rank-ordered)\nconst PEAK_END  = 88;   // hours 0–870  (top 10 %)\nconst INTER_END = 526;  // hours 880–5250 (middle 50 %)\n                        // hours 5260–8750 (bottom 40 %) = base load\n\n// Total annual energy (area under curve, GWh)\nconst totalGWh = Math.round(loads.reduce((s, v) => s + v * 10, 0) / 1000);\n\n// Region fill arrays — null outside each region so Chart.js creates clean fill gaps\nconst fillPeak  = loads.map((v, i) => (i < PEAK_END                    ? v : null));\nconst fillInter = loads.map((v, i) => (i >= PEAK_END && i < INTER_END  ? v : null));\nconst fillBase  = loads.map((v, i) => (i >= INTER_END                  ? v : null));\n\n// Horizontal dashed capacity-tier lines (constant value across all hours)\nconst cap520 = Array(N).fill(520);  // base / intermediate boundary\nconst cap900 = Array(N).fill(900);  // intermediate / peak boundary\n\n// Rgba helper — avoids 8-char hex, safe in all Chromium builds\nconst rgba = (hex, a) => {\n  const r = parseInt(hex.slice(1, 3), 16);\n  const g = parseInt(hex.slice(3, 5), 16);\n  const b = parseInt(hex.slice(5, 7), 16);\n  return `rgba(${r},${g},${b},${a})`;\n};\n\n// --- Mount ---\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Chart ---\nconst title = \"line-load-duration · javascript · chartjs · anyplot.ai\";\n\nnew Chart(canvas, {\n  type: \"line\",\n  data: {\n    labels: hours,\n    datasets: [\n      // Region fills — base first so palette[0] = #009E73 is the first dataset\n      {\n        label: \"Base Load\",\n        data: fillBase,\n        backgroundColor: rgba(t.palette[0], 0.28),\n        borderColor: \"transparent\",\n        borderWidth: 0,\n        fill: \"origin\",\n        pointRadius: 0,\n        spanGaps: false,\n        tension: 0,\n      },\n      {\n        label: \"Intermediate Load\",\n        data: fillInter,\n        backgroundColor: rgba(t.palette[2], 0.28),\n        borderColor: \"transparent\",\n        borderWidth: 0,\n        fill: \"origin\",\n        pointRadius: 0,\n        spanGaps: false,\n        tension: 0,\n      },\n      {\n        label: \"Peak Load\",\n        data: fillPeak,\n        backgroundColor: rgba(t.palette[4], 0.28),  // matte red — semantic: peak/critical\n        borderColor: \"transparent\",\n        borderWidth: 0,\n        fill: \"origin\",\n        pointRadius: 0,\n        spanGaps: false,\n        tension: 0,\n      },\n      // Horizontal capacity-tier dashed lines\n      {\n        label: \"Base Capacity (520 MW)\",\n        data: cap520,\n        borderColor: t.palette[0],\n        borderWidth: 1.5,\n        borderDash: [10, 6],\n        pointRadius: 0,\n        fill: false,\n        tension: 0,\n      },\n      {\n        label: \"Intermediate Capacity (900 MW)\",\n        data: cap900,\n        borderColor: t.palette[4],\n        borderWidth: 1.5,\n        borderDash: [10, 6],\n        pointRadius: 0,\n        fill: false,\n        tension: 0,\n      },\n      // Main load duration curve\n      {\n        label: \"Hourly Load\",\n        data: loads,\n        borderColor: t.ink,\n        borderWidth: 2.5,\n        pointRadius: 0,\n        fill: false,\n        tension: 0.25,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    plugins: {\n      title: {\n        display: true,\n        text: title,\n        color: t.ink,\n        font: { size: 22, weight: \"500\" },\n        padding: { top: 4, bottom: 20 },\n      },\n      legend: {\n        display: true,\n        labels: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          // Show only capacity lines + main load curve; hide fill-only datasets\n          filter: (item) => item.datasetIndex >= 3,\n          usePointStyle: true,\n          pointStyleWidth: 32,\n          padding: 22,\n        },\n      },\n    },\n    scales: {\n      x: {\n        title: {\n          display: true,\n          text: \"Duration (hours)\",\n          color: t.ink,\n          font: { size: 16, weight: \"500\" },\n          padding: { top: 10 },\n        },\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 13 },\n          maxTicksLimit: 10,\n          callback: (val, idx) => `${hours[idx] !== undefined ? hours[idx].toLocaleString() : val}h`,\n        },\n        grid: { color: t.grid },\n      },\n      y: {\n        title: {\n          display: true,\n          text: \"Electrical Load (MW)\",\n          color: t.ink,\n          font: { size: 16, weight: \"500\" },\n          padding: { bottom: 10 },\n        },\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 13 },\n          callback: (v) => `${v} MW`,\n          maxTicksLimit: 8,\n        },\n        grid: { color: t.grid },\n        min: 0,\n        max: 1350,\n      },\n    },\n    layout: {\n      padding: { left: 10, right: 40, top: 10, bottom: 10 },\n    },\n  },\n  plugins: [\n    {\n      id: \"regionAnnotations\",\n      afterDraw(chart) {\n        const ctx = chart.ctx;\n        const { chartArea, scales } = chart;\n        const xScale = scales.x;\n        const yScale = scales.y;\n\n        ctx.save();\n\n        // Clip to chart area to keep annotations inside the plot\n        ctx.beginPath();\n        ctx.rect(\n          chartArea.left,\n          chartArea.top,\n          chartArea.right - chartArea.left,\n          chartArea.bottom - chartArea.top\n        );\n        ctx.clip();\n\n        // Region label pills (centered in each time + load range)\n        const regions = [\n          { text: \"PEAK\",         dataX: 440,  dataY: 1060, color: t.palette[4] },\n          { text: \"INTERMEDIATE\", dataX: 3070, dataY:  730, color: t.palette[2] },\n          { text: \"BASE LOAD\",    dataX: 7000, dataY:  462, color: t.palette[0] },\n        ];\n\n        ctx.font = \"bold 15px system-ui, -apple-system, sans-serif\";\n        ctx.textAlign = \"center\";\n        ctx.textBaseline = \"middle\";\n\n        regions.forEach(({ text, dataX, dataY, color }) => {\n          // Category scale uses indices (0–875), not raw hour values\n          const px = xScale.getPixelForValue(Math.round(dataX / 10));\n          const py = yScale.getPixelForValue(dataY);\n          const m = ctx.measureText(text);\n          const padX = 10;\n          const bw = m.width + padX * 2;\n          const bh = 30;\n\n          ctx.fillStyle = t.elevatedBg;\n          ctx.beginPath();\n          ctx.roundRect(px - bw / 2, py - bh / 2, bw, bh, 4);\n          ctx.fill();\n\n          ctx.fillStyle = color;\n          ctx.fillText(text, px, py);\n        });\n\n        ctx.restore();\n\n        // Energy annotation — upper-right, outside clip\n        ctx.save();\n\n        const annotText = `Total Annual Energy: ${totalGWh.toLocaleString()} GWh`;\n        ctx.font = \"500 13px system-ui, -apple-system, sans-serif\";\n        ctx.textAlign = \"right\";\n        ctx.textBaseline = \"middle\";\n\n        const am = ctx.measureText(annotText);\n        const apX = 14;\n        const apY = 8;\n        const abw = am.width + apX * 2;\n        const abh = 34;\n        const ax = chartArea.right - 16;\n        const ay = chartArea.top + 24;\n\n        ctx.fillStyle = t.elevatedBg;\n        ctx.strokeStyle = t.grid;\n        ctx.lineWidth = 1;\n        ctx.beginPath();\n        ctx.roundRect(ax - abw, ay - abh / 2, abw, abh, 4);\n        ctx.fill();\n        ctx.stroke();\n\n        ctx.fillStyle = t.ink;\n        ctx.fillText(annotText, ax - apX, ay);\n\n        ctx.restore();\n      },\n    },\n  ],\n});\n"}