Skip to content

Theme

The palette the workbench paints plotly, pyvista, xterm and Monaco with, and the colormap catalogue its pickers offer.

femforge_workbench.theme

The palette read from the dist's theme.json, painting plotly, pyvista, xterm and Monaco in the stylesheet's colours.

Classes:

  • Scheme –

    The two colour schemes theme.json carries.

  • Token –

    The stylesheet tokens the palette reads.

  • Section –

    The non-scheme sections of theme.json.

  • Font –

    The font stacks theme.json names.

  • Tokens –

    What theme.json carries: the colours of each scheme, the metrics in CSS pixels and the font stacks.

  • Metrics –

    The type sizes and chrome heights of the stylesheet, in CSS pixels.

  • Tones –

    The interaction colours derived from a palette, as hex.

  • Palette –

    The colours the panels share, read from one scheme of the tokens.

Functions:

  • rgb –

    The channels of a CSS colour written as a hex triplet, rgb()/rgba(), or white/black.

  • hex_color –

    The colour as an upper-case hex triplet, the form pyvista takes.

  • mix –

    The hex colour fraction of the way from onto to color.

  • luminance –

    The perceived brightness of a colour on the 0-255 scale.

  • rotate_hue –

    The colour with its hue rotated by a fraction of the wheel, as a hex triplet.

  • template_json –

    A registered plotly template (the default when unnamed) as plain JSON.

  • section –

    The nested object under key, empty when absent.

  • text –

    The string under key, else the fallback.

  • load_tokens –

    The tokens of the built dist; a missing dist names the build command.

Scheme

Bases: StrEnum

The two colour schemes theme.json carries.

Token

Bases: StrEnum

The stylesheet tokens the palette reads.

Section

Bases: StrEnum

The non-scheme sections of theme.json.

Font

Bases: StrEnum

The font stacks theme.json names.

Tokens dataclass

Tokens(colors: Mapping[str, Mapping[str, str]], metrics: Mapping[str, float], fonts: Mapping[str, str])

What theme.json carries: the colours of each scheme, the metrics in CSS pixels and the font stacks.

Methods:

  • from_json –

    Reads the tokens from theme.json's document.

from_json classmethod

from_json(document: Mapping[str, object]) -> Tokens

Reads the tokens from theme.json's document.

Source code in packages/femforge-workbench/src/femforge_workbench/theme.py
@classmethod
def from_json(cls, document: Mapping[str, object]) -> "Tokens":
    """Reads the tokens from theme.json's document."""
    colors = {str(scheme): {name: str(value) for name, value in section(document, scheme).items()} for scheme in Scheme}
    metrics = {name: float(cast("float", value)) for name, value in section(document, Section.METRICS).items()}
    fonts = {name: str(value) for name, value in section(document, Section.FONTS).items()}

    return cls(colors=colors, metrics=metrics, fonts=fonts)

Metrics dataclass

Metrics(size_body: float, size_label: float, bar_height: float, tab_height: float, space: float, row_height: float)

The type sizes and chrome heights of the stylesheet, in CSS pixels.

Methods:

  • from_tokens –

    Reads the metrics from the tokens, which name each field in kebab case.

from_tokens classmethod

from_tokens(tokens: Tokens) -> Metrics

Reads the metrics from the tokens, which name each field in kebab case.

Source code in packages/femforge-workbench/src/femforge_workbench/theme.py
@classmethod
def from_tokens(cls, tokens: Tokens) -> "Metrics":
    """Reads the metrics from the tokens, which name each field in kebab case."""
    return cls(**{field.name: tokens.metrics[field.name.replace("_", "-")] for field in fields(cls)})

Tones dataclass

Tones(raised: str, on_accent: str)

The interaction colours derived from a palette, as hex.

Palette dataclass

Palette(paper: str, plot: str, text: str, grid: str, accent: str, muted: str, chart: tuple[str, ...], dark: bool, metrics: Metrics, font: str, mono: str)

The colours the panels share, read from one scheme of the tokens.

Methods:

  • from_tokens –

    The palette of one scheme of the tokens.

  • load –

    The dist's palette in one scheme.

  • with_accent –

    The dist's palette with an accent of the caller's own.

  • colorway –

    The chart colours, then hues rotated evenly from the accent for the rest.

  • tones –

    The derived interaction colours: a raised surface and text that reads on the accent.

  • plotly_template –

    The base template (plotly_dark or plotly_white by scheme) repainted in this palette, at the label size, with the top margin a titleless figure needs.

  • install –

    Registers this palette's plotly template as the default, so every figure built afterwards is painted in it.

  • xterm_theme –

    The xterm ITheme colours.

Attributes:

  • is_dark (bool) –

    Whether the palette paints the dark scheme.

  • monaco_theme (str) –

    The built-in Monaco theme matching the scheme.

is_dark property

is_dark: bool

Whether the palette paints the dark scheme.

monaco_theme property

monaco_theme: str

The built-in Monaco theme matching the scheme.

from_tokens classmethod

from_tokens(tokens: Tokens, *, dark: bool) -> Palette

The palette of one scheme of the tokens.

Source code in packages/femforge-workbench/src/femforge_workbench/theme.py
@classmethod
def from_tokens(cls, tokens: Tokens, *, dark: bool) -> "Palette":
    """The palette of one scheme of the tokens."""
    colors = tokens.colors[Scheme.DARK if dark else Scheme.LIGHT]

    return cls(
        paper=colors[Token.BACKGROUND],
        plot=colors[Token.CARD],
        text=colors[Token.FOREGROUND],
        grid=colors[Token.BORDER],
        accent=colors[Token.PRIMARY],
        muted=colors[Token.MUTED_FOREGROUND],
        chart=tuple(colors[token] for token in CHART_TOKENS),
        dark=dark,
        metrics=Metrics.from_tokens(tokens),
        font=tokens.fonts[Font.SANS],
        mono=tokens.fonts[Font.MONO],
    )

load classmethod

load(*, dark: bool = True) -> Palette

The dist's palette in one scheme.

Source code in packages/femforge-workbench/src/femforge_workbench/theme.py
@classmethod
def load(cls, *, dark: bool = True) -> "Palette":
    """The dist's palette in one scheme."""
    return cls.from_tokens(load_tokens(), dark=dark)

with_accent classmethod

with_accent(accent: str, *, dark: bool = True) -> Palette

The dist's palette with an accent of the caller's own.

Source code in packages/femforge-workbench/src/femforge_workbench/theme.py
@classmethod
def with_accent(cls, accent: str, *, dark: bool = True) -> "Palette":
    """The dist's palette with an accent of the caller's own."""
    return replace(cls.load(dark=dark), accent=accent)

colorway

colorway(count: int = 6) -> list[str]

The chart colours, then hues rotated evenly from the accent for the rest.

Source code in packages/femforge-workbench/src/femforge_workbench/theme.py
def colorway(self, count: int = 6) -> list[str]:
    """The chart colours, then hues rotated evenly from the accent for the rest."""
    listed = list(self.chart[:count])
    extra = count - len(listed)

    return [*listed, *(rotate_hue(self.accent, (i + 1) / (extra + 1)) for i in range(extra))]

tones

tones() -> Tones

The derived interaction colours: a raised surface and text that reads on the accent.

Source code in packages/femforge-workbench/src/femforge_workbench/theme.py
def tones(self) -> Tones:
    """The derived interaction colours: a raised surface and text that reads on the accent."""
    on_accent = self.paper if luminance(self.accent) >= DARK_BELOW else self.text

    return Tones(raised=mix(self.text, self.paper, 0.12), on_accent=hex_color(on_accent))

plotly_template

plotly_template(base: str | None = None) -> Json

The base template (plotly_dark or plotly_white by scheme) repainted in this palette, at the label size, with the top margin a titleless figure needs.

Source code in packages/femforge-workbench/src/femforge_workbench/theme.py
def plotly_template(self, base: str | None = None) -> Json:
    """The base template (plotly_dark or plotly_white by scheme) repainted in this palette, at the label size, with the top margin a titleless figure needs."""
    template = template_json(base or ("plotly_dark" if self.dark else "plotly_white"))
    layout = section(template, "layout")
    axis_paint = {"gridcolor": self.grid, "zerolinecolor": self.grid, "linecolor": self.grid}
    repainted: Json = {
        **layout,
        "paper_bgcolor": self.paper,
        "plot_bgcolor": self.plot,
        "font": {**section(layout, "font"), "color": self.text, "family": self.font, "size": self.metrics.size_label},
        "colorway": self.colorway(),
        "margin": {**section(layout, "margin"), "t": TITLELESS_TOP_MARGIN},
        "xaxis": {**section(layout, "xaxis"), **axis_paint},
        "yaxis": {**section(layout, "yaxis"), **axis_paint},
    }

    return {**template, "layout": repainted}

install

install() -> None

Registers this palette's plotly template as the default, so every figure built afterwards is painted in it.

Source code in packages/femforge-workbench/src/femforge_workbench/theme.py
def install(self) -> None:
    """Registers this palette's plotly template as the default, so every figure built afterwards is painted in it."""
    pio.templates[TEMPLATE] = self.plotly_template()  # pyright: ignore[reportUnknownMemberType]  # plotly's template registry ships no stub
    pio.templates.default = TEMPLATE  # pyright: ignore[reportUnknownMemberType]

xterm_theme

xterm_theme() -> dict[str, str]

The xterm ITheme colours.

Source code in packages/femforge-workbench/src/femforge_workbench/theme.py
def xterm_theme(self) -> dict[str, str]:
    """The xterm ITheme colours."""
    return {"background": self.paper, "foreground": self.text, "cursor": self.accent, "selectionBackground": self.tones().raised}

rgb

rgb(color: str) -> tuple[int, int, int]

The channels of a CSS colour written as a hex triplet, rgb()/rgba(), or white/black.

Source code in packages/femforge-workbench/src/femforge_workbench/theme.py
def rgb(color: str) -> tuple[int, int, int]:
    """The channels of a CSS colour written as a hex triplet, rgb()/rgba(), or white/black."""
    color = color.strip().lower()
    if color in NAMED_COLORS:
        return NAMED_COLORS[color]

    if color.startswith("#"):
        digits = color[1:]
        if len(digits) == 3:
            digits = "".join(ch * 2 for ch in digits)

        return int(digits[0:2], 16), int(digits[2:4], 16), int(digits[4:6], 16)

    numbers: list[str] = re.findall(r"[\d.]+", color)
    r, g, b = (int(float(n)) for n in numbers[:3])

    return r, g, b

hex_color

hex_color(color: str) -> str

The colour as an upper-case hex triplet, the form pyvista takes.

Source code in packages/femforge-workbench/src/femforge_workbench/theme.py
def hex_color(color: str) -> str:
    """The colour as an upper-case hex triplet, the form pyvista takes."""
    red, green, blue = rgb(color)

    return f"#{red:02X}{green:02X}{blue:02X}"

mix

mix(color: str, onto: str, fraction: float) -> str

The hex colour fraction of the way from onto to color.

Parameters:

  • color (str) –

    The colour at a fraction of 1.

  • onto (str) –

    The colour at a fraction of 0.

  • fraction (float) –

    How far from onto toward color, from 0 to 1.

Source code in packages/femforge-workbench/src/femforge_workbench/theme.py
def mix(color: str, onto: str, fraction: float) -> str:
    """The hex colour `fraction` of the way from `onto` to `color`.

    Args:
        color: The colour at a fraction of 1.
        onto: The colour at a fraction of 0.
        fraction: How far from `onto` toward `color`, from 0 to 1.
    """
    channels = [round(a * fraction + b * (1 - fraction)) for a, b in zip(rgb(color), rgb(onto), strict=True)]

    return "#" + "".join(f"{channel:02X}" for channel in channels)

luminance

luminance(color: str) -> float

The perceived brightness of a colour on the 0-255 scale.

Source code in packages/femforge-workbench/src/femforge_workbench/theme.py
def luminance(color: str) -> float:
    """The perceived brightness of a colour on the 0-255 scale."""
    r, g, b = rgb(color)

    return 0.299 * r + 0.587 * g + 0.114 * b

rotate_hue

rotate_hue(color: str, turns: float) -> str

The colour with its hue rotated by a fraction of the wheel, as a hex triplet.

Source code in packages/femforge-workbench/src/femforge_workbench/theme.py
def rotate_hue(color: str, turns: float) -> str:
    """The colour with its hue rotated by a fraction of the wheel, as a hex triplet."""
    r, g, b = rgb(color)
    hue, lightness, saturation = colorsys.rgb_to_hls(r / 255, g / 255, b / 255)
    r, g, b = colorsys.hls_to_rgb((hue + turns) % 1, lightness, saturation)

    return f"#{round(r * 255):02X}{round(g * 255):02X}{round(b * 255):02X}"

template_json

template_json(name: str | None = None) -> Json

A registered plotly template (the default when unnamed) as plain JSON.

Source code in packages/femforge-workbench/src/femforge_workbench/theme.py
def template_json(name: str | None = None) -> Json:
    """A registered plotly template (the default when unnamed) as plain JSON."""
    key = name or str(pio.templates.default)  # pyright: ignore[reportUnknownMemberType, reportUnknownArgumentType]  # plotly's template registry ships no stub

    return cast("Json", pio.templates[key].to_plotly_json())  # pyright: ignore[reportUnknownMemberType]

section

section(source: Mapping[str, object], key: str) -> Json

The nested object under key, empty when absent.

Source code in packages/femforge-workbench/src/femforge_workbench/theme.py
def section(source: Mapping[str, object], key: str) -> Json:
    """The nested object under `key`, empty when absent."""
    value = source.get(key)
    if not isinstance(value, Mapping):
        return {}

    return dict(cast("Mapping[str, object]", value))

text

text(source: Mapping[str, object], key: str, fallback: str) -> str

The string under key, else the fallback.

Parameters:

  • source (Mapping[str, object]) –

    The JSON object to read.

  • key (str) –

    The key to read.

  • fallback (str) –

    The string returned when the value is missing or not a string.

Source code in packages/femforge-workbench/src/femforge_workbench/theme.py
def text(source: Mapping[str, object], key: str, fallback: str) -> str:
    """The string under `key`, else the fallback.

    Args:
        source: The JSON object to read.
        key: The key to read.
        fallback: The string returned when the value is missing or not a string.
    """
    value = source.get(key)
    if not isinstance(value, str):
        return fallback

    return value

load_tokens cached

load_tokens() -> Tokens

The tokens of the built dist; a missing dist names the build command.

Source code in packages/femforge-workbench/src/femforge_workbench/theme.py
@cache
def load_tokens() -> Tokens:
    """The tokens of the built dist; a missing dist names the build command."""
    path = SERVE_DIR / THEME_FILE
    if not path.exists():
        raise FileNotFoundError(f"{path} is missing: {BUILD_HINT}")

    return Tokens.from_json(cast("Mapping[str, object]", json.loads(path.read_text(encoding="utf-8"))))

femforge_workbench.colormaps

The colormap catalogue the field viewer's pickers offer, by type, with the favorites listed first.

Classes:

  • ColormapType –

    Enumeration of colormap types.

  • Colormap –

    One catalogue entry: its type, the name pyvista resolves and the sentence-case label the pickers show.

Attributes:

  • FAVORITES (tuple[str, ...]) –

    Suggested starting maps, listed first in the colormap pickers.

FAVORITES module-attribute

FAVORITES: tuple[str, ...] = ('viridis', 'haline', 'batlow', 'inferno', 'Black-Body Radiation', 'coolwarm', 'vik', 'Fast', 'turbo')

Suggested starting maps, listed first in the colormap pickers.

ColormapType

Bases: Enum

Enumeration of colormap types.

Colormap

Bases: NamedTuple

One catalogue entry: its type, the name pyvista resolves and the sentence-case label the pickers show.