{"spec_id":"line-timeseries","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// line-timeseries: Time Series Line Plot\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 92/100 | Created: 2026-09-05\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Daily closing price of a fictional tech stock across trading days of 2024.\n// A tiny fixed-seed LCG stands in for a seeded RNG (the browser has none).\nfunction makeLcg(seed) {\n  let state = seed >>> 0;\n  return () => {\n    state = (state * 1664525 + 1013904223) >>> 0;\n    return state / 4294967296;\n  };\n}\nconst rand = makeLcg(42);\n\nconst dates = [];\nconst prices = [];\nlet price = 182;\nconst cursor = new Date(2024, 0, 2);\nwhile (dates.length < 252) {\n  const weekday = cursor.getDay();\n  if (weekday !== 0 && weekday !== 6) {\n    dates.push(new Date(cursor));\n    const shock = (rand() - 0.5) * 6;\n    const drift = 0.15;\n    price = Math.max(20, price + drift + shock);\n    prices.push(Math.round(price * 100) / 100);\n  }\n  cursor.setDate(cursor.getDate() + 1);\n}\n\nconst dateLabels = dates.map((d) =>\n  d.toLocaleDateString(\"en-US\", { year: \"numeric\", month: \"short\", day: \"numeric\" })\n);\n\n// Highlight the year's peak close — a focal point for an otherwise flat line.\nconst peakIndex = prices.indexOf(Math.max(...prices));\n\n// Brand-green fill fading to transparent, built from the chart's own canvas\n// context so it scales with the actual plot area (Chart.js scriptable option).\nfunction hexToRgba(hex, alpha) {\n  const n = parseInt(hex.slice(1), 16);\n  return `rgba(${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}, ${alpha})`;\n}\nfunction areaGradient({ chart }) {\n  const { ctx, chartArea } = chart;\n  if (!chartArea) return hexToRgba(t.palette[0], 0);\n  const gradient = ctx.createLinearGradient(0, chartArea.top, 0, chartArea.bottom);\n  gradient.addColorStop(0, hexToRgba(t.palette[0], 0.32));\n  gradient.addColorStop(1, hexToRgba(t.palette[0], 0));\n  return gradient;\n}\n\n// Smart tick selection: one tick per calendar month, label carries the year\n// only at a year boundary (or the very first tick) — Chart.js has no bundled\n// date adapter, so the \"smart formatting\" happens here rather than via a\n// `type: 'time'` scale.\nconst monthTickIndices = new Set();\ndates.forEach((d, i) => {\n  if (i === 0 || d.getMonth() !== dates[i - 1].getMonth()) monthTickIndices.add(i);\n});\n\n// --- Mount -------------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Chart ---------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"line\",\n  data: {\n    labels: dateLabels,\n    datasets: [\n      {\n        label: \"Closing Price ($)\",\n        data: prices,\n        borderColor: t.palette[0],\n        backgroundColor: areaGradient,\n        borderWidth: 3,\n        pointRadius: (ctx) => (ctx.dataIndex === peakIndex ? 6 : 0),\n        pointHoverRadius: 4,\n        pointBackgroundColor: t.palette[0],\n        pointBorderColor: t.pageBg,\n        pointBorderWidth: 2,\n        fill: true,\n        tension: 0,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    layout: { padding: { top: 8, right: 24, bottom: 8, left: 8 } },\n    plugins: {\n      title: {\n        display: true,\n        text: \"line-timeseries · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22 },\n        padding: { bottom: 20 },\n      },\n      legend: { display: false },\n      tooltip: {\n        callbacks: {\n          title: (items) => dateLabels[items[0].dataIndex],\n          label: (item) =>\n            item.dataIndex === peakIndex\n              ? `Closing Price: $${item.parsed.y.toFixed(2)} (year high)`\n              : `Closing Price: $${item.parsed.y.toFixed(2)}`,\n        },\n      },\n    },\n    scales: {\n      x: {\n        type: \"category\",\n        afterBuildTicks: (axis) => {\n          axis.ticks = axis.ticks.filter((tick) => monthTickIndices.has(tick.value));\n        },\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          callback: (value) => {\n            const d = dates[value];\n            const isYearStart = value === 0 || d.getMonth() === 0;\n            return isYearStart\n              ? d.toLocaleDateString(\"en-US\", { month: \"short\", year: \"numeric\" })\n              : d.toLocaleDateString(\"en-US\", { month: \"short\" });\n          },\n        },\n        grid: { color: t.grid },\n        title: { display: true, text: \"Date\", color: t.ink, font: { size: 16 } },\n      },\n      y: {\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          callback: (value) => `$${value}`,\n        },\n        grid: { color: t.grid },\n        title: { display: true, text: \"Closing Price ($)\", color: t.ink, font: { size: 16 } },\n      },\n    },\n  },\n});\n"}