Tufte-style templates and helpers for Plotly charts
Project description
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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nolegend-0.1.2.tar.gz.
File metadata
- Download URL: nolegend-0.1.2.tar.gz
- Upload date:
- Size: 115.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b08a68724a1c9e49de8ef8fa9a713b0ca032945aef84f9b4918dfe0f2cee1c05
|
|
| MD5 |
bba0c162293a5a330ab3039647c33924
|
|
| BLAKE2b-256 |
534e36609cf6496ea2efa7105ae814f39ab808d399d674af8709aae015168d4e
|
Provenance
The following attestation bundles were made for nolegend-0.1.2.tar.gz:
Publisher:
workflow.yml on mojzis/nolegend
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nolegend-0.1.2.tar.gz -
Subject digest:
b08a68724a1c9e49de8ef8fa9a713b0ca032945aef84f9b4918dfe0f2cee1c05 - Sigstore transparency entry: 1290819070
- Sigstore integration time:
-
Permalink:
mojzis/nolegend@9c0755001d87f279a6e8ffdd87bb794399d2f14b -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/mojzis
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@9c0755001d87f279a6e8ffdd87bb794399d2f14b -
Trigger Event:
push
-
Statement type:
File details
Details for the file nolegend-0.1.2-py3-none-any.whl.
File metadata
- Download URL: nolegend-0.1.2-py3-none-any.whl
- Upload date:
- Size: 9.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
40649f4611e7ce502d554a0bf9e3efd34d537d92bfd27be7a44e86fd6f2d7340
|
|
| MD5 |
4c5c4b7d2f854a4ef3407b93479e694a
|
|
| BLAKE2b-256 |
1c03c6772f03aa8e713cbd2592bc67efc0b67a179ac8447d23937cca23fde7a5
|
Provenance
The following attestation bundles were made for nolegend-0.1.2-py3-none-any.whl:
Publisher:
workflow.yml on mojzis/nolegend
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nolegend-0.1.2-py3-none-any.whl -
Subject digest:
40649f4611e7ce502d554a0bf9e3efd34d537d92bfd27be7a44e86fd6f2d7340 - Sigstore transparency entry: 1290819152
- Sigstore integration time:
-
Permalink:
mojzis/nolegend@9c0755001d87f279a6e8ffdd87bb794399d2f14b -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/mojzis
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@9c0755001d87f279a6e8ffdd87bb794399d2f14b -
Trigger Event:
push
-
Statement type: