{"spec_id":"line-parametric","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nline-parametric: Parametric Curve Plot\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\nfrom plotly.subplots import make_subplots\n\n\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\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\n# Imprint sequential colorscale: brand green → blue (single-polarity continuous t)\nimprint_seq = [[0.0, \"#009E73\"], [1.0, \"#4467A3\"]]\n\n# Pre-compute 100 segment colors from Imprint seq palette via numpy (no helper function)\nn_seg = 100\nc0, c1 = np.array([0x00, 0x9E, 0x73]), np.array([0x44, 0x67, 0xA3])\nseg_rgb = (c0 + np.linspace(0, 1, n_seg)[:, None] * (c1 - c0)).astype(int)\nseg_colors = [f\"rgb({r},{g},{b})\" for r, g, b in seg_rgb]\n\n# Data\nn_pts = 2000\nt_liss = np.linspace(0, 2 * np.pi, n_pts)\nx_liss, y_liss = np.sin(3 * t_liss), np.sin(2 * t_liss)\n\nt_spiral = np.linspace(0, 4 * np.pi, n_pts)\nx_spiral = t_spiral * np.cos(t_spiral)\ny_spiral = t_spiral * np.sin(t_spiral)\n\n# Figure with two subplots\nfig = make_subplots(\n    rows=1,\n    cols=2,\n    subplot_titles=(\n        \"<b>Lissajous Figure</b><br><i>x = sin(3t), y = sin(2t),  t ∈ [0, 2π]</i>\",\n        \"<b>Archimedean Spiral</b><br><i>x = t·cos(t), y = t·sin(t),  t ∈ [0, 4π]</i>\",\n    ),\n    horizontal_spacing=0.14,\n)\n\n# Capture subplot title annotation count before adding data annotations\nn_title_anns = len(fig.layout.annotations)\n\nseg_size = n_pts // n_seg\n\nfor xx, yy, tt, t_max, col_idx, xref, yref, t_end_lbl in [\n    (x_liss, y_liss, t_liss, 2 * np.pi, 1, \"x\", \"y\", \"2π\"),\n    (x_spiral, y_spiral, t_spiral, 4 * np.pi, 2, \"x2\", \"y2\", \"4π\"),\n]:\n    # Gradient line segments using pre-computed Imprint palette colors\n    for i, color in enumerate(seg_colors):\n        s = i * seg_size\n        e = min(s + seg_size + 1, n_pts)\n        fig.add_trace(\n            go.Scatter(\n                x=xx[s:e], y=yy[s:e], mode=\"lines\", line=dict(width=3, color=color), showlegend=False, hoverinfo=\"skip\"\n            ),\n            row=1,\n            col=col_idx,\n        )\n\n    # Invisible markers for hover + single shared colorbar on far right\n    fig.add_trace(\n        go.Scatter(\n            x=xx[::10],\n            y=yy[::10],\n            mode=\"markers\",\n            marker=dict(\n                size=0.1,\n                opacity=0,\n                color=tt[::10] / t_max,\n                colorscale=imprint_seq,\n                showscale=(col_idx == 2),\n                colorbar=dict(\n                    title=dict(text=\"Curve progress\", font=dict(size=12, color=INK), side=\"right\"),\n                    tickvals=[0.0, 0.5, 1.0],\n                    ticktext=[\"Start\", \"Midpoint\", \"End\"],\n                    tickfont=dict(size=10, color=INK_SOFT),\n                    len=0.7,\n                    x=1.03,\n                    thickness=14,\n                    outlinewidth=0,\n                    bgcolor=ELEVATED_BG,\n                ),\n            ),\n            hovertemplate=\"t = %{customdata:.3f} rad<br>x = %{x:.3f}<br>y = %{y:.3f}<extra></extra>\",\n            customdata=tt[::10],\n            showlegend=False,\n        ),\n        row=1,\n        col=col_idx,\n    )\n\n    # Start marker (Imprint green — matches start of Imprint seq gradient)\n    fig.add_trace(\n        go.Scatter(\n            x=[xx[0]],\n            y=[yy[0]],\n            mode=\"markers\",\n            marker=dict(size=14, color=\"#009E73\", symbol=\"circle\", line=dict(color=PAGE_BG, width=2)),\n            showlegend=False,\n            hovertemplate=\"<b>Start</b>: t = 0<extra></extra>\",\n        ),\n        row=1,\n        col=col_idx,\n    )\n    fig.add_annotation(\n        x=xx[0],\n        y=yy[0],\n        text=\"<b>t = 0</b>\",\n        showarrow=True,\n        arrowhead=0,\n        arrowwidth=1.5,\n        arrowcolor=\"#009E73\",\n        ax=55,\n        ay=-42,\n        font=dict(size=11, color=\"#009E73\"),\n        bgcolor=ELEVATED_BG,\n        bordercolor=\"#009E73\",\n        borderwidth=1,\n        borderpad=3,\n        xref=xref,\n        yref=yref,\n    )\n\n    # End marker (Imprint blue — matches end of Imprint seq gradient)\n    fig.add_trace(\n        go.Scatter(\n            x=[xx[-1]],\n            y=[yy[-1]],\n            mode=\"markers\",\n            marker=dict(size=14, color=\"#4467A3\", symbol=\"square\", line=dict(color=PAGE_BG, width=2)),\n            showlegend=False,\n            hovertemplate=f\"<b>End</b>: t = {t_end_lbl}<extra></extra>\",\n        ),\n        row=1,\n        col=col_idx,\n    )\n    fig.add_annotation(\n        x=xx[-1],\n        y=yy[-1],\n        text=f\"<b>t = {t_end_lbl}</b>\",\n        showarrow=True,\n        arrowhead=0,\n        arrowwidth=1.5,\n        arrowcolor=\"#4467A3\",\n        ax=(-55 if col_idx == 1 else -60),\n        ay=(45 if col_idx == 1 else -35),\n        font=dict(size=11, color=\"#4467A3\"),\n        bgcolor=ELEVATED_BG,\n        bordercolor=\"#4467A3\",\n        borderwidth=1,\n        borderpad=3,\n        xref=xref,\n        yref=yref,\n    )\n\n# Apply theme-adaptive INK color to subplot title annotations only\nfor ann in list(fig.layout.annotations)[:n_title_anns]:\n    ann.update(font=dict(size=12, color=INK))\n\n# Layout — canvas: width=800, height=450, scale=4 → 3200×1800 px (landscape)\nfig.update_layout(\n    title=dict(\n        text=\"line-parametric · python · plotly · anyplot.ai\", font=dict(size=16, color=INK), x=0.5, xanchor=\"center\"\n    ),\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    autosize=False,\n    width=800,\n    height=450,\n    margin=dict(l=80, r=100, t=90, b=80),\n    font=dict(color=INK),\n)\n\nfor col in [1, 2]:\n    y_ref = \"y\" if col == 1 else \"y2\"\n    fig.update_xaxes(\n        title=dict(text=\"x(t)\", font=dict(size=12, color=INK), standoff=8),\n        tickfont=dict(size=10, color=INK_SOFT),\n        showgrid=True,\n        gridwidth=1,\n        gridcolor=GRID,\n        zeroline=True,\n        zerolinewidth=1,\n        zerolinecolor=INK_SOFT,\n        showline=True,\n        linewidth=1,\n        linecolor=INK_SOFT,\n        scaleanchor=y_ref,\n        scaleratio=1,\n        row=1,\n        col=col,\n    )\n    fig.update_yaxes(\n        title=dict(text=\"y(t)\", font=dict(size=12, color=INK), standoff=8),\n        tickfont=dict(size=10, color=INK_SOFT),\n        showgrid=True,\n        gridwidth=1,\n        gridcolor=GRID,\n        zeroline=True,\n        zerolinewidth=1,\n        zerolinecolor=INK_SOFT,\n        showline=True,\n        linewidth=1,\n        linecolor=INK_SOFT,\n        row=1,\n        col=col,\n    )\n\n# Interactive reset button (Plotly-specific feature)\nfig.update_layout(\n    updatemenus=[\n        dict(\n            type=\"buttons\",\n            showactive=True,\n            x=0.5,\n            y=-0.14,\n            xanchor=\"center\",\n            buttons=[\n                dict(\n                    label=\"Reset View\",\n                    method=\"relayout\",\n                    args=[\n                        {\n                            \"xaxis.autorange\": True,\n                            \"yaxis.autorange\": True,\n                            \"xaxis2.autorange\": True,\n                            \"yaxis2.autorange\": True,\n                        }\n                    ],\n                )\n            ],\n            font=dict(size=12, color=INK),\n            bgcolor=ELEVATED_BG,\n            bordercolor=INK_SOFT,\n            borderwidth=1,\n        )\n    ]\n)\n\n# Save — 3200×1800 landscape (width=800, height=450, 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\")\n"}