Basic Lollipop Chart — plotnine

A lollipop chart displays categorical data with thin lines (stems) extending from a baseline to circular markers (dots) at each data point. It presents the same information as a bar chart but with a cleaner, more minimalist aesthetic that reduces visual clutter while maintaining clear value comparisons.

Basic Lollipop Chart rendered with plotnine

Python source (plotnine)

""" anyplot.ai
lollipop-basic: Basic Lollipop Chart
Library: plotnine 0.15.7 | Python 3.13.14
Quality: 84/100 | Updated: 2026-07-01
"""

import os
import sys


# Prevent circular import: remove this script's directory from sys.path so
# "from plotnine import ..." resolves to the installed library, not this file.
_here = os.path.dirname(os.path.abspath(__file__))
sys.path = [p for p in sys.path if os.path.abspath(p) != _here]

import pandas as pd
from plotnine import (
    aes,
    element_blank,
    element_line,
    element_rect,
    element_text,
    geom_point,
    geom_segment,
    geom_text,
    ggplot,
    ggsave,
    labs,
    scale_size_continuous,
    theme,
    theme_minimal,
)


THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
BRAND = "#009E73"

# Product sales by category, sorted ascending for ranking narrative
data = {
    "category": [
        "Electronics",
        "Furniture",
        "Clothing",
        "Groceries",
        "Sports",
        "Books",
        "Toys",
        "Beauty",
        "Garden",
        "Automotive",
    ],
    "value": [245, 198, 176, 152, 134, 118, 95, 87, 72, 58],
}

df = pd.DataFrame(data)
df = df.sort_values("value", ascending=True).reset_index(drop=True)
df["category"] = pd.Categorical(df["category"], categories=df["category"], ordered=True)
df["label"] = df["value"].astype(str) + "k"

plot = (
    ggplot(df, aes(x="category", y="value"))
    + geom_segment(aes(x="category", xend="category", y=0, yend="value"), color=BRAND, size=0.6)
    + geom_point(aes(size="value"), color=BRAND, fill=BRAND, show_legend=False)
    + geom_text(
        aes(label="label"),
        color=INK_SOFT,
        size=2.8,  # geom_text size is in mm (~2.8mm ≈ 8pt at dpi=400)
        nudge_y=16,
        va="bottom",
    )
    + scale_size_continuous(range=[2, 7])
    + labs(x="Product Category", y="Sales (thousands $)", title="lollipop-basic · python · plotnine · anyplot.ai")
    + 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, color=PAGE_BG),
        panel_border=element_blank(),
        text=element_text(size=7, color=INK),
        axis_title=element_text(size=10, color=INK),
        axis_text=element_text(size=8, color=INK_SOFT),
        axis_text_x=element_text(angle=45, ha="right", color=INK_SOFT),
        axis_line=element_line(color=INK_SOFT),
        plot_title=element_text(size=12, color=INK),
        panel_grid_minor=element_blank(),
        panel_grid_major_x=element_blank(),
        panel_grid_major_y=element_line(color=INK, alpha=0.15, size=0.3),
    )
)

ggsave(plot, filename=f"plot-{THEME}.png", dpi=400, width=8, height=4.5, units="in")

Part of Basic Lollipop Chart on anyplot.ai.

Other implementations