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.

""" anyplot.ai
lollipop-basic: Basic Lollipop Chart
Library: letsplot 4.11.0 | Python 3.13.14
Quality: 83/100 | Updated: 2026-07-01
"""
import os
import pandas as pd
from lets_plot import (
LetsPlot,
aes,
element_blank,
element_line,
element_rect,
element_text,
geom_point,
geom_segment,
geom_text,
ggplot,
ggsize,
labs,
layer_tooltips,
scale_x_continuous,
scale_y_continuous,
theme,
theme_minimal,
)
from lets_plot.export import ggsave
LetsPlot.setup_html()
# Theme tokens
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"
RULE = "#D6D3C7" if THEME == "light" else "#3A3A34"
BRAND = "#009E73"
# Data — Product sales by category, sorted ascending
categories = ["Electronics", "Clothing", "Home/Garden", "Sports", "Books", "Toys", "Grocery", "Beauty"]
values = [45200, 32800, 28500, 21300, 18900, 15600, 12400, 9800]
df = pd.DataFrame({"category": categories, "value": values})
df = df.sort_values("value", ascending=True).reset_index(drop=True)
df["x_pos"] = range(len(df))
df["y_start"] = 0
df["value_label"] = df["value"].apply(lambda v: f"${v:,.0f}")
df["label_y"] = df["value"] + max(values) * 0.028
y_ceiling = max(values) * 1.20
# Plot — stems + stroked dots + value labels with interactive tooltips
plot = (
ggplot(df)
+ geom_segment(mapping=aes(x="x_pos", xend="x_pos", y="y_start", yend="value"), size=1.5, color=BRAND)
+ geom_point(
mapping=aes(x="x_pos", y="value"),
shape=21,
size=10,
fill=BRAND,
color=INK_SOFT,
stroke=0.8,
tooltips=layer_tooltips().line("@category").line("Sales|@value_label"),
)
+ geom_text(mapping=aes(x="x_pos", y="label_y", label="value_label"), color=INK_SOFT, size=3.5, hjust=0.5)
+ scale_x_continuous(breaks=df["x_pos"].tolist(), labels=df["category"].tolist())
+ scale_y_continuous(limits=[0, y_ceiling])
+ labs(x="Product Category", y="Sales ($)", title="lollipop-basic · python · letsplot · anyplot.ai")
+ ggsize(800, 450)
+ theme_minimal()
+ theme(
plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
panel_grid_major_y=element_line(color=RULE, size=0.3),
panel_grid_minor=element_blank(),
panel_grid_major_x=element_blank(),
axis_line_y=element_line(color=INK_SOFT, size=0.4),
axis_line_x=element_blank(),
axis_ticks=element_line(color=INK_SOFT),
axis_title=element_text(size=12, color=INK),
axis_text_x=element_text(size=10, color=INK_SOFT, angle=45, hjust=1),
axis_text_y=element_text(size=10, color=INK_SOFT),
plot_title=element_text(size=16, color=INK, hjust=0.5),
plot_margin=[20, 20, 30, 10],
)
)
ggsave(plot, f"plot-{THEME}.png", path=".", scale=4)
ggsave(plot, f"plot-{THEME}.html", path=".")
Part of Basic Lollipop Chart on anyplot.ai.