{"spec_id":"lightcurve-transit","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nlightcurve-transit: Astronomical Light Curve\nLibrary: plotly 6.8.0 | Python 3.13.14\nQuality: 90/100 | Updated: 2026-06-20\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\n\n\n# Theme tokens (Imprint palette — see prompts/default-style-guide.md)\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\n# Imprint categorical palette\nBRAND = \"#009E73\"  # position 1 — observations (first series, always)\nMODEL_COLOR = \"#C475FD\"  # position 2 — transit model\n\n# Data\nnp.random.seed(42)\nn_points = 500\nphase = np.sort(np.random.uniform(0.0, 1.0, n_points))\n\n# Transit model parameters (quadratic limb darkening)\ntransit_center = 0.5\ntransit_duration = 0.06\ntransit_depth = 0.01\nu1, u2 = 0.4, 0.2\n\n# Compute model flux with limb-darkened transit shape\nmodel_flux = np.ones_like(phase)\nin_transit = np.abs(phase - transit_center) < transit_duration\nz = np.abs(phase[in_transit] - transit_center) / transit_duration\nmu = np.sqrt(1 - z**2)\nlimb_darkening = 1 - u1 * (1 - mu) - u2 * (1 - mu) ** 2\ntransit_profile = 1 - transit_depth * limb_darkening\nmodel_flux[in_transit] = transit_profile\n\n# Observed flux with noise\nflux_err = np.random.uniform(0.0008, 0.0020, n_points)\nflux = model_flux + np.random.normal(0, 1, n_points) * flux_err\n\n# Fine model curve for smooth overlay\nphase_model_fine = np.linspace(0, 1, 2000)\nmodel_fine = np.ones_like(phase_model_fine)\nin_transit_fine = np.abs(phase_model_fine - transit_center) < transit_duration\nz_fine = np.abs(phase_model_fine[in_transit_fine] - transit_center) / transit_duration\nmu_fine = np.sqrt(1 - z_fine**2)\nld_fine = 1 - u1 * (1 - mu_fine) - u2 * (1 - mu_fine) ** 2\nmodel_fine[in_transit_fine] = 1 - transit_depth * ld_fine\n\n# Plot\nfig = go.Figure()\n\nfig.add_trace(\n    go.Scatter(\n        x=phase,\n        y=flux,\n        mode=\"markers\",\n        name=\"Observations\",\n        marker={\"size\": 8, \"color\": BRAND, \"opacity\": 0.55, \"line\": {\"width\": 0.5, \"color\": PAGE_BG}},\n        error_y={\n            \"type\": \"data\",\n            \"array\": flux_err,\n            \"visible\": True,\n            \"color\": \"rgba(0,158,115,0.35)\",\n            \"thickness\": 1.5,\n            \"width\": 0,\n        },\n        hovertemplate=\"Phase: %{x:.4f}<br>Flux: %{y:.5f}<extra></extra>\",\n    )\n)\n\nfig.add_trace(\n    go.Scatter(\n        x=phase_model_fine,\n        y=model_fine,\n        mode=\"lines\",\n        name=\"Transit Model\",\n        line={\"color\": MODEL_COLOR, \"width\": 3},\n        hovertemplate=\"Phase: %{x:.4f}<br>Model: %{y:.5f}<extra></extra>\",\n    )\n)\n\n# Transit window highlight\nfig.add_vrect(\n    x0=transit_center - transit_duration,\n    x1=transit_center + transit_duration,\n    fillcolor=\"rgba(196,117,253,0.06)\",\n    line_width=0,\n    annotation_text=\"Transit Window\",\n    annotation_position=\"top\",\n    annotation_font={\"size\": 13, \"color\": INK_MUTED},\n)\n\n# Baseline reference line\nfig.add_hline(y=1.0, line_dash=\"dot\", line_color=GRID, line_width=1.5)\n\n# Transit depth annotation\nmin_model = 1 - transit_depth\nfig.add_annotation(\n    x=transit_center + transit_duration + 0.025,\n    y=(1.0 + min_model) / 2,\n    text=f\"Depth: {transit_depth * 100:.1f}%\",\n    showarrow=False,\n    font={\"size\": 16, \"color\": INK_SOFT},\n    bgcolor=ELEVATED_BG,\n    borderpad=4,\n)\n\n# Bracket lines for transit depth\nbracket_x = transit_center + transit_duration + 0.015\nfig.add_shape(type=\"line\", x0=bracket_x, x1=bracket_x, y0=1.0, y1=min_model, line={\"color\": INK_SOFT, \"width\": 1.2})\nfig.add_shape(\n    type=\"line\", x0=bracket_x - 0.005, x1=bracket_x + 0.005, y0=1.0, y1=1.0, line={\"color\": INK_SOFT, \"width\": 1.2}\n)\nfig.add_shape(\n    type=\"line\",\n    x0=bracket_x - 0.005,\n    x1=bracket_x + 0.005,\n    y0=min_model,\n    y1=min_model,\n    line={\"color\": INK_SOFT, \"width\": 1.2},\n)\n\n# Ingress/egress contact markers\nfor label, xpos in [(\"T₁\", transit_center - transit_duration), (\"T₄\", transit_center + transit_duration)]:\n    fig.add_annotation(x=xpos, y=min_model - 0.0015, text=label, showarrow=False, font={\"size\": 14, \"color\": INK_MUTED})\n\n# Title length 69 chars → ratio 67/69 ≈ 0.97 → rounds to 16px (at default floor)\ntitle = \"Exoplanet Transit · lightcurve-transit · python · plotly · anyplot.ai\"\n\n# Style\nfig.update_layout(\n    autosize=False,\n    title={\"text\": title, \"font\": {\"size\": 16, \"color\": INK}, \"x\": 0.5, \"xanchor\": \"center\"},\n    xaxis={\n        \"title\": {\"text\": \"Orbital Phase\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"showgrid\": False,\n        \"showline\": False,\n        \"zeroline\": False,\n        \"range\": [-0.02, 1.02],\n        \"linecolor\": INK_SOFT,\n        \"spikemode\": \"across\",\n        \"spikethickness\": 1,\n        \"spikecolor\": GRID,\n        \"spikedash\": \"dot\",\n    },\n    yaxis={\n        \"title\": {\"text\": \"Relative Flux\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"showgrid\": True,\n        \"gridcolor\": GRID,\n        \"gridwidth\": 1,\n        \"showline\": False,\n        \"zeroline\": False,\n        \"linecolor\": INK_SOFT,\n        \"spikemode\": \"across\",\n        \"spikethickness\": 1,\n        \"spikecolor\": GRID,\n        \"spikedash\": \"dot\",\n    },\n    hoverdistance=20,\n    hovermode=\"closest\",\n    legend={\n        \"font\": {\"size\": 10, \"color\": INK_SOFT},\n        \"x\": 0.02,\n        \"y\": 0.02,\n        \"xanchor\": \"left\",\n        \"yanchor\": \"bottom\",\n        \"bgcolor\": ELEVATED_BG,\n        \"bordercolor\": INK_SOFT,\n        \"borderwidth\": 1,\n    },\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK},\n    margin={\"l\": 80, \"r\": 40, \"t\": 80, \"b\": 60},\n)\n\n# Save — 3200×1800 landscape (width=800 × scale=4)\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\", config={\"displayModeBar\": True, \"scrollZoom\": True})\n"}