{"spec_id":"line-annotated-events","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// line-annotated-events: Annotated Line Plot with Event Markers\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 fixed-seed LCG) -------------------------\n// Daily closing price of a fictional tech company over one trading year, with\n// quarterly earnings and other corporate milestones marked as events.\nlet seed = 20260905;\nfunction lcg() {\n  seed = (seed * 1664525 + 1013904223) % 4294967296;\n  return seed / 4294967296;\n}\n\nconst MONTHS = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"];\nconst TRADING_DAYS = 252;\nconst labels = [];\nconst prices = [];\nlet price = 148;\nfor (let i = 0; i < TRADING_DAYS; i += 1) {\n  const month = Math.floor(i / 21);\n  const day = (i % 21) + 1;\n  labels.push(`${MONTHS[Math.min(month, 11)]} ${day}`);\n  price += (lcg() - 0.48) * 4.2;\n  price = Math.max(price, 60);\n  prices.push(Math.round(price * 100) / 100);\n}\n\nconst events = [\n  { index: 20, label: \"Product Launch\" },\n  { index: 47, label: \"Q1 Earnings\" },\n  { index: 95, label: \"Q2 Earnings\" },\n  { index: 128, label: \"Supply Chain Disruption\" },\n  { index: 150, label: \"Q3 Earnings\" },\n  { index: 205, label: \"Q4 Earnings\" },\n  { index: 228, label: \"Analyst Downgrade\" },\n];\n\nconst eventMarkers = labels.map(() => null);\nevents.forEach((event) => {\n  eventMarkers[event.index] = prices[event.index];\n});\n\nconst maxPrice = Math.max(...prices);\nconst minPrice = Math.min(...prices);\n\n// --- Mount -------------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Custom plugin: dashed vertical event lines + alternating labels --------\n// Chart.js core has no built-in annotation support; the harness only vendors\n// the core UMD bundle (no chartjs-plugin-annotation). Drawing directly on the\n// canvas via a plugin's afterDraw hook is a native, fully-supported Chart.js\n// extension mechanism, not a workaround.\nconst eventLinePlugin = {\n  id: \"eventLines\",\n  afterDraw(chart) {\n    const { ctx, chartArea, scales } = chart;\n    const xScale = scales.x;\n    const labelRows = 3;\n    const labelBandHeight = 18 + labelRows * 26;\n    ctx.save();\n    events.forEach((event, i) => {\n      const xPixel = xScale.getPixelForValue(event.index);\n      if (xPixel < chartArea.left || xPixel > chartArea.right) return;\n\n      // Line starts below the label band so dashes never cross the text.\n      ctx.beginPath();\n      ctx.setLineDash([8, 5]);\n      ctx.lineWidth = 2;\n      ctx.strokeStyle = t.ink;\n      ctx.globalAlpha = 0.55;\n      ctx.moveTo(xPixel, chartArea.top + labelBandHeight);\n      ctx.lineTo(xPixel, chartArea.bottom);\n      ctx.stroke();\n      ctx.setLineDash([]);\n      ctx.globalAlpha = 1;\n\n      const row = i % labelRows;\n      const labelY = chartArea.top + 18 + row * 26;\n      ctx.fillStyle = t.ink;\n      ctx.font = \"600 15px sans-serif\";\n      ctx.textAlign = \"center\";\n      ctx.fillText(event.label, xPixel, labelY);\n    });\n    ctx.restore();\n  },\n};\n\n// --- Chart -------------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"line\",\n  data: {\n    labels,\n    datasets: [\n      {\n        label: \"Closing Price\",\n        data: prices,\n        borderColor: t.palette[0],\n        backgroundColor: t.palette[0],\n        borderWidth: 3,\n        pointRadius: 0,\n        tension: 0.15,\n        order: 2,\n      },\n      {\n        label: \"Event\",\n        data: eventMarkers,\n        showLine: false,\n        pointStyle: \"triangle\",\n        pointRadius: 9,\n        backgroundColor: t.palette[1],\n        pointBackgroundColor: t.palette[1],\n        pointBorderColor: t.pageBg,\n        pointBorderWidth: 2,\n        order: 1,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    layout: {\n      padding: { top: 10 },\n    },\n    plugins: {\n      title: {\n        display: true,\n        text: \"line-annotated-events · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22 },\n      },\n      legend: {\n        labels: { color: t.ink, font: { size: 16 } },\n      },\n    },\n    scales: {\n      x: {\n        ticks: { color: t.inkSoft, font: { size: 13 }, autoSkip: true, maxTicksLimit: 13, maxRotation: 0 },\n        grid: { display: false },\n        title: { display: true, text: \"Trading Date\", color: t.ink, font: { size: 16 } },\n      },\n      y: {\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.grid },\n        title: { display: true, text: \"Closing Price (USD)\", color: t.ink, font: { size: 16 } },\n        min: Math.floor((minPrice - 8) / 10) * 10,\n        suggestedMax: Math.ceil((maxPrice + (maxPrice - minPrice) * 0.28) / 10) * 10,\n      },\n    },\n  },\n  plugins: [eventLinePlugin],\n});\n"}