Multi-Line Comparison Plot — plotnine

A multi-line plot displays multiple data series on the same axes for direct comparison. Each series is represented by a distinct line with its own color and optional style, making it easy to identify trends, correlations, and divergences between variables. This visualization is essential for comparing related metrics over a common sequence or time period.

Multi-Line Comparison Plot rendered with plotnine

Renders

Python source (plotnine)

""" anyplot.ai
line-multi: Multi-Line Comparison Plot
Library: plotnine 0.15.7 | Python 3.13.14
Quality: 92/100 | Updated: 2026-08-05
"""

import os

import numpy as np
import pandas as pd
from plotnine import (
    aes,
    element_blank,
    element_line,
    element_rect,
    element_text,
    geom_line,
    geom_point,
    geom_text,
    ggplot,
    labs,
    scale_color_manual,
    scale_fill_manual,
    scale_x_continuous,
    theme,
    theme_minimal,
)


# Theme tokens
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"

# Imprint palette (brand green always first)
IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233"]

# Data - Monthly sales for 4 product lines over 12 months, category order
# matches the year-end ranking so the legend reads top-to-bottom like the lines
np.random.seed(42)
months = np.arange(1, 13)
month_labels = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]

electronics = 100 + months * 9 + np.cumsum(np.random.normal(0, 3, 12)) * 0.4
accessories = 85 + months * 3.2 + np.cumsum(np.random.normal(0, 2.5, 12)) * 0.4
furniture = 95 + np.sin(months * 0.4) * 6 + np.cumsum(np.random.normal(0, 2, 12)) * 0.3
clothing = 128 - months * 3.8 + np.cumsum(np.random.normal(0, 2.5, 12)) * 0.4

# Long-format DataFrame for plotnine
df = pd.DataFrame(
    {
        "Month": np.tile(months, 4),
        "Sales": np.concatenate([electronics, accessories, furniture, clothing]),
        "Product": np.repeat(["Electronics", "Accessories", "Furniture", "Clothing"], 12),
    }
)
df["Product"] = pd.Categorical(
    df["Product"], categories=["Electronics", "Accessories", "Furniture", "Clothing"], ordered=True
)

color_map = dict(zip(["Electronics", "Accessories", "Furniture", "Clothing"], IMPRINT, strict=True))
brand_only = df[df["Product"] == "Electronics"]

# Direct end-of-line value labels (year-end sales) so the crossover story reads
# without relying on the legend alone. Labels are spread apart with a minimum
# gap so the close Furniture/Clothing pair at month 12 doesn't collide.
end_labels = (
    df.loc[df["Month"] == 12, ["Product", "Sales"]].sort_values("Sales", ascending=False).reset_index(drop=True)
)
end_labels["label_y"] = end_labels["Sales"]
min_gap = 14
for i in range(1, len(end_labels)):
    if end_labels.loc[i - 1, "label_y"] - end_labels.loc[i, "label_y"] < min_gap:
        end_labels.loc[i, "label_y"] = end_labels.loc[i - 1, "label_y"] - min_gap
end_labels["Month"] = 12
end_labels["Label"] = end_labels["Sales"].round(0).astype(int).astype(str)

# Plot - base lines for all series, brand series (Electronics) redrawn
# thicker on top to lead the eye, plus direct end-of-line value labels
plot = (
    ggplot(df, aes(x="Month", y="Sales", color="Product", group="Product"))
    + geom_line(size=1.3)
    + geom_point(aes(fill="Product"), shape="o", color=PAGE_BG, stroke=0.6, size=2.9)
    + geom_line(data=brand_only, size=2.1, show_legend=False)
    + geom_text(
        data=end_labels,
        mapping=aes(x="Month", y="label_y", label="Label", color="Product"),
        ha="left",
        nudge_x=0.35,
        size=2.8,
        fontweight="bold",
        show_legend=False,
    )
    + scale_color_manual(values=color_map)
    + scale_fill_manual(values=color_map, guide=None)
    + scale_x_continuous(breaks=months, labels=month_labels, expand=(0.02, 0, 0.1, 0.6))
    + labs(
        x="Month", y="Sales (thousands USD)", title="line-multi · python · plotnine · anyplot.ai", color="Product Line"
    )
    + theme_minimal()
    + theme(
        figure_size=(8, 4.5),
        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
        panel_background=element_rect(fill=PAGE_BG),
        panel_grid_major_x=element_blank(),
        panel_grid_minor=element_blank(),
        panel_grid_major_y=element_line(color=INK, size=0.3, alpha=0.12),
        axis_line=element_blank(),
        text=element_text(size=7, color=INK_SOFT),
        axis_title=element_text(size=10, color=INK),
        axis_text=element_text(size=8, color=INK_SOFT),
        plot_title=element_text(size=12, color=INK, ha="center"),
        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT, size=0.5),
        legend_key=element_rect(fill=PAGE_BG, color=None),
        legend_text=element_text(size=8, color=INK_SOFT),
        legend_title=element_text(size=9, color=INK),
        legend_position="right",
    )
)

# Save
plot.save(f"plot-{THEME}.png", dpi=400, width=8, height=4.5, units="in", verbose=False)

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/line-multi/plotnine/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "line-multi",
  "language": "python",
  "library": "plotnine",
  "page": "https://anyplot.ai/line-multi/python/plotnine",
  "hub": "https://anyplot.ai/line-multi",
  "code_json": "https://api.anyplot.ai/specs/line-multi/plotnine/code",
  "spec_json": "https://api.anyplot.ai/specs/line-multi",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/line-multi/python/plotnine/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/line-multi/python/plotnine/plot-dark.png",
  "quality_score": 92.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Multi-Line Comparison Plot on anyplot.ai.

Other implementations