{"spec_id":"lightcurve-transit","library":"muix","language":"javascript","code":"// anyplot.ai\n// lightcurve-transit: Astronomical Light Curve\n// Library: muix 9.16.0 | JavaScript 22.23.3\n// Quality: 86/100 | Updated: 2026-10-24\n\nimport { ChartsContainer } from \"@mui/x-charts/ChartsContainer\";\nimport { LinePlot } from \"@mui/x-charts/LineChart\";\nimport { ChartsXAxis } from \"@mui/x-charts/ChartsXAxis\";\nimport { ChartsYAxis } from \"@mui/x-charts/ChartsYAxis\";\nimport { ChartsGrid } from \"@mui/x-charts/ChartsGrid\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\nimport { useXScale, useYScale } from \"@mui/x-charts/hooks\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// Constants\nconst FONT = '\"Plus Jakarta Sans\", \"Inter\", -apple-system, BlinkMacSystemFont, sans-serif';\nconst MONO = '\"JetBrains Mono\", \"SFMono-Regular\", Menlo, monospace';\nconst PHASE_AXIS_LABEL = \"Orbital phase\";\nconst BASELINE_FLUX = 1.0;\nconst Y_PAD_SHARE = 0.14;\n\n// Columns: set these to your data's column names\nconst TIME = \"Observation time (BJD - 2457000)\"; // role: time\nconst PERIOD = \"Orbital period (days)\"; // role: period\nconst FLUX = \"Relative flux\"; // role: flux\nconst FLUX_ERR = \"Flux uncertainty\"; // role: flux_err\nconst MODEL_FLUX = \"Transit model\"; // role: model_flux\n\n// Data: replace this block with your own records\nconst N_PTS = 280;\nconst ORBITAL_PERIOD_DAYS = 3.5247;\nconst EPOCH_BJD = 1325.41;\nconst TRANSIT_CENTER = 0.5;\nconst TRANSIT_HALF_DUR = 0.018;\nconst TRANSIT_DEPTH = 0.01;\nconst NOISE_SIGMA = 0.0015;\n\nlet lcgState = 42 >>> 0;\nconst data: Array<{\n  [TIME]: number;\n  [PERIOD]: number;\n  [FLUX]: number;\n  [FLUX_ERR]: number;\n  [MODEL_FLUX]: number;\n}> = [];\n\nfor (let i = 0; i < N_PTS; i += 1) {\n  const ph = i / N_PTS;\n  const orbitCycle = i === 0 ? 0 : i % 4;\n  const timeBjd = EPOCH_BJD + (orbitCycle + ph) * ORBITAL_PERIOD_DAYS;\n\n  const dt = Math.abs(ph - TRANSIT_CENTER);\n  let modFlux = BASELINE_FLUX;\n  if (dt < TRANSIT_HALF_DUR) {\n    const u = 1 - dt / TRANSIT_HALF_DUR;\n    const smooth = u * u * (3 - 2 * u);\n    modFlux = BASELINE_FLUX - TRANSIT_DEPTH * smooth;\n  }\n\n  lcgState = (Math.imul(1664525, lcgState) + 1013904223) >>> 0;\n  const r1 = lcgState / 0x100000000;\n  const obsFlux = modFlux + (r1 * 2 - 1) * NOISE_SIGMA * 2.5;\n\n  lcgState = (Math.imul(1664525, lcgState) + 1013904223) >>> 0;\n  const r2 = lcgState / 0x100000000;\n  const fluxErr = NOISE_SIGMA * (1 + r2 * 0.35);\n\n  data.push({\n    [TIME]: Number(timeBjd.toFixed(6)),\n    [PERIOD]: ORBITAL_PERIOD_DAYS,\n    [FLUX]: Number(obsFlux.toFixed(6)),\n    [FLUX_ERR]: Number(fluxErr.toFixed(6)),\n    [MODEL_FLUX]: Number(modFlux.toFixed(6)),\n  });\n}\n\n// Plot: reads only data and the constants above\nconst rawTimes = data.map((d) => d[TIME]);\nconst minTime = Math.min(...rawTimes);\nconst maxTime = Math.max(...rawTimes);\nconst isAlreadyPhase = minTime >= 0 && maxTime <= 1;\n\nconst foldedRows = data\n  .map((row) => {\n    const p = row[PERIOD];\n    const phase = isAlreadyPhase\n      ? row[TIME]\n      : (((row[TIME] - minTime) / p) % 1 + 1) % 1;\n    return {\n      ...row,\n      phase,\n    };\n  })\n  .sort((a, b) => a.phase - b.phase);\n\nconst phases = foldedRows.map((d) => d.phase);\nconst modelFlux = foldedRows.map((d) => d[MODEL_FLUX]);\nconst orbitalPeriod = foldedRows[0][PERIOD];\n\nconst minPhase = Math.min(...phases);\nconst maxPhase = Math.max(...phases);\nconst lowerExtents = foldedRows.map((d) => d[FLUX] - d[FLUX_ERR]);\nconst upperExtents = foldedRows.map((d) => d[FLUX] + d[FLUX_ERR]);\nconst minDataFlux = Math.min(...lowerExtents, ...modelFlux);\nconst maxDataFlux = Math.max(...upperExtents, ...modelFlux);\nconst fluxSpan = maxDataFlux - minDataFlux;\nconst FLUX_MIN = minDataFlux - fluxSpan * Y_PAD_SHARE;\nconst FLUX_MAX = maxDataFlux + fluxSpan * Y_PAD_SHARE;\n\nconst minModelFlux = Math.min(...modelFlux);\nconst transitDepthPct = ((BASELINE_FLUX - minModelFlux) * 100).toFixed(2);\nconst inTransitPhases = foldedRows\n  .filter((r) => r[MODEL_FLUX] < BASELINE_FLUX - 1e-5)\n  .map((r) => r.phase);\nconst transitPhaseSpan =\n  inTransitPhases.length > 1\n    ? Math.max(...inTransitPhases) - Math.min(...inTransitPhases)\n    : 0;\nconst transitDurationHours = (\n  transitPhaseSpan *\n  orbitalPeriod *\n  24\n).toFixed(2);\nconst timeSpanDays = (maxTime - minTime).toFixed(1);\n\nconst OBS_COLOR = t.palette[0];\nconst MODEL_COLOR = t.palette[1];\n\nfunction TransitDataLayer() {\n  const xScale = useXScale(\"phase\") as (v: number) => number;\n  const yScale = useYScale(\"flux\") as (v: number) => number;\n\n  return (\n    <g>\n      {foldedRows.map((row, i) => {\n        const x = xScale(row.phase);\n        const yTop = yScale(row[FLUX] + row[FLUX_ERR]);\n        const yBot = yScale(row[FLUX] - row[FLUX_ERR]);\n        return (\n          <line\n            key={`err-${i}`}\n            x1={x}\n            y1={yTop}\n            x2={x}\n            y2={yBot}\n            stroke={OBS_COLOR}\n            strokeWidth={1.25}\n            opacity={0.5}\n          />\n        );\n      })}\n      {foldedRows.map((row, i) => (\n        <circle\n          key={`pt-${i}`}\n          cx={xScale(row.phase)}\n          cy={yScale(row[FLUX])}\n          r={3.5}\n          fill={OBS_COLOR}\n          opacity={0.84}\n        />\n      ))}\n    </g>\n  );\n}\n\nexport default function Chart() {\n  const { width, height } = window.ANYPLOT_SIZE;\n  const headerHeight = 84;\n  const chartHeight = height - headerHeight;\n\n  return (\n    <div\n      style={{\n        width,\n        height,\n        background: t.pageBg,\n        color: t.ink,\n        fontFamily: FONT,\n        display: \"flex\",\n        flexDirection: \"column\",\n        boxSizing: \"border-box\",\n      }}\n    >\n      <div\n        style={{\n          height: headerHeight,\n          padding: \"14px 44px 4px 116px\",\n          display: \"flex\",\n          alignItems: \"center\",\n          justifyContent: \"space-between\",\n          boxSizing: \"border-box\",\n        }}\n      >\n        <div>\n          <div\n            style={{\n              fontSize: 22,\n              fontWeight: 700,\n              color: t.ink,\n              lineHeight: 1.2,\n            }}\n          >\n            lightcurve-transit · javascript · muix · anyplot.ai\n          </div>\n          <div\n            style={{\n              fontSize: 14,\n              color: t.inkSoft,\n              marginTop: 4,\n              fontFamily: MONO,\n            }}\n          >\n            {foldedRows.length} phase-folded observations ({timeSpanDays} d\n            span, P = {orbitalPeriod.toFixed(4)} d) · transit depth{\" \"}\n            {transitDepthPct}% ({transitDurationHours} h duration)\n          </div>\n        </div>\n\n        <div\n          style={{\n            display: \"flex\",\n            alignItems: \"center\",\n            gap: 24,\n            fontSize: 15,\n            color: t.ink,\n          }}\n        >\n          <div style={{ display: \"flex\", alignItems: \"center\", gap: 8 }}>\n            <span\n              style={{\n                width: 11,\n                height: 11,\n                borderRadius: \"50%\",\n                background: OBS_COLOR,\n                display: \"inline-block\",\n              }}\n            />\n            <span>Observed {FLUX} (±1σ)</span>\n          </div>\n          <div style={{ display: \"flex\", alignItems: \"center\", gap: 8 }}>\n            <span\n              style={{\n                width: 24,\n                height: 3,\n                borderRadius: 2,\n                background: MODEL_COLOR,\n                display: \"inline-block\",\n              }}\n            />\n            <span>{MODEL_FLUX}</span>\n          </div>\n        </div>\n      </div>\n\n      <ChartsContainer\n        width={width}\n        height={chartHeight}\n        margin={{ left: 20, right: 40, top: 20, bottom: 20 }}\n        skipAnimation\n        series={[\n          {\n            type: \"line\",\n            id: \"model\",\n            xAxisId: \"phase\",\n            yAxisId: \"flux\",\n            data: modelFlux,\n            label: MODEL_FLUX,\n            color: MODEL_COLOR,\n            showMark: false,\n            curve: \"monotoneX\",\n          },\n        ]}\n        xAxis={[\n          {\n            id: \"phase\",\n            data: phases,\n            scaleType: \"linear\",\n            min: minPhase,\n            max: maxPhase,\n            height: 60,\n            label: PHASE_AXIS_LABEL,\n            labelStyle: { fontSize: 16, fontWeight: 600, fill: t.ink },\n            tickLabelStyle: {\n              fontSize: 14,\n              fill: t.inkSoft,\n              fontFamily: MONO,\n            },\n            valueFormatter: (v: number) => v.toFixed(2),\n          },\n        ]}\n        yAxis={[\n          {\n            id: \"flux\",\n            min: FLUX_MIN,\n            max: FLUX_MAX,\n            width: 96,\n            label: FLUX,\n            labelStyle: { fontSize: 16, fontWeight: 600, fill: t.ink },\n            tickLabelStyle: {\n              fontSize: 14,\n              fill: t.inkSoft,\n              fontFamily: MONO,\n            },\n            valueFormatter: (v: number) => v.toFixed(3),\n          },\n        ]}\n        sx={{\n          \"& .MuiChartsGrid-line\": { stroke: t.grid },\n        }}\n      >\n        <ChartsGrid horizontal />\n        <ChartsReferenceLine\n          y={BASELINE_FLUX}\n          axisId=\"flux\"\n          lineStyle={{\n            stroke: t.inkSoft,\n            strokeDasharray: \"5 4\",\n            strokeWidth: 1.4,\n          }}\n        />\n        <TransitDataLayer />\n        <LinePlot skipAnimation />\n        <ChartsXAxis axisId=\"phase\" />\n        <ChartsYAxis axisId=\"flux\" />\n      </ChartsContainer>\n    </div>\n  );\n}\n"}