Skip to content
dysonsphere

Palettes

Every dysonsphere palette is built in Oklab and resampled by arc length, so sequential ramps step evenly in perceived lightness and diverging ramps balance around a neutral midpoint. Pass a palette name anywhere a palette= argument is accepted, or pull colors directly with ds.palette(name, n).

ds.palette(name, n) returns n evenly spaced stops; start, end, step, and reverse give finer control. Feed the result into any Altair scale=alt.Scale(range=...):

import polars as pl
import altair as alt
import dysonsphere as ds
ds.theme()
# palette() slices/samples any of the 300+ palettes: n evenly spaced stops,
# or start/end/step/reverse for full control.
df = pl.DataFrame({"dose": ["0", "1", "10", "100"], "response": [8.0, 21.0, 55.0, 89.0]})
chart = (
alt.Chart(df)
.mark_bar()
.encode(
x=alt.X("dose:N", sort=None, title="Dose (nM)"),
y=alt.Y("response:Q", title="Response (%)"),
color=alt.Color(
"dose:N", legend=None, sort=None,
scale=alt.Scale(range=ds.palette("ember", 4)),
),
)
)

ds.categorical(members=n) returns the CVD-robust qualitative palette (blue / pink / yellow / green) sized to your group count. The default (members=1) is a flat, tier-major palette - adjacent categories differ in hue, for unrelated groups:

import dysonsphere as ds
from vega_datasets import data
# categorical(members=n) returns the CVD-robust qualitative palette sized to
# your group count (blue / pink / yellow / green).
ds.theme()
cars = ds.ensure_polars(data.cars()).drop_nulls(["Miles_per_Gallon"])
origins = ["USA", "Europe", "Japan"]
chart = ds.mark_strip(
cars, "Origin", "Miles_per_Gallon", origins,
palette=ds.categorical(3), yTitle="Miles per gallon",
)

For paired data (a treatment measured pre/post, two timepoints per condition), pass members=2..10: the palette becomes hue-major, so each consecutive block of members categories is one hue climbing in lightness. Sort the members adjacent and each group reads as a family:

import altair as alt
import polars as pl
import dysonsphere as ds
ds.theme(chartWidth=170, xLabelAngle=-45)
# Paired data: each treatment measured at two timepoints. categorical(members=2) returns a
# hue-major palette - each consecutive pair of categories is one hue climbing in lightness, so
# related bars read as a group. (members=1, the default, gives the tier-major flat palette where
# adjacent categories differ in hue - for UNRELATED groups.)
df = pl.DataFrame(
{
"condition": ["A pre", "A post", "B pre", "B post",
"C pre", "C post", "D pre", "D post"],
"response": [4.2, 7.8, 3.9, 6.1, 5.0, 9.2, 4.4, 5.5],
}
)
chart = (
alt.Chart(df)
.mark_bar()
.encode(
x=alt.X("condition:N", sort=None, title=None),
y=alt.Y("response:Q", title="Response (AU)"),
color=alt.Color(
"condition:N",
sort=None,
scale=alt.Scale(range=ds.categorical(members=2)),
legend=None,
),
)
)

The Palette browser shows all 300+ palettes as filterable swatch strips, with a live preview - click any swatch to copy its hex codes and see the charts restyled with it.

ds.export_swatches() writes an Illustrator ExtendScript and an .ase swatch file for the whole catalogue (or a subset), so the exact colors are available in your vector editor. See the Palettes reference.