Basic Icicle Chart — lets-plot

An icicle chart displaying hierarchical data as adjacent rectangles in a layered structure, where each rectangle's size represents its value in the hierarchy. Unlike treemaps that nest rectangles, icicle charts stack them in rows (horizontal) or columns (vertical), making parent-child relationships explicitly visible through spatial adjacency. This layout excels at showing both the hierarchy levels and the proportional values simultaneously.

Basic Icicle Chart rendered with lets-plot

Python source (lets-plot)

""" anyplot.ai
icicle-basic: Basic Icicle Chart
Library: letsplot 4.9.0 | Python 3.13.13
Quality: 92/100 | Updated: 2026-05-13
"""

import os

import pandas as pd
from lets_plot import (
    LetsPlot,
    aes,
    element_blank,
    element_rect,
    element_text,
    geom_rect,
    geom_text,
    ggplot,
    ggsave,
    ggsize,
    labs,
    scale_fill_manual,
    scale_size_identity,
    theme,
    xlim,
    ylim,
)


LetsPlot.setup_html()

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 = ["#009E73", "#C475FD", "#4467A3", "#BD8233"]

# Hierarchical data: File system example
hierarchy = [
    {"name": "root", "parent": "", "value": 1000},
    {"name": "Documents", "parent": "root", "value": 350},
    {"name": "Media", "parent": "root", "value": 400},
    {"name": "Projects", "parent": "root", "value": 250},
    {"name": "Work", "parent": "Documents", "value": 200},
    {"name": "Personal", "parent": "Documents", "value": 150},
    {"name": "Photos", "parent": "Media", "value": 220},
    {"name": "Videos", "parent": "Media", "value": 180},
    {"name": "Python", "parent": "Projects", "value": 120},
    {"name": "Web", "parent": "Projects", "value": 130},
    {"name": "Reports", "parent": "Work", "value": 120},
    {"name": "Contracts", "parent": "Work", "value": 80},
    {"name": "Letters", "parent": "Personal", "value": 90},
    {"name": "Receipts", "parent": "Personal", "value": 60},
    {"name": "2024", "parent": "Photos", "value": 130},
    {"name": "2023", "parent": "Photos", "value": 90},
    {"name": "Movies", "parent": "Videos", "value": 100},
    {"name": "Clips", "parent": "Videos", "value": 80},
    {"name": "DataViz", "parent": "Python", "value": 70},
    {"name": "ML", "parent": "Python", "value": 50},
    {"name": "Frontend", "parent": "Web", "value": 75},
    {"name": "Backend", "parent": "Web", "value": 55},
]

# Build tree structure
name_to_node = {row["name"]: row for row in hierarchy}
children = {}
for row in hierarchy:
    parent = row["parent"]
    if parent not in children:
        children[parent] = []
    if parent:
        children[parent].append(row["name"])

# Calculate level for each node
levels = {}
for row in hierarchy:
    level = 0
    current = row["name"]
    while name_to_node[current]["parent"]:
        level += 1
        current = name_to_node[current]["parent"]
    levels[row["name"]] = level

max_level = max(levels.values())

# Calculate rectangle positions (horizontal icicle: root at top)
rects = []
stack = [("root", 0.0, 1.0)]

while stack:
    name, x_start, x_end = stack.pop()
    level = levels[name]

    rects.append(
        {
            "name": name,
            "xmin": x_start,
            "xmax": x_end,
            "ymin": max_level - level,
            "ymax": max_level - level + 1,
            "level": level,
        }
    )

    if name in children and children[name]:
        child_names = children[name]
        total_value = sum(name_to_node[c]["value"] for c in child_names)
        x_end_temp = x_end

        for child_name in reversed(child_names):
            child_value = name_to_node[child_name]["value"]
            child_width = (x_end - x_start) * (child_value / total_value)
            child_x_start = x_end_temp - child_width
            stack.append((child_name, child_x_start, x_end_temp))
            x_end_temp = child_x_start

# Create dataframe for rectangles
rect_df = pd.DataFrame(rects)
rect_df["level_str"] = rect_df["level"].astype(str)

# Calculate center positions and dimensions for labels
rect_df["x_center"] = (rect_df["xmin"] + rect_df["xmax"]) / 2
rect_df["y_center"] = (rect_df["ymin"] + rect_df["ymax"]) / 2
rect_df["width"] = rect_df["xmax"] - rect_df["xmin"]

# Show labels for rectangles with sufficient width
rect_df["label_len"] = rect_df["name"].str.len()
rect_df["show_label"] = rect_df["width"] > (rect_df["label_len"] * 0.007 + 0.01)
label_df = rect_df[rect_df["show_label"]].copy()

# Adjust font size based on level
label_df["font_size"] = label_df["level"].map({0: 14, 1: 12, 2: 8, 3: 7})

# Color palette by level using Okabe-Ito
colors = {
    "0": IMPRINT[0],  # Level 0: bluish green
    "1": IMPRINT[1],  # Level 1: vermillion
    "2": IMPRINT[2],  # Level 2: blue
    "3": IMPRINT[3],  # Level 3: reddish purple
}

anyplot_theme = theme(
    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
    panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
    panel_grid=element_blank(),
    axis_title=element_blank(),
    axis_text=element_blank(),
    axis_ticks=element_blank(),
    axis_line=element_blank(),
    plot_title=element_text(size=24, color=INK),
    legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),
    legend_text=element_text(size=16, color=INK_SOFT),
    legend_title=element_text(size=18, color=INK),
)

# Create plot
plot = (
    ggplot()
    + geom_rect(
        aes(xmin="xmin", xmax="xmax", ymin="ymin", ymax="ymax", fill="level_str"),
        data=rect_df,
        color=INK_SOFT,
        size=1.5,
        alpha=0.9,
    )
    + geom_text(aes(x="x_center", y="y_center", label="name", size="font_size"), data=label_df, color=INK)
    + scale_fill_manual(values=colors, name="Hierarchy Level")
    + scale_size_identity()
    + xlim(-0.02, 1.02)
    + ylim(-0.1, max_level + 1.1)
    + labs(title="icicle-basic · letsplot · anyplot.ai")
    + anyplot_theme
    + ggsize(1600, 900)
)

# Save outputs
ggsave(plot, f"plot-{THEME}.png", path=".", scale=3)
ggsave(plot, f"plot-{THEME}.html", path=".")

Part of Basic Icicle Chart on anyplot.ai.

Other implementations