Chris Parmer — home

Tumor-response waterfall chart

Oncology — synthetic data

Example from the compendium of canonical charts

Oncology — Tumor-response waterfall chart (synthetic data)

Python Code

"""Oncology — Tumor-response waterfall chart (synthetic data)."""


import numpy as np
import plotly.graph_objects as go


# Response category thresholds (RECIST-like)
PR_THRESHOLD = -30   # partial response: < -30%
PD_THRESHOLD = +20   # progressive disease: > +20%

# Colors
COLOR_RESPONDER  = GREEN    # partial/complete response
COLOR_STABLE     = TEAL   # stable disease
COLOR_PROGRESSOR = "#D94F4F"  # progressive disease (red)


def generate():
    rng = np.random.default_rng(2024)
    n = 60

    # 30% responders, 50% stable, 20% progressors
    n_resp  = round(0.30 * n)
    n_stable = round(0.50 * n)
    n_prog  = n - n_resp - n_stable

    changes = np.concatenate([
        rng.uniform(-80, -30, n_resp),    # responders
        rng.uniform(-30, +20, n_stable),  # stable
        rng.uniform(+20, +60, n_prog),    # progressors
    ])

    # Sort best to worst (ascending: most negative first)
    order = np.argsort(changes)
    changes = changes[order]

    patient_ids = [f"P{i+1:03d}" for i in range(n)]

    # Assign colors
    colors = []
    for c in changes:
        if c < PR_THRESHOLD:
            colors.append(COLOR_RESPONDER)
        elif c > PD_THRESHOLD:
            colors.append(COLOR_PROGRESSOR)
        else:
            colors.append(COLOR_STABLE)

    fig = go.Figure([
        go.Bar(
            x=patient_ids,
            y=changes,
            marker_color=colors,
            hovertemplate="%{x}<br>%{y:.1f}%<extra></extra>",
        )
    ])

    # Threshold lines
    x_range = [-0.5, n - 0.5]
    for thresh, label, dash in [
        (PR_THRESHOLD, "−30% (PR)", "dash"),
        (PD_THRESHOLD, "+20% (PD)", "dot"),
    ]:
        fig.add_shape(
            type="line",
            x0=x_range[0], x1=x_range[1],
            y0=thresh, y1=thresh,
            line=dict(color="black", width=1.2, dash=dash),
        )
        fig.add_annotation(
            x=n - 1,
            y=thresh,
            text=label,
            showarrow=False,
            xanchor="right",
            yanchor="bottom",
            font=dict(size=10),
            yshift=3,
        )

    fig.update_layout(
        xaxis=dict(
            title="Patient (sorted by best response)",
            showticklabels=False,
            tickangle=0,
        ),
        yaxis=dict(
            title="Best % Change in Tumor Size",
            ticksuffix="%",
            zeroline=True,
            zerolinewidth=1,
            zerolinecolor="black",
        ),
        showlegend=False,
        margin=dict(t=40, b=60, l=80, r=40),
        height=500,
        bargap=0.05,
    )
    return fig


if __name__ == "__main__":
    generate()

Made with Plotly