{"spec_id":"lightcurve-transit","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// lightcurve-transit: Astronomical Light Curve\n// Library: chartjs 4.4.7 | JavaScript 22.22.3\n// Quality: 91/100 | Created: 2026-06-20\n\nconst t = window.ANYPLOT_TOKENS;\n\n// Deterministic 31-bit LCG (seed=1337) — avoids Math.random()\nlet _seed = 1337;\nconst rand = () => {\n  _seed = (_seed * 214013 + 2531011) & 0x7fffffff;\n  return _seed / 0x80000000;\n};\n\n// Box-Muller transform for Gaussian noise (uses deterministic rand())\nconst randn = () => {\n  const u1 = Math.max(rand(), 1e-10);\n  const u2 = rand();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n};\n\n// Simplified quadratic limb-darkened transit profile\nconst T_DEPTH = 0.0108;  // 1.08% dip\nconst T_HALF  = 0.042;   // half-duration in phase units\nconst T_ING   = 0.015;   // ingress half-width\n\nconst transitFlux = ph => {\n  const a = Math.abs(ph);\n  if (a >= T_HALF) return 1.0;\n  if (a <= T_HALF - T_ING) {\n    return 1.0 - T_DEPTH * (1.0 - 0.07 * ((a / (T_HALF - T_ING)) ** 2));\n  }\n  const u = (a - (T_HALF - T_ING)) / T_ING;\n  return 1.0 - T_DEPTH * (1.0 - u * u);\n};\n\n// 320 phase-folded TESS-like photometric measurements with Gaussian noise\nconst N_OBS = 320;\nconst obsData = Array.from({ length: N_OBS }, (_, i) => {\n  const ph  = Math.max(-0.499, Math.min(0.499, -0.5 + i / N_OBS + (rand() - 0.5) * 0.5 / N_OBS));\n  const err = 0.0016 + rand() * 0.0012;\n  return { x: ph, y: transitFlux(ph) + randn() * err, err };\n}).sort((a, b) => a.x - b.x);\n\n// 800-point smooth model curve (no noise)\nconst modelData = Array.from({ length: 800 }, (_, i) => ({\n  x: -0.5 + i / 799,\n  y: transitFlux(-0.5 + i / 799),\n}));\n\n// Plugin: canvas background + transit region shading + per-point error bars\nChart.register({\n  id: 'lightcurveExtras',\n  beforeDraw({ ctx, width, height }) {\n    ctx.save();\n    ctx.fillStyle = t.pageBg;\n    ctx.fillRect(0, 0, width, height);\n    ctx.restore();\n  },\n  beforeDatasetsDraw(chart) {\n    const { ctx, chartArea, scales: { x: xSc, y: ySc } } = chart;\n    ctx.save();\n    ctx.beginPath();\n    ctx.rect(chartArea.left, chartArea.top, chartArea.width, chartArea.height);\n    ctx.clip();\n\n    // Semi-transparent transit window shading (phase ±T_HALF)\n    const x1 = xSc.getPixelForValue(-T_HALF);\n    const x2 = xSc.getPixelForValue(T_HALF);\n    ctx.fillStyle = t.palette[0] + '1a';\n    ctx.fillRect(x1, chartArea.top, x2 - x1, chartArea.height);\n\n    // Dashed baseline at out-of-transit flux = 1.0\n    const y0 = ySc.getPixelForValue(1.0);\n    ctx.strokeStyle = t.inkSoft + '60';\n    ctx.lineWidth = 1;\n    ctx.setLineDash([4, 4]);\n    ctx.beginPath();\n    ctx.moveTo(chartArea.left, y0);\n    ctx.lineTo(chartArea.right, y0);\n    ctx.stroke();\n\n    ctx.restore();\n  },\n  afterDatasetsDraw(chart) {\n    const { ctx, chartArea, scales: { y: ySc } } = chart;\n    const meta = chart.getDatasetMeta(0);\n    if (meta.hidden) return;\n\n    ctx.save();\n    ctx.beginPath();\n    ctx.rect(chartArea.left, chartArea.top, chartArea.width, chartArea.height);\n    ctx.clip();\n    ctx.strokeStyle = t.inkSoft + 'a0';\n    ctx.lineWidth   = 1.5;\n    const CAP = 3.5;\n\n    obsData.forEach((d, i) => {\n      const pt = meta.data[i];\n      if (!pt) return;\n      const px  = pt.x;\n      const py1 = ySc.getPixelForValue(d.y + d.err);\n      const py2 = ySc.getPixelForValue(d.y - d.err);\n      ctx.beginPath(); ctx.moveTo(px, py1);       ctx.lineTo(px, py2);       ctx.stroke();\n      ctx.beginPath(); ctx.moveTo(px - CAP, py1); ctx.lineTo(px + CAP, py1); ctx.stroke();\n      ctx.beginPath(); ctx.moveTo(px - CAP, py2); ctx.lineTo(px + CAP, py2); ctx.stroke();\n    });\n\n    ctx.restore();\n  },\n});\n\n// Mount canvas into harness-provided #container\nconst canvas = document.createElement('canvas');\ndocument.getElementById('container').appendChild(canvas);\n\nconst TITLE    = 'lightcurve-transit · javascript · chartjs · anyplot.ai';\nconst titleSize = Math.max(16, Math.round(22 * Math.min(1.0, 67 / TITLE.length)));\n\nnew Chart(canvas, {\n  type: 'scatter',\n  data: {\n    datasets: [\n      {\n        label: 'Photometric data',\n        data: obsData,\n        type: 'scatter',\n        backgroundColor: t.palette[0] + 'bb',\n        borderColor:     t.palette[0],\n        borderWidth: 0.5,\n        pointRadius: 3,\n        pointHoverRadius: 4,\n        order: 2,\n      },\n      {\n        label: 'Transit model',\n        data: modelData,\n        type: 'line',\n        borderColor:     t.palette[1],\n        backgroundColor: 'transparent',\n        borderWidth: 3.5,\n        pointRadius: 0,\n        tension: 0.2,\n        order: 1,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    layout: { padding: { right: 20, top: 10 } },\n    plugins: {\n      title: {\n        display: true,\n        text: TITLE,\n        color: t.ink,\n        font: { size: titleSize, weight: '500' },\n        padding: { bottom: 16 },\n      },\n      legend: {\n        position: 'top',\n        align: 'end',\n        labels: {\n          color: t.ink,\n          font: { size: 14 },\n          usePointStyle: true,\n          padding: 20,\n        },\n      },\n    },\n    scales: {\n      x: {\n        type: 'linear',\n        title: {\n          display: true,\n          text: 'Orbital Phase',\n          color: t.ink,\n          font: { size: 15 },\n        },\n        ticks: { color: t.inkSoft, font: { size: 13 } },\n        grid: { color: t.grid },\n        border: { display: false },\n        min: -0.5,\n        max: 0.5,\n      },\n      y: {\n        type: 'linear',\n        title: {\n          display: true,\n          text: 'Relative Flux',\n          color: t.ink,\n          font: { size: 15 },\n        },\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 13 },\n          callback: v => v.toFixed(3),\n        },\n        grid: { color: t.grid },\n        border: { display: false },\n      },\n    },\n  },\n});\n"}