nolegend
Remove the legend to become one.
Tufte-style templates and helpers for Plotly. One import, beautiful charts.
Based on Edward Tufte's The Visual Display of Quantitative Information: maximize data-ink ratio, remove chartjunk, direct-label instead of legends, use range frames, and apply colorblind-safe palettes.
Why this exists
There is no Tufte package for Plotly on PyPI. matplotlib has matplotx,
plotnine has theme_tufte(), ggplot2 has ggthemes — Plotly has nothing.
The closest built-in template (simple_white) removes gridlines but stops
there. The default Plotly output is noisy: legends, gridlines, aggressive
colors, titles that describe axes instead of insights.
nolegend fills that gap with opinionated defaults and easy overrides.
What this is NOT
- Not a general visualization framework. Plotly only.
- Not a dashboard framework. Use marimo for layout.
- Not a Tufte orthodoxy enforcer. We take the useful parts (data-ink ratio, range frames, direct labeling, small multiples) and skip the dogma.
- Not a replacement for domain judgment. The library guides chart selection and color use, but the human picks the story.
Install
uv add nolegend
Claude Code skill
nolegend ships a visualization skill that teaches Claude Code the px → go workflow, Tufte principles, color rules, and marimo patterns.
npx skills add mojzis/nolegend
Once installed, Claude Code will automatically apply nolegend conventions whenever you create Plotly charts.
Quick start
import plotly.express as px
import nolegend
nolegend.activate() # set as default template for all figures
df = px.data.gapminder().query("continent == 'Europe' and year == 2007")
fig = px.bar(df.nlargest(10, "gdpPercap"), x="country", y="gdpPercap")
fig.show()
The px → go workflow
Start fast with Plotly Express, then refine:
import plotly.express as px
import nolegend
# 1. Quick draft with px
fig = px.line(
df, x="year", y="revenue", color="product",
template="tufte",
)
# 2. Refine: axes span only the data
nolegend.range_frame(fig)
# 3. Refine: labels on the lines, not in a legend box
nolegend.direct_label(fig)
# 4. Refine: call out the key moment
nolegend.annotate_point(fig, x=2023, y=peak, text="Record quarter")
fig.show()
Color palettes
Six curated, colorblind-safe palettes:
| Palette | Use case |
|---|---|
qualitative |
Categorical data (default) |
ink |
Grayscale, maximum data-ink |
duo |
Two series (dark + red accent) |
earth |
Business/financial |
slate |
Technical/scientific |
sequential |
Ordered/continuous data |
# Use a specific palette
fig = px.line(df, ..., template=nolegend.with_palette("earth"))
# Spotlight: highlight one series, gray out the rest
colors = nolegend.QUALITATIVE.spotlight(index=2)
Hard ceiling of 5 colors per chart. Gray is a deliberate design tool, not a leftover.
Sparklines
Word-sized graphics, inline with text:
spark = nolegend.sparkline([1, 4, 2, 5, 3, 7], width=150, height=30)
spark.show()
Dark mode
nolegend.activate(dark=True)
# or per-figure:
fig = px.scatter(df, ..., template="tufte_dark")
In marimo notebooks
import marimo as mo
import nolegend
# Auto-detect marimo theme
nolegend.activate(dark=mo.app_meta().theme == "dark")
# Reactive plotly charts
fig = px.scatter(df, x="x", y="y", template="tufte")
chart = mo.ui.plotly(fig)
API
| Function | What it does |
|---|---|
activate(dark=False) |
Set tufte as default template |
with_palette(name, dark=False) |
Register + return template with specific palette |
range_frame(fig, pad=0.02) |
Trim axes to data range |
direct_label(fig, position="right") |
Label lines directly, remove legend |
sparkline(values, ...) |
Create a word-sized trend graphic |
annotate_point(fig, x, y, text) |
Arrow annotation on a data point |
strip_chartjunk(fig) |
Quick cleanup of any Plotly figure |
License
MIT
Metadata
Release files for nolegend 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| nolegend-0.1.2.tar.gz | 115.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| nolegend-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 124.6 kB
Release files / nolegend-0.1.2.tar.gz
| Download URL | nolegend-0.1.2.tar.gz |
|---|---|
| Size | 115.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b08a68724a1c9e49de8ef8fa9a713b0ca032945aef84f9b4918dfe0f2cee1c05
|
|
BLAKE2b-256 checksum How to use checksums |
534e36609cf6496ea2efa7105ae814f39ab808d399d674af8709aae015168d4e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 14, 2026.
Transparency logRelease files / nolegend-0.1.2-py3-none-any.whl
| Download URL | nolegend-0.1.2-py3-none-any.whl |
|---|---|
| Size | 9.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
40649f4611e7ce502d554a0bf9e3efd34d537d92bfd27be7a44e86fd6f2d7340
|
|
BLAKE2b-256 checksum How to use checksums |
1c03c6772f03aa8e713cbd2592bc67efc0b67a179ac8447d23937cca23fde7a5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 14, 2026.
Transparency log