Parametric Curve Plot — Plotly

A parametric curve plot visualizes x(t) and y(t) as functions of a parameter t, tracing smooth curves in 2D space. Unlike standard function plots where y = f(x), parametric curves can loop, self-intersect, and form closed shapes such as Lissajous figures, spirals, and cardioids. This makes them essential for representing trajectories, oscillations, and classical mathematical curves that cannot be expressed as single-valued functions.

Parametric Curve Plot rendered with Plotly

Python source (Plotly)

""" anyplot.ai
line-parametric: Parametric Curve Plot
Library: plotly 6.8.0 | Python 3.13.14
Quality: 90/100 | Updated: 2026-06-20
"""

import os

import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots


THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
GRID = "rgba(26,26,23,0.15)" if THEME == "light" else "rgba(240,239,232,0.15)"

# Imprint sequential colorscale: brand green → blue (single-polarity continuous t)
imprint_seq = [[0.0, "#009E73"], [1.0, "#4467A3"]]

# Pre-compute 100 segment colors from Imprint seq palette via numpy (no helper function)
n_seg = 100
c0, c1 = np.array([0x00, 0x9E, 0x73]), np.array([0x44, 0x67, 0xA3])
seg_rgb = (c0 + np.linspace(0, 1, n_seg)[:, None] * (c1 - c0)).astype(int)
seg_colors = [f"rgb({r},{g},{b})" for r, g, b in seg_rgb]

# Data
n_pts = 2000
t_liss = np.linspace(0, 2 * np.pi, n_pts)
x_liss, y_liss = np.sin(3 * t_liss), np.sin(2 * t_liss)

t_spiral = np.linspace(0, 4 * np.pi, n_pts)
x_spiral = t_spiral * np.cos(t_spiral)
y_spiral = t_spiral * np.sin(t_spiral)

# Figure with two subplots
fig = make_subplots(
    rows=1,
    cols=2,
    subplot_titles=(
        "<b>Lissajous Figure</b><br><i>x = sin(3t), y = sin(2t),  t ∈ [0, 2π]</i>",
        "<b>Archimedean Spiral</b><br><i>x = t·cos(t), y = t·sin(t),  t ∈ [0, 4π]</i>",
    ),
    horizontal_spacing=0.14,
)

# Capture subplot title annotation count before adding data annotations
n_title_anns = len(fig.layout.annotations)

seg_size = n_pts // n_seg

for xx, yy, tt, t_max, col_idx, xref, yref, t_end_lbl in [
    (x_liss, y_liss, t_liss, 2 * np.pi, 1, "x", "y", "2π"),
    (x_spiral, y_spiral, t_spiral, 4 * np.pi, 2, "x2", "y2", "4π"),
]:
    # Gradient line segments using pre-computed Imprint palette colors
    for i, color in enumerate(seg_colors):
        s = i * seg_size
        e = min(s + seg_size + 1, n_pts)
        fig.add_trace(
            go.Scatter(
                x=xx[s:e], y=yy[s:e], mode="lines", line=dict(width=3, color=color), showlegend=False, hoverinfo="skip"
            ),
            row=1,
            col=col_idx,
        )

    # Invisible markers for hover + single shared colorbar on far right
    fig.add_trace(
        go.Scatter(
            x=xx[::10],
            y=yy[::10],
            mode="markers",
            marker=dict(
                size=0.1,
                opacity=0,
                color=tt[::10] / t_max,
                colorscale=imprint_seq,
                showscale=(col_idx == 2),
                colorbar=dict(
                    title=dict(text="Curve progress", font=dict(size=12, color=INK), side="right"),
                    tickvals=[0.0, 0.5, 1.0],
                    ticktext=["Start", "Midpoint", "End"],
                    tickfont=dict(size=10, color=INK_SOFT),
                    len=0.7,
                    x=1.03,
                    thickness=14,
                    outlinewidth=0,
                    bgcolor=ELEVATED_BG,
                ),
            ),
            hovertemplate="t = %{customdata:.3f} rad<br>x = %{x:.3f}<br>y = %{y:.3f}<extra></extra>",
            customdata=tt[::10],
            showlegend=False,
        ),
        row=1,
        col=col_idx,
    )

    # Start marker (Imprint green — matches start of Imprint seq gradient)
    fig.add_trace(
        go.Scatter(
            x=[xx[0]],
            y=[yy[0]],
            mode="markers",
            marker=dict(size=14, color="#009E73", symbol="circle", line=dict(color=PAGE_BG, width=2)),
            showlegend=False,
            hovertemplate="<b>Start</b>: t = 0<extra></extra>",
        ),
        row=1,
        col=col_idx,
    )
    fig.add_annotation(
        x=xx[0],
        y=yy[0],
        text="<b>t = 0</b>",
        showarrow=True,
        arrowhead=0,
        arrowwidth=1.5,
        arrowcolor="#009E73",
        ax=55,
        ay=-42,
        font=dict(size=11, color="#009E73"),
        bgcolor=ELEVATED_BG,
        bordercolor="#009E73",
        borderwidth=1,
        borderpad=3,
        xref=xref,
        yref=yref,
    )

    # End marker (Imprint blue — matches end of Imprint seq gradient)
    fig.add_trace(
        go.Scatter(
            x=[xx[-1]],
            y=[yy[-1]],
            mode="markers",
            marker=dict(size=14, color="#4467A3", symbol="square", line=dict(color=PAGE_BG, width=2)),
            showlegend=False,
            hovertemplate=f"<b>End</b>: t = {t_end_lbl}<extra></extra>",
        ),
        row=1,
        col=col_idx,
    )
    fig.add_annotation(
        x=xx[-1],
        y=yy[-1],
        text=f"<b>t = {t_end_lbl}</b>",
        showarrow=True,
        arrowhead=0,
        arrowwidth=1.5,
        arrowcolor="#4467A3",
        ax=(-55 if col_idx == 1 else -60),
        ay=(45 if col_idx == 1 else -35),
        font=dict(size=11, color="#4467A3"),
        bgcolor=ELEVATED_BG,
        bordercolor="#4467A3",
        borderwidth=1,
        borderpad=3,
        xref=xref,
        yref=yref,
    )

# Apply theme-adaptive INK color to subplot title annotations only
for ann in list(fig.layout.annotations)[:n_title_anns]:
    ann.update(font=dict(size=12, color=INK))

# Layout — canvas: width=800, height=450, scale=4 → 3200×1800 px (landscape)
fig.update_layout(
    title=dict(
        text="line-parametric · python · plotly · anyplot.ai", font=dict(size=16, color=INK), x=0.5, xanchor="center"
    ),
    paper_bgcolor=PAGE_BG,
    plot_bgcolor=PAGE_BG,
    autosize=False,
    width=800,
    height=450,
    margin=dict(l=80, r=100, t=90, b=80),
    font=dict(color=INK),
)

for col in [1, 2]:
    y_ref = "y" if col == 1 else "y2"
    fig.update_xaxes(
        title=dict(text="x(t)", font=dict(size=12, color=INK), standoff=8),
        tickfont=dict(size=10, color=INK_SOFT),
        showgrid=True,
        gridwidth=1,
        gridcolor=GRID,
        zeroline=True,
        zerolinewidth=1,
        zerolinecolor=INK_SOFT,
        showline=True,
        linewidth=1,
        linecolor=INK_SOFT,
        scaleanchor=y_ref,
        scaleratio=1,
        row=1,
        col=col,
    )
    fig.update_yaxes(
        title=dict(text="y(t)", font=dict(size=12, color=INK), standoff=8),
        tickfont=dict(size=10, color=INK_SOFT),
        showgrid=True,
        gridwidth=1,
        gridcolor=GRID,
        zeroline=True,
        zerolinewidth=1,
        zerolinecolor=INK_SOFT,
        showline=True,
        linewidth=1,
        linecolor=INK_SOFT,
        row=1,
        col=col,
    )

# Interactive reset button (Plotly-specific feature)
fig.update_layout(
    updatemenus=[
        dict(
            type="buttons",
            showactive=True,
            x=0.5,
            y=-0.14,
            xanchor="center",
            buttons=[
                dict(
                    label="Reset View",
                    method="relayout",
                    args=[
                        {
                            "xaxis.autorange": True,
                            "yaxis.autorange": True,
                            "xaxis2.autorange": True,
                            "yaxis2.autorange": True,
                        }
                    ],
                )
            ],
            font=dict(size=12, color=INK),
            bgcolor=ELEVATED_BG,
            bordercolor=INK_SOFT,
            borderwidth=1,
        )
    ]
)

# Save — 3200×1800 landscape (width=800, height=450, scale=4)
fig.write_image(f"plot-{THEME}.png", width=800, height=450, scale=4)
fig.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn")

Part of Parametric Curve Plot on anyplot.ai.

Other implementations