
ninejs
ninejs adds interactive behavior to plotnine charts with a minimal API. You can attach tooltips, hover grouping, and on click events directly from aes(), then export the result as a standalone HTML plot. All of this with just 2 lines of code!
- Works out of the box with Jupyter, Quarto, Marimo, and Shiny
- Includes a built-in preview in Positron
- Supports custom CSS and JavaScript
- Copy-pastable self contained documentation for AI and agents
Quick start¶
Specify the tooltip and hover_group 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, save
gg = (
ggplot(
data=anscombe_quartet,
mapping=aes(x="x", y="y", color="dataset", tooltip="dataset", hover_group="dataset"),
)
+ geom_point(size=7, alpha=0.5)
+ theme_minimal()
)
interactive(gg) + save("plot.html")
Installation¶
Examples¶
map_plot = (
ggplot()
+ geom_map(
data=regions,
mapping=aes(fill="region", tooltip="tooltip", hover_key="region"),
)
+ theme_void()
)
bar_plot = (
ggplot(bars, aes("reorder(region, value)", "value"))
+ geom_col(aes(fill="region", tooltip="tooltip", hover_key="region"))
+ theme_void()
)
plot = map_plot | bar_plot
interactive(plot) + save("docs/iframes/linked-map-bars.html")
gg = (
ggplot(df, aes(x="date", y="value", fill="group", tooltip="group"))
+ geom_area(alpha=0.8)
+ theme_minimal()
+ labs(title="Monthly Growth by Product", x="Date", y="Value", fill="Category")
+ scale_x_date(date_labels="%b")
+ theme(
figure_size=(10, 5),
plot_title=element_text(size=16, weight="bold"),
axis_title=element_text(size=11),
axis_text=element_text(size=10),
legend_title=element_text(size=11),
legend_text=element_text(size=10),
)
)
interactive(gg) + save("docs/iframes/area-chart.html")
from plotnine.data import economics
df = economics[economics["date"].dt.year >= 2000].copy()
df["tooltip"] = [
f"{date:%b %Y}<br>Saving rate: {value:.1f}%"
for date, value in zip(df["date"], df["psavert"], strict=True)
]
gg = (
ggplot(df, aes("date", "psavert"))
+ geom_line(color="#2f6f73", size=1)
+ geom_point(aes(tooltip="tooltip"), color="#d95f02", size=3, alpha=0.7)
+ labs(title="U.S. personal saving rate since 2000", x="", y="Saving rate (%)")
+ theme_minimal()
)
interactive(gg, hover_nearest=True) + save("docs/iframes/saving-rate.html")
anscombe_quartet["open_url"] = "window.open('https://www.ysunflower.com/')"
gg = (
ggplot(
data=anscombe_quartet,
mapping=aes(
x="x",
y="y",
color="dataset",
tooltip="dataset",
on_click="open_url",
),
)
+ geom_point(size=7, alpha=0.7)
+ theme_minimal()
)
interactive(gg) + save("docs/iframes/on-click-new-window.html")
gg = (
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(gg) + save("docs/iframes/facet_wrap.html")
goo_css = """
svg {filter: contrast(20);}
.point {
filter: blur(5px);
animation: goo 3s ease-in-out infinite alternate;
}
.point:nth-child(2n) {animation-duration: 4s;}
.point:nth-child(3n) {animation-duration: 8s;}
@keyframes goo {
from {transform: translate(-12px, -12px) scale(1);}
to {transform: translate(12px, 12px) scale(1.3);}
}
"""
interactive(gg) + css(goo_css) + save("docs/iframes/animation-art.html")
This is just a small subset of what's possible with ninejs. Check out the gallery and the guides for more.
Tools for AI and agents¶
A single-file overview of the ninejs API, written for AI/LLMs and coding agents. This file contains everything an agent needs to know to use ninejs properly!
The file is also available at this URL: llms.txt
A ninejs skill that your agents will know when to use automatically. Once installed, your agent will automatically know when to use the skill, or you can mention it in a prompt (inside Codex / Claude Code) using the $skill-name syntax:
- Install for Claude Code:
- Install for Codex:
Next steps?¶
For more in-depth explanations and feature overviews, check out: