{"spec_id":"dashboard-metrics-tiles","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// dashboard-metrics-tiles: Real-Time Dashboard Tiles\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 92/100 | Updated: 2026-09-02\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Tiny fixed-seed LCG — the browser has no seeded RNG.\nlet seed = 42;\nfunction rand() {\n  seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n}\n\n// Builds a short trend history that drifts toward `drift` each step, then\n// derives the tile's headline value from the trend's final point so the\n// sparkline and the big number always agree.\nfunction buildMetric(\n  name,\n  unit,\n  decimals,\n  base,\n  spread,\n  drift,\n  points,\n  changePercent,\n  higherIsBetter,\n  status,\n) {\n  const data = [];\n  let v = base;\n  for (let i = 0; i < points; i++) {\n    v = Math.max(0, v + (rand() - 0.5) * spread + drift);\n    data.push(v);\n  }\n  const value =\n    decimals > 0\n      ? +data[data.length - 1].toFixed(decimals)\n      : Math.round(data[data.length - 1]);\n  return { name, unit, value, data, changePercent, higherIsBetter, status };\n}\n\nconst metrics = [\n  buildMetric(\"CPU Usage\", \"%\", 0, 50, 6, -0.35, 16, -5.2, false, \"good\"),\n  buildMetric(\"Memory\", \"%\", 0, 64, 5, 0.55, 16, 8.4, false, \"warning\"),\n  buildMetric(\n    \"Response Time\",\n    \"ms\",\n    0,\n    140,\n    12,\n    -1.3,\n    16,\n    -15.3,\n    false,\n    \"good\",\n  ),\n  buildMetric(\n    \"Error Rate\",\n    \"%\",\n    1,\n    2.2,\n    0.6,\n    0.12,\n    16,\n    42.1,\n    false,\n    \"critical\",\n  ),\n  buildMetric(\"Requests/sec\", \"\", 0, 1700, 90, 8, 16, 6.7, true, \"good\"),\n  buildMetric(\"Active Users\", \"\", 0, 9500, 220, -18, 16, -2.1, true, \"warning\"),\n];\n\n// good/warning/critical are a traffic-light convention readers already expect —\n// semantic exception from the Imprint palette rather than ordinal position.\nconst STATUS_COLOR = {\n  good: t.palette[0],\n  warning: t.amber,\n  critical: t.palette[4],\n};\n\n// null = flat (no favorable/unfavorable read); otherwise \"does this change help\".\nfunction favorableColor(m) {\n  if (m.changePercent === 0) return t.inkSoft;\n  const favorable = m.changePercent > 0 === m.higherIsBetter;\n  return favorable ? t.palette[0] : t.palette[4];\n}\n\n// Chart.js plugin: marks the sparkline's last point with a filled \"now\" dot,\n// ringed in the card color so it reads clearly against the line/fill.\nconst endpointDotPlugin = {\n  id: \"endpointDot\",\n  afterDatasetsDraw(chart) {\n    const meta = chart.getDatasetMeta(0);\n    const point = meta.data[meta.data.length - 1];\n    if (!point) return;\n    const { ctx } = chart;\n    ctx.save();\n    ctx.beginPath();\n    ctx.arc(point.x, point.y, 4, 0, Math.PI * 2);\n    ctx.fillStyle = chart.data.datasets[0].borderColor;\n    ctx.fill();\n    ctx.lineWidth = 2;\n    ctx.strokeStyle = t.elevatedBg;\n    ctx.stroke();\n    ctx.restore();\n  },\n};\n\n// --- Mount -------------------------------------------------------------------\nconst root = document.getElementById(\"container\");\nroot.style.display = \"flex\";\nroot.style.flexDirection = \"column\";\nroot.style.padding = \"36px 44px 40px\";\nroot.style.gap = \"22px\";\n\nconst heading = document.createElement(\"div\");\nheading.textContent =\n  \"dashboard-metrics-tiles · javascript · chartjs · anyplot.ai\";\nheading.style.fontSize = \"22px\";\nheading.style.fontWeight = \"600\";\nheading.style.color = t.ink;\nroot.appendChild(heading);\n\nconst grid = document.createElement(\"div\");\ngrid.style.flex = \"1\";\ngrid.style.display = \"grid\";\ngrid.style.gridTemplateColumns = \"repeat(3, 1fr)\";\ngrid.style.gridTemplateRows = \"repeat(2, 1fr)\";\ngrid.style.gap = \"22px\";\nroot.appendChild(grid);\n\n// --- Tiles ---------------------------------------------------------------\nmetrics.forEach((m) => {\n  const tile = document.createElement(\"div\");\n  tile.style.background = t.elevatedBg;\n  tile.style.borderRadius = \"16px\";\n  tile.style.padding = \"24px 28px\";\n  tile.style.display = \"flex\";\n  tile.style.flexDirection = \"column\";\n  tile.style.justifyContent = \"center\";\n  tile.style.gap = \"14px\";\n  grid.appendChild(tile);\n\n  const labelRow = document.createElement(\"div\");\n  labelRow.style.display = \"flex\";\n  labelRow.style.alignItems = \"center\";\n  labelRow.style.gap = \"10px\";\n  tile.appendChild(labelRow);\n\n  const dot = document.createElement(\"span\");\n  dot.style.width = \"11px\";\n  dot.style.height = \"11px\";\n  dot.style.borderRadius = \"50%\";\n  dot.style.background = STATUS_COLOR[m.status];\n  dot.style.flex = \"none\";\n  labelRow.appendChild(dot);\n\n  const label = document.createElement(\"span\");\n  label.textContent = m.name;\n  label.style.fontSize = \"16px\";\n  label.style.fontWeight = \"500\";\n  label.style.color = t.inkSoft;\n  labelRow.appendChild(label);\n\n  const valueRow = document.createElement(\"div\");\n  valueRow.style.display = \"flex\";\n  valueRow.style.alignItems = \"baseline\";\n  valueRow.style.gap = \"14px\";\n  tile.appendChild(valueRow);\n\n  const value = document.createElement(\"span\");\n  value.textContent = `${m.value}${m.unit}`;\n  value.style.fontSize = \"46px\";\n  value.style.fontWeight = \"700\";\n  value.style.color = t.ink;\n  valueRow.appendChild(value);\n\n  const arrow = m.changePercent > 0 ? \"▲\" : m.changePercent < 0 ? \"▼\" : \"–\";\n  const change = document.createElement(\"span\");\n  change.textContent = `${arrow} ${Math.abs(m.changePercent).toFixed(1)}%`;\n  change.style.fontSize = \"18px\";\n  change.style.fontWeight = \"600\";\n  change.style.color = favorableColor(m);\n  valueRow.appendChild(change);\n\n  const sparkWrap = document.createElement(\"div\");\n  sparkWrap.style.height = \"84px\";\n  tile.appendChild(sparkWrap);\n\n  const canvas = document.createElement(\"canvas\");\n  sparkWrap.appendChild(canvas);\n\n  new Chart(canvas, {\n    type: \"line\",\n    data: {\n      labels: m.data.map((_, i) => i),\n      datasets: [\n        {\n          data: m.data,\n          borderColor: STATUS_COLOR[m.status],\n          // Canvas gradient (fades to transparent) instead of a flat alpha\n          // fill — a genuine canvas/Chart.js scriptable-option feature.\n          backgroundColor: (ctx) => {\n            const { chartArea, ctx: c } = ctx.chart;\n            if (!chartArea) return `${STATUS_COLOR[m.status]}26`;\n            const gradient = c.createLinearGradient(\n              0,\n              chartArea.top,\n              0,\n              chartArea.bottom,\n            );\n            gradient.addColorStop(0, `${STATUS_COLOR[m.status]}55`);\n            gradient.addColorStop(1, `${STATUS_COLOR[m.status]}00`);\n            return gradient;\n          },\n          borderWidth: 2.5,\n          pointRadius: 0,\n          fill: true,\n          tension: 0.35,\n        },\n      ],\n    },\n    options: {\n      responsive: true,\n      maintainAspectRatio: false,\n      animation: false,\n      // Room for the endpoint dot, which would otherwise clip against the\n      // canvas edge since the last data point sits at the plot boundary.\n      layout: { padding: { top: 5, right: 5, bottom: 5 } },\n      plugins: { legend: { display: false }, tooltip: { enabled: false } },\n      scales: { x: { display: false }, y: { display: false } },\n    },\n    plugins: [endpointDotPlugin],\n  });\n});\n"}