{"spec_id":"lightcurve-transit","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nlightcurve-transit: Astronomical Light Curve\nLibrary: letsplot 4.10.1 | Python 3.13.14\nQuality: 93/100 | Updated: 2026-06-20\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_errorbar,\n    geom_line,\n    geom_point,\n    geom_ribbon,\n    geom_text,\n    ggplot,\n    ggsave,\n    ggsize,\n    labs,\n    layer_tooltips,\n    scale_color_manual,\n    scale_fill_manual,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n    theme_minimal,\n)\n\n\nLetsPlot.setup_html()\n\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\n\n# Imprint palette — first series always #009E73\nIMPRINT_GREEN = \"#009E73\"\nIMPRINT_LAVENDER = \"#C475FD\"\n\n# Theme-adaptive chrome tokens\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\"\n# Grid: rgba(INK, 0.15) composited on PAGE_BG\nGRID_COLOR = \"#D8D7D0\" if THEME == \"light\" else \"#3A3A36\"\n\n# Data: simulated phase-folded exoplanet transit\nnp.random.seed(42)\nn_points = 400\nphase = np.sort(np.random.uniform(0.0, 1.0, n_points))\n\ntransit_center = 0.5\ntransit_duration = 0.08\ntransit_depth = 0.01\ningress_duration = 0.015\nhalf_dur = transit_duration / 2\nhalf_ingress = ingress_duration / 2\n\n# Limb-darkened transit model (observations)\ndist_obs = np.abs(phase - transit_center)\nmodel_flux = np.ones_like(phase)\nin_transit = dist_obs < half_dur - half_ingress\nr_obs = dist_obs[in_transit] / (half_dur - half_ingress)\nmodel_flux[in_transit] = 1.0 - transit_depth * (1 - 0.3 * (1 - np.sqrt(1 - r_obs**2)))\nin_ingress = (dist_obs >= half_dur - half_ingress) & (dist_obs < half_dur + half_ingress)\nmodel_flux[in_ingress] = 1.0 - transit_depth * (half_dur + half_ingress - dist_obs[in_ingress]) / (2 * half_ingress)\n\nflux_err = np.random.uniform(0.001, 0.003, n_points)\nflux = model_flux + np.random.normal(0, 1, n_points) * flux_err\n\n# Smooth model curve with uncertainty envelope\nphase_model = np.linspace(0.0, 1.0, 1000)\ndist_model = np.abs(phase_model - transit_center)\nmodel_smooth = np.ones_like(phase_model)\nin_transit_m = dist_model < half_dur - half_ingress\nr_model = dist_model[in_transit_m] / (half_dur - half_ingress)\nmodel_smooth[in_transit_m] = 1.0 - transit_depth * (1 - 0.3 * (1 - np.sqrt(1 - r_model**2)))\nin_ingress_m = (dist_model >= half_dur - half_ingress) & (dist_model < half_dur + half_ingress)\nmodel_smooth[in_ingress_m] = 1.0 - transit_depth * (half_dur + half_ingress - dist_model[in_ingress_m]) / (\n    2 * half_ingress\n)\n\nmodel_upper = model_smooth + 0.0015\nmodel_lower = model_smooth - 0.0015\n\ndf_obs = pd.DataFrame(\n    {\n        \"phase\": phase,\n        \"flux\": flux,\n        \"flux_err\": flux_err,\n        \"ymin\": flux - flux_err,\n        \"ymax\": flux + flux_err,\n        \"series\": \"Observations\",\n    }\n)\n\ndf_model = pd.DataFrame(\n    {\n        \"phase\": phase_model,\n        \"flux\": model_smooth,\n        \"upper\": model_upper,\n        \"lower\": model_lower,\n        \"series\": \"Transit Model\",\n        \"band\": \"Model ±1.5σ\",\n    }\n)\n\n# Annotation: mark the transit minimum\ntransit_min = model_smooth[np.argmin(np.abs(phase_model - transit_center))]\ndf_annot = pd.DataFrame({\"phase\": [transit_center], \"flux\": [transit_min - 0.0018], \"label\": [\"Transit Minimum\"]})\n\nplot = (\n    ggplot()\n    + geom_ribbon(\n        aes(x=\"phase\", ymin=\"lower\", ymax=\"upper\", fill=\"band\"),\n        data=df_model,\n        alpha=0.18,\n        color=IMPRINT_LAVENDER,\n        size=0.0,\n        tooltips=layer_tooltips()\n        .line(\"Model flux|@flux\")\n        .line(\"Phase|@phase\")\n        .format(\"@flux\", \".5f\")\n        .format(\"@phase\", \".3f\"),\n    )\n    + geom_errorbar(\n        aes(x=\"phase\", ymin=\"ymin\", ymax=\"ymax\"), data=df_obs, color=INK_MUTED, alpha=0.5, size=0.4, width=0.0\n    )\n    + geom_point(\n        aes(x=\"phase\", y=\"flux\", color=\"series\"),\n        data=df_obs,\n        size=2.5,\n        alpha=0.5,\n        tooltips=layer_tooltips()\n        .line(\"Flux|@flux\")\n        .line(\"Phase|@phase\")\n        .line(\"Error|@flux_err\")\n        .format(\"@flux\", \".5f\")\n        .format(\"@phase\", \".3f\")\n        .format(\"@flux_err\", \".4f\"),\n    )\n    + geom_line(aes(x=\"phase\", y=\"flux\", color=\"series\"), data=df_model, size=1.5, tooltips=\"none\")\n    + geom_text(aes(x=\"phase\", y=\"flux\", label=\"label\"), data=df_annot, color=INK_SOFT, size=3, hjust=0.5, vjust=0.5)\n    + scale_color_manual(values={\"Observations\": IMPRINT_GREEN, \"Transit Model\": IMPRINT_LAVENDER}, name=\"\")\n    + scale_fill_manual(values={\"Model ±1.5σ\": IMPRINT_LAVENDER}, name=\"\")\n    + scale_x_continuous(name=\"Orbital Phase\", breaks=[0.0, 0.2, 0.4, 0.6, 0.8, 1.0])\n    + scale_y_continuous(name=\"Relative Flux\")\n    + labs(\n        title=\"lightcurve-transit · python · letsplot · anyplot.ai\",\n        subtitle=\"Phase-folded exoplanet transit  ·  Depth: ~1%  ·  Quadratic limb darkening model\",\n    )\n    + theme_minimal()\n    + theme(\n        plot_title=element_text(size=16, face=\"bold\", color=INK),\n        plot_subtitle=element_text(size=10, color=INK_SOFT),\n        axis_title=element_text(size=12, color=INK),\n        axis_text=element_text(size=10, color=INK_SOFT),\n        legend_text=element_text(size=10, color=INK_SOFT),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_position=\"top\",\n        panel_grid_major_x=element_blank(),\n        panel_grid_minor=element_blank(),\n        panel_grid_major_y=element_line(color=GRID_COLOR, size=0.4),\n        axis_ticks=element_blank(),\n        axis_line_x=element_line(color=INK_SOFT),\n        axis_line_y=element_line(color=INK_SOFT),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n    )\n    + ggsize(800, 450)\n)\n\nggsave(plot, f\"plot-{THEME}.png\", scale=4, path=\".\")\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}