An Altair configuration wrapper with perceptually uniform palettes and chart utilities for publication-ready figures.
Project description
dysonsphere
An Altair configuration wrapper with perceptually uniform palettes and chart utilities for publication-ready figures.
This is a personal project under active development, so there may be breaking changes between minor versions.
Installation
# uv
uv pip install dysonsphere
# pip
pip install dysonsphere
Requires Python 3.11+. Dependencies: altair, numpy, polars, scipy.
Quick start
import altair as alt
import polars as pl
import dysonsphere as theme # or: import dysonsphere
theme.options(chartWidth=300, chartHeight=200)
chart = (
alt.Chart(df)
.mark_point()
.encode(
x=alt.X("x:Q"),
y=alt.Y("y:Q"),
color=alt.Color("y:Q", scale=alt.Scale(range=theme.palette("blues"))),
)
)
theme.save(chart, "plots/myplot")
# writes: plots/myplot_light.png, plots/myplot_light.svg
# plots/myplot_dark.png, plots/myplot_dark.svg
# plots/myplot_vegalite.json
dysonsphere.options()
Call before building any Altair charts to configure global theme defaults.
theme.options() # apply defaults
theme.options( # custom configuration
chartWidth=400,
chartHeight=250,
fontSize=8,
grid=True,
palette="blues",
)
| Parameter | Default | Description |
|---|---|---|
angledX |
False |
Angle x-axis labels 45° |
axisOffset |
tickSize |
Distance between axis line and data area |
axisWidth |
0.25 |
Stroke width of axes, ticks, and rules |
bandPadding |
0.1 |
Inner and outer padding for ordinal bands |
chartFill |
"white" |
Background fill of the entire chart |
chartHeight |
100 |
Default chart height in pixels |
chartWidth |
100 |
Default chart width in pixels |
closed |
auto | Draw a border around the plot area. Auto-enabled when viewFill is set |
darkmode |
False |
Invert text and axis colors for dark backgrounds |
dashedLine |
False |
Render line marks dashed |
dashedRule |
True |
Render rule marks dashed |
dashedWidth |
[2, 2] |
Dash/gap pattern [dash, gap] in pixels |
font |
"HelveticaNeue" |
Font family for all labels and titles |
fontSize |
7 |
Font size in points |
fontWeight |
400 |
Font weight: 300 = light, 400 = normal, 700 = bold |
grid |
False |
Show axis grid lines |
gridColor |
"darkGray" |
Grid line color |
legend |
True |
Show legends |
legendOffset |
tickSize |
Distance between legend and chart edge |
legendStroke |
False |
Draw a border around the legend box |
markFill |
"black" |
Default fill color for marks |
markFillOpacity |
1.0 |
Default mark fill opacity |
markSize |
min(W, H) × 0.1 |
Mark size; for points, this is area in sq px |
markStroke |
"black" |
Default stroke color for marks |
markStrokeOpacity |
1 |
Default mark stroke opacity |
palette |
None |
Default color scheme applied to category, diverging, heatmap, and ramp scales. Accepts a key from colors or a raw list |
strokeCap |
"round" |
Stroke end cap: "butt", "round", or "square" |
ticks |
True |
Show axis ticks |
tickSize |
5 |
Tick length in pixels |
transparentBackground |
False |
Transparent chart background (overrides chartFill) |
verticalY |
False |
Rotate y-axis labels 90° |
viewFill |
None |
Fill color of the plot area only. Setting this auto-enables closed |
xTicks |
True |
Show ticks on the x-axis |
yTicks |
True |
Show ticks on the y-axis |
Palettes
All custom palettes are built in Oklab (Ottosson, A perceptual color space for image processing, 2020) for perceptual uniformity. They are stored in dysonsphere.colors, a plain dict[str, list[str]] mapping palette names to 12-stop hex lists (13 stops for diverging palettes).
Accessing palettes
from dysonsphere.palettes import colors
blues = colors["blues"] # list of 12 hex strings, light → dark
dysonsphere.palette()
Samples a slice or subset from any named palette.
theme.palette("blues") # all 12 stops
theme.palette("blues", n=5) # 5 evenly-spaced stops
theme.palette("blues", start=3) # stops 3–11
theme.palette("blues", end=6, step=2) # indices 0, 2, 4, 6
theme.palette("blues", n=4, reverse=True) # reversed
| Parameter | Default | Description |
|---|---|---|
name |
required | Key in colors |
n |
None |
Return n evenly-spaced stops (overrides step) |
start |
0 |
Index of the first stop to include |
end |
last | Index of the last stop to include (inclusive) |
step |
1 |
Step between indices (used when n is not set) |
reverse |
False |
Reverse the returned list |
Theme defaults
When no explicit scale= is set on a color encoding, Vega-Lite falls back to the theme's range defaults:
| Range type | Default palette | Used for |
|---|---|---|
category |
blues (even indices: 0, 2, 4, 6, 8, 10) |
Nominal/unordered groups |
ordinal |
blues |
Ordered discrete values |
ramp |
blues |
Sequential continuous (legend ramps) |
heatmap |
blues |
Rect/heatmap marks |
diverging |
redsblues |
Diverging scales |
Setting theme.options(palette="mypalette") overrides all five types simultaneously.
Available palettes
See the palette gallery for a visual overview of all palettes, or open docs/index.html locally.
Sequential — Single-hue (12 stops, light → dark):
blues, greens, purples, lavenders, violets, greys, reds, rose, oranges, browns, yellows, cyans, magentas, neongreens
Sequential — Single-hue 2 (12 stops, deeper saturation built with Oklab arc-length resampling):
blues2, greens2, purples2, lavenders2, violets2, greys2, reds2, roses2, oranges2, browns2, yellows2, cyans2, magentas2, neongreens2
Sequential — Multi-hue (12 stops, two or more hues blended in Oklab):
yellowgreen, ember, dusk, shoal, moss, GnBu, YlGnBu, candy, lagoon, bluestlagoon, bluerlagoon, bluelagoon
Diverging (13 stops, exact-white pivot at stop 6):
RdBu, RdYlBu, PuGn, MgGn, PkTe, GdBu, BrTe, BrGn
Diverging — Sequential pairs (13 stops, one sequential hue per arm):
greensblues, redsblues, redsgreens, redscyans, redslavenders, redsviolets, redsneongreens, rosesblues, rosescyans, rosesgreens, rosesneongreens, orangesblues, orangescyans, orangespurples, orangeslavenders, orangesviolets, orangesneongreens, yellowsblues, yellowspurples, yellowslavenders, brownsblues, brownsgreens, brownscyans, brownsneongreens, magentasneongreens, magentasgreens, magentasblues, magentascyans, violetsoranges, violetsyellows, purplesgreens, purplesblues, purplesneongreens, lavendersgreens, lavendersblues, lavendersneongreens, cyanspurples, cyanslavenders, cyansviolets, greysblues, greysreds, greysgreens, greyscyans, greysyellows, greysoranges, greysmagentas, greysviolets, greysneongreens, greyspurples, greyslavender, greysrose
Discrete:
nucleotides (5 colors: A, T, G, C, U), proteins (8 biochemical groups: hydrophobic, aromatic, positive, negative, polar, proline, glycine, cysteine)
Matplotlib ported (prefixed with mpl_):
mpl_viridis, mpl_plasma, mpl_inferno, mpl_magma, mpl_cividis, mpl_turbo, mpl_Blues, mpl_Greens, mpl_Greys, mpl_Oranges, mpl_Purples, mpl_Reds, mpl_YlGnBu, mpl_YlOrBr, mpl_YlOrRd, and more.
cmocean ported (prefixed with cmocean_):
cmocean_algae, cmocean_amp, cmocean_balance, cmocean_curl, cmocean_deep, cmocean_delta, cmocean_dense, cmocean_diff, cmocean_gray, cmocean_haline, cmocean_ice, cmocean_matter, cmocean_oxy, cmocean_phase, cmocean_rain, cmocean_solar, cmocean_speed, cmocean_tarn, cmocean_tempo, cmocean_thermal, cmocean_topo, cmocean_turbid
Saving charts
theme.save(chart, "plots/myplot")
# writes: plots/myplot_light.png, plots/myplot_light.svg
# plots/myplot_dark.png, plots/myplot_dark.svg
# plots/myplot_vegalite.json
Produces light and dark PNG and SVG files from a single call. SVG output is post-processed to flatten Vega's redundant <g> wrappers, making it easier to navigate in Illustrator. A Vega-Lite JSON spec is also saved by default for full reproducibility.
theme.save(chart, "myplot", ppi=1200) # default PPI; reduce for faster exports
theme.save(chart, "myplot", save_vega_spec=False) # skip the JSON spec
theme.save(chart, "myplot", description="Figure 1") # embed a description in the SVG
Custom marks
dysonsphere.mark_violin()
Violin plot with an embedded boxplot.
theme.options(chartWidth=300)
palette = theme.palette("lavenders", n=len(CATEGORIES))
chart = theme.mark_violin(df, "group", "value", CATEGORIES, palette=palette)
theme.save(chart, "violin")
| Parameter | Default | Description |
|---|---|---|
df |
required | Polars DataFrame |
x_col |
required | Grouping column name |
y_col |
required | Value column name |
categories |
required | Ordered list of group labels |
palette |
None |
Single color or list of colors for violin fills |
boxplot_size |
markSize × 0.8 |
Boxplot box width in pixels |
boxplot_color |
"black" |
Boxplot fill color |
fillOpacity |
theme default | Violin fill opacity |
stroke |
None |
Violin outline color (None = no outline) |
strokeWidth |
theme default | Violin outline width |
legend |
False |
Show a color legend |
angledX |
theme default | Angle x-axis labels |
steps |
200 |
KDE grid resolution per group |
dysonsphere.mark_strip()
Jittered or beeswarm points with a median tick and optional mean ± error bars.
chart = theme.mark_strip(df, "group", "value", CATEGORIES)
chart = theme.mark_strip(df, "group", "value", CATEGORIES, scatter="beeswarm")
| Parameter | Default | Description |
|---|---|---|
scatter |
"jitter" |
"jitter" (fast, random Gaussian) or "beeswarm" (collision-avoidance) |
palette |
None |
List of colors for points |
point_size |
theme markSize |
Point size in sq px |
jitter_scale |
4.0 |
Jitter standard deviation in pixels |
errorbars |
True |
Show mean ± error bars |
errorbar_extent |
"sem" |
"sem" or "sd" |
Statistical annotations
Add a p-value bracket between two groups using dysonsphere.pvalue_layer(). Combine with any chart using +.
ann = theme.pvalue_layer(
df, "group", "value", "Control", "Drug A",
test="mannwhitneyu",
categories=["Control", "Drug A", "Drug B"],
chartWidth=300,
y=210,
)
chart + ann
From a pre-computed p-value:
ann = theme.pvalue_layer(
group1="Control", group2="Drug A",
pvalue=0.023, y=210,
categories=CATEGORIES,
chartWidth=300,
)
| Parameter | Default | Description |
|---|---|---|
df |
None |
Polars DataFrame (required unless pvalue and y are both provided) |
x_col, y_col |
None |
Column names for groups and values |
group1, group2 |
required | Group labels to compare |
test |
"mannwhitneyu" |
Statistical test: "mannwhitneyu", "ttest_ind", "ttest_rel", "wilcoxon", "tukey_hsd" |
pvalue |
None |
Pre-computed p-value (skips the test) |
correction |
None |
"bonferroni" or None |
n_comparisons |
1 |
Number of comparisons for Bonferroni correction |
y |
auto | Y position of the bracket in data units |
y_pad |
5 |
Padding above the group max when y is auto-placed |
style |
"line" |
"line" (bar only) or "bracket" (bar + end ticks) |
categories |
inferred | Ordered list of all x-axis categories |
chartWidth |
theme default | Chart width used to compute text x position |
reverse |
False |
Flip the annotation below the bar |
decimals |
3 |
Decimal places in the p-value label |
Data transforms
Jitter
Adds random Gaussian x-offsets to each row, useful for strip plots.
df = theme.add_jitter_offsets(df, scale=5)
alt.Chart(df).mark_circle().encode(
x=alt.X("group:N"),
y=alt.Y("value:Q"),
xOffset=alt.XOffset("jitter_x:Q"),
)
| Parameter | Default | Description |
|---|---|---|
scale |
5.0 |
Standard deviation of jitter in pixels |
out_col |
"jitter_x" |
Output column name |
seed |
20220701 |
Random seed |
Beeswarm
Computes collision-avoiding x-offsets per group. Better than jitter for small n, but slower.
df = theme.add_beeswarm_offsets(
df,
y_col="value",
group_by=["group"],
height_px=200,
markSize=10,
)
| Parameter | Default | Description |
|---|---|---|
y_col |
required | Value column |
group_by |
required | Column(s) defining each beeswarm group |
height_px |
theme chartHeight |
Chart height in pixels |
markSize |
10 |
Point size (area in sq px) |
out_col |
"beeswarm_x" |
Output column name |
Development
Building palettes
scripts/build/build_palettes.py documents the Oklab recipes for all custom palette families and prints updated hex literals to stdout. Use this to calibrate or extend palettes.
# uv
uv run python scripts/build/build_palettes.py
# pip
python scripts/build/build_palettes.py
The four recipes are:
- Sequential single-hue — fix hue; sweep L from light to dark with C =
frac × Cmax(L, hue); arc-length resample to 12 stops. - Sequential multi-hue — interpolate
(L, hue)between keyframes; same chroma and arc-length logic. - Diverging — two arms meeting at an exact-white pivot; 13 stops so the white center lands exactly on the V-corner.
- Chroma-scaling — preserve L, scale
(a, b)by a constant to derive lighter variants.
Palette hex values live in dysonsphere/palettes.py as plain lists — no color math runs at import time.
Building the gallery
# uv
uv run python scripts/build/build_gallery.py
# pip
python scripts/build/build_gallery.py
Writes docs/index.html. Open in a browser to browse all palettes across 11 chart types.
Exporting swatches for Adobe Illustrator
# uv
uv run python scripts/build/build_swatches_for_illustrator.py
# pip
python scripts/build/build_swatches_for_illustrator.py
Generates scripts/import_palettes_to_illustrator.jsx. To import into Illustrator:
- Open or create a document in Adobe Illustrator.
- Go to File > Scripts > Other Script...
- Select
scripts/import_palettes_to_illustrator.jsx. - All palettes are added as named swatch groups in the Swatches panel.
Re-run this script after adding or modifying palettes in dysonsphere/palettes.py.
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 dysonsphere-0.5.0.tar.gz.
File metadata
- Download URL: dysonsphere-0.5.0.tar.gz
- Upload date:
- Size: 1.4 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.23 {"installer":{"name":"uv","version":"0.11.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eab117bb211896bc529179bcd8909876524688c8c590be2d45da72fe2da242f8
|
|
| MD5 |
a48cd552243f000fa3c2cf51e35b7b13
|
|
| BLAKE2b-256 |
4347289d084283b77d7c96d9396480add125e027fd42eda6e3ba7b4cee0aed1d
|
File details
Details for the file dysonsphere-0.5.0-py3-none-any.whl.
File metadata
- Download URL: dysonsphere-0.5.0-py3-none-any.whl
- Upload date:
- Size: 35.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.23 {"installer":{"name":"uv","version":"0.11.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
37e531df15ee91b4457287cd58b9d618ab623086fd3021ba33faba49c5770c78
|
|
| MD5 |
8a3d373c236d42d203e89f8808b9d9f2
|
|
| BLAKE2b-256 |
90d30033695ffa5f68cf54e7db57fa4a85707caa7e9c27d4e6bcf6f4c857c5f0
|