Skip to content


ninejs

Bringing ✨interactivity✨ to plotnine.

ninejs adds interactive behavior to plotnine charts with a minimal, composable API. You can attach tooltips, hover grouping, and other frontend interactions directly from aes(), then export the result as a standalone HTML plot.

It works out of the box with Quarto, marimo, and Shiny, and it includes a built-in preview in Positron.

Quick start

Specify the tooltip and data_id aesthetic mappings, and then pass your plotnine chart to interactive():

from plotnine import ggplot, aes, geom_point, theme_minimal
from plotnine.data import anscombe_quartet

from ninejs import interactive, css, save

gg = (
    ggplot(
        data=anscombe_quartet,
        mapping=aes(x="x", y="y", color="dataset", tooltip="dataset", data_id="dataset"),
    )
    + geom_point(size=7, alpha=0.5)
    + theme_minimal()
)

(
    interactive(gg)
    + css(from_dict={".tooltip": {"font-size": "3em"}})
    + save("docs/iframes/quickstart2.html")
)

Installation

pip install ninejs
uv add ninejs
pixi add ninejs

Examples

from plotnine import ggplot, aes, geom_point, theme_minimal
from plotnine.data import anscombe_quartet

from ninejs import interactive, css, save

gg = (
    ggplot(data=anscombe_quartet, mapping=aes(x="x", y="y", tooltip="dataset"))
    + geom_point(size=7, alpha=0.5)
    + theme_minimal()
)

(
    interactive(gg)
    + css(".tooltip {font-size: 2em;}")
    + save("docs/iframes/point.html")
)

from plotnine import ggplot, aes, geom_point, theme_minimal
from plotnine.data import anscombe_quartet

from ninejs import interactive, css, save

gg = (
    ggplot(
        data=anscombe_quartet,
        mapping=aes(x="x", y="y", color="dataset", tooltip="dataset", data_id="dataset"),
    )
    + geom_point(size=7, alpha=0.5)
    + theme_minimal()
)

(
    interactive(gg)
    + css(from_dict={".tooltip": {"font-size": "3em"}})
    + save("docs/iframes/quickstart2.html")
)

gg = (
    ggplot(
        data=anscombe_quartet,
        mapping=aes(x="x", y="y", color="dataset", tooltip="dataset"),
    )
    + geom_line(size=4, alpha=0.5)
    + theme_minimal()
)

(
    interactive(gg)
    + css(from_dict={".tooltip": {"font-size": "3em"}})
    + save("docs/iframes/line.html")
)

df = pd.DataFrame({"category": ["A", "B", "C"], "value": [3, 7, 5]})
df["tooltip"] = df["category"].astype(str) + " (" + df["value"].astype(str) + ")"


gg = (
    ggplot(df, aes(x="category", y="value", tooltip="tooltip"))
    + geom_col()
    + theme_classic()
)

interactive(gg) + save("docs/iframes/bar.html")

plot = (
    ggplot(anscombe_quartet, aes("x", "y", tooltip="x"))
    + geom_point(color="sienna", fill="orange", size=3)
    + geom_smooth(method="lm", se=False, fullrange=True, color="steelblue", size=1)
    + facet_wrap("dataset")
    + labs(title="Anscombe’s Quartet")
    + scale_y_continuous(breaks=(4, 8, 12))
    + coord_fixed(xlim=(3, 22), ylim=(2, 14))
    + theme_tufte(base_family="Futura", base_size=16)
    + theme(
        axis_line=element_line(color="#4d4d4d"),
        axis_ticks_major=element_line(color="#00000000"),
        axis_title=element_blank(),
        panel_spacing=0.09,
    )
)

interactive(plot) + save("docs/iframes/facet_wrap.html")