Skip to main content

Declarative, metadata-driven charting library for Python

Project description

orama

όραμα

Greek; noun

Vision, goal.

Declarative, metadata-driven charting library for Python

orama produces Plotly figures from structured Polars DataFrames using a pluggable strategy pattern, fields-metadata-driven field discovery, therismos-based query descriptors, and composable color strategies (using polychromos and armonia). It is designed to be embedded in any Python application regardless of domain model or data-access layer.


Overview

orama sits between your data layer and your presentation layer. You describe which fields to group and aggregate; orama builds the corresponding QueryParams for you to execute, then turns the resulting Polars DataFrames into fully themed, i18n-aware Plotly figures.

What orama does

  • Provides a catalogue of chart strategies: bar, grouped bar, line, categorical line, pie, sunburst, heatmap, boxplot, grouped boxplot, and choropleth map.
  • Derives query parameters (group-by fields, aggregations, expected schema) directly from the strategy configuration.
  • Renders themed, i18n-aware go.Figure objects from the query results you supply.

What orama does NOT do

  • Execute queries or fetch data — callers are responsible for running the queries and returning the results as Polars DataFrames.
  • Provide a web layer — dynamic WTForms form generation and HTTP serialization are available via orama[web] (orama.web).

Installation

pip install orama

Requirements: Python ≥ 3.11

Optional extras:

Extra Installs
orama[web] WTForms (web binding for orama.web)
orama[demo] orama[web] + Flask, Flask-WTF (for the demo application)

Core Concepts

Three-step workflow

Every chart follows the same workflow regardless of chart type:

1. Extract field metadata
       fields-metadata MetadataExtractor
       ──────────────────────────────────►  dict[str, FieldMetadata]
                                                        │
2. Instantiate a FigureStrategy                         │
       FigureStrategy(fields_metadata, ...)  ◄──────────┘
                        │
3. Get query params, execute, build
       strategy.get_query_params()     ──►  dict[str, QueryParams]  (you execute these)
       strategy.build(dfs, tr, theme)  ──►  FigureView  ← honours pre/post callbacks
       strategy.plot(dfs, tr, theme)   ──►  FigureView  ← raw render, no callbacks

Step 1 — Extract field metadata. Use fields-metadata's MetadataExtractor on your domain model to produce a dict[str, FieldMetadata] (a FieldsMetadataMap). This map drives field validation inside each strategy: categorical fields go to categorical variables, numeric fields go to numeric variables, and so on.

Step 2 — Instantiate a strategy. Choose the appropriate FigureStrategy subclass and pass it the field metadata, your chosen variables, options, and optional color rules. The constructor validates all inputs immediately.

Step 3 — Query, then plot. Call get_query_params() to obtain a dict[str, QueryParams]. Execute each query using your data layer (MongoDB, Polars, SQL — orama is agnostic) and collect the results as Polars DataFrames in a dict keyed by the same names. Pass that dict to build(dfs, tr, theme) to obtain a FigureView (recommended — honours any registered callbacks). Use plot(dfs, tr, theme) for a raw render with no callback side-effects.


Quick Start

The following example renders a simple bar chart from a toy Polars DataFrame.

from dataclasses import dataclass

import polars as pl
from babel.support import NullTranslations
from fields_metadata import MetadataExtractor
from polychromos.color import HSLColor

from orama.strategies.bar import BarChartStrategy
from orama.theme import Theme
from orama.variables import Aggregation

# ── Step 1: extract field metadata ────────────────────────────────────────────

@dataclass
class SalesRecord:
    region: str    # categorical — accepted by CategoricalVariable
    revenue: float # numeric    — accepted by NumericalAggregationVariable

fields_metadata = MetadataExtractor(SalesRecord).extract()

# ── Step 2: instantiate the strategy ──────────────────────────────────────────

strategy = BarChartStrategy(
    fields_metadata=fields_metadata,
    category_variables=["region"],
    aggregation=Aggregation(
        name="total_revenue",   # column name in the result DataFrame
        function="sum",         # therismos AggregationFunction value
        field="revenue",        # source field to aggregate
    ),
)

# ── Step 3: get query params, execute, and plot ───────────────────────────────

query_params = strategy.get_query_params()
# query_params["main"].group_fields   == ["region"]
# query_params["main"].aggregations   == [Aggregation(id="total_revenue", ...)]
# query_params["main"].dataframe_schema expresses the expected Polars schema

# Execute query with your data layer; here we build a toy DataFrame directly:
df = pl.DataFrame({
    "region": ["North", "South", "East", "West"],
    "total_revenue": [120.5, 98.3, 145.2, 87.6],
})

theme = Theme(colors={
    "background": HSLColor.from_hex("#ffffff"),
    "primary_color_normal": HSLColor.from_hex("#3b82f6"),
    "secondary_color_normal": HSLColor.from_hex("#f59e0b"),
})

view = strategy.plot(
    dfs={"main": df},
    tr=NullTranslations(),
    theme=theme,
)
view.figure.show()

The Aggregation.function string must be a valid value of therismos.grouping.AggregationFunction (e.g. "count", "sum", "average", "min", "max"). Refer to the therismos documentation for the complete list.


Labels

Every strategy accepts three universal text parameters and a set of variable-specific label parameters. All are optional strings that default to None; when None, each strategy computes a meaningful default from its configured variables.

Parameter Scope Description
title universal Figure title. Defaults to a generated description of the chart.
subtitle universal Figure subtitle rendered below the title (Plotly 5.15+).
caption universal Caption intended for <figcaption> in an HTML <figure> wrapper. Not applied to the Plotly figure — returned in FigureView.caption.
description universal Free multiline text explaining how to interpret the figure. Not applied to the Plotly figure — returned in FigureView.description.
category_label bar, grouped bar, pie, boxplot Label for the category axis / legend.
value_label bar, grouped bar, line, categorical line, pie, sunburst, heatmap, boxplot, grouped boxplot, choropleth map Label for the value axis / colorbar.
group_label grouped bar, grouped boxplot Legend title for the grouping variable.
country_label choropleth map Label for the country dimension.
date_label line X-axis label for the date dimension.
category_label line Legend title for the category dimension (only shown when category_variable is set).
x_label categorical line, heatmap X-axis label.
series_label categorical line Legend title for the series dimension (only shown when series_variable is set).
y_label heatmap Y-axis label.
path_label sunburst Reserved for future use.
from orama.strategies.bar import BarChartStrategy
from orama.variables import Aggregation

strategy = BarChartStrategy(
    fields_metadata=fields_metadata,
    category_variables=["region"],
    aggregation=Aggregation(name="total_revenue", function="sum", field="revenue"),
    # Universal labels
    title="Revenue by Region",
    subtitle="All regions · Q1 2025",
    caption="Source: Internal sales data. Figures in EUR.",
    description="Revenue is computed as the sum of invoiced amounts net of VAT.\nOnly regions with at least one transaction are shown.",
    # Variable-specific labels
    category_label="Region",
    value_label="Total Revenue (€)",
)

view = strategy.plot(dfs={"main": df}, tr=NullTranslations(), theme=theme)

# Render with caption and/or description
html = view.figure.to_html(full_html=False)
if view.caption:
    html = f"<figure>{html}<figcaption>{view.caption}</figcaption></figure>"
# view.description is available for custom rendering (e.g., a tooltip or popover)

Unit annotations

All strategies recognise FieldMetadata.extra["unit"] and automatically append the unit to the auto-generated default value label. When no explicit value_label is provided, the label becomes "<default label> (<unit>)" — for example "Sum of revenue (EUR)".

The unit is not appended when the caller provides an explicit value_label; the annotation only affects the fallback.

from fields_metadata import FieldMetadata, MetadataExtractor

metadata = MetadataExtractor().extract(MyModel)

# Annotate numeric fields with their units after extraction
metadata["revenue"].extra["unit"] = "EUR"
metadata["tax_rate"].extra["unit"] = "%"

# Now any strategy using `revenue` as its aggregation field will automatically
# display e.g. "Sum of revenue (EUR)" on the value axis when no explicit
# value_label is passed.
strategy = BarChartStrategy(
    fields_metadata=metadata,
    category_variables=["region"],
    aggregation=Aggregation(name="total_revenue", function="sum", field="revenue"),
)
# value axis label → "Sum of revenue (EUR)"

Callbacks

Two optional callbacks can be passed at construction time to hook into the rendering pipeline. Use build() — the recommended entry point — to honour them. plot() remains available as a raw render with no side-effects.

FigureStrategy(
    ...,
    pre_build_callback=fn,   # fires before plot()
    post_build_callback=fn,  # fires after plot()
)
view = strategy.build(dfs, tr, theme)

pre_build_callback

Fires before plot(). Mutations to payload["dfs"] change what gets plotted. Mutations to payload["title"], payload["subtitle"], payload["caption"], and payload["description"] are reflected in the rendered figure and the returned FigureView.

Payload structure:

payload
├── "dfs"          dict[str, pl.DataFrame]   Replace entries to change what's plotted
├── "strategy"     str                        e.g. "BarChartStrategy"  (read-only)
├── "title"        str | None                 Assign to override the figure title
├── "subtitle"     str | None                 Assign to override the figure subtitle
├── "caption"      str | None                 Assign to override FigureView.caption
└── "description"  str | None                 Assign to override FigureView.description

post_build_callback

Fires after plot(). Can mutate payload["figure"] or overwrite payload["caption"] / payload["description"] before FigureView is returned.

Payload structure:

payload
├── "figure"       go.Figure
├── "caption"      str | None       Assign to override FigureView.caption
├── "description"  str | None       Assign to override FigureView.description
├── "metadata"     (same structure as pre_build_callback)
└── "data"
    └── "<query_key>"
        ├── "columns"  list[str]
        └── "rows"     list[dict]

Examples

Example 1 — pre-build: LLM-generated data storytelling:

def storytelling_callback(payload):
    rows = payload["dfs"]["main"].to_dicts()
    payload["description"] = llm_client.generate(
        f"Write a one-sentence insight for a '{payload['title']}' chart: {rows}"
    )

strategy = BarChartStrategy(
    ...,
    title="Revenue by Region",
    pre_build_callback=storytelling_callback,
)
view = strategy.build(dfs={"main": df}, tr=tr, theme=theme)
# view.description is now populated directly from the pre callback

Example 2 — post-build: override caption and description:

def annotate(payload):
    payload["description"] = "Automatically generated insight."
    payload["caption"] = "Source: internal CRM data."

strategy = BarChartStrategy(..., post_build_callback=annotate)
view = strategy.build(dfs={"main": df}, tr=tr, theme=theme)
# view.caption == "Source: internal CRM data."

Example 3 — pre-build: mutate title and description in one callback:

def enrich(payload):
    rows = payload["dfs"]["main"].to_dicts()
    payload["title"] = f"Revenue by Region — {len(rows)} entries"
    payload["description"] = llm_client.generate(f"Summarise: {rows}")

strategy = BarChartStrategy(..., pre_build_callback=enrich)
view = strategy.build(dfs={"main": df}, tr=tr, theme=theme)
# The figure title and view.description both reflect the callback mutations

Chart Types Reference

Strategy Description
BarChartStrategy Single-series bar chart (vertical or horizontal)
GroupedBarChartStrategy Multi-series grouped or stacked bar chart
LineChartStrategy Time-series line chart grouped by calendar year/month
CategoricalLineChartStrategy Line chart over an ordered categorical X axis
PieChartStrategy Pie / doughnut chart
SunburstChartStrategy Multi-level hierarchical sunburst chart
HeatMapChartStrategy Two-dimensional categorical heat map
BoxplotChartStrategy Boxplot distribution chart per category
GroupedBoxplotChartStrategy Side-by-side boxplots grouped by a second categorical variable
ChoroplethMapChartStrategy Country-level choropleth world map

All strategies share the same base constructor signature and expose both build(dfs, tr, theme) (recommended — honours callbacks) and plot(dfs, tr, theme) (raw render), so they are fully interchangeable from the caller's perspective.


BarChartStrategy

Represents categorical data with rectangular bars whose heights are proportional to the aggregated values. Bars may be vertical or horizontal.

from orama.strategies.bar import BarChartStrategy
from orama.variables import Aggregation
from orama.enums import SortOrder

strategy = BarChartStrategy(
    fields_metadata=fields_metadata,
    category_variables=["region"],          # list[str] — one or more categorical fields
    aggregation=Aggregation(
        name="count",
        function="count",
        field=None,                         # None for count-only aggregations
    ),
    sort_by_value=SortOrder.DESCENDING,     # SortOrder.NONE | ASCENDING | DESCENDING
    horizontal=False,                       # bool — render bars horizontally
    min_value=None,                         # float | None — axis lower bound
    max_value=None,                         # float | None — axis upper bound
    mean_category_name="",                  # str — add a mean bar with this label, or ""
    mean_annotation=False,                  # bool — draw a mean line annotation
    color_rules=None,                       # list[ColorRule] | None
    title=None,                             # str | None — figure title
    subtitle=None,                          # str | None — figure subtitle
    caption=None,                           # str | None — figure caption (for <figcaption>)
    category_label=None,                    # str | None — X/Y axis label for the category
    value_label=None,                       # str | None — X/Y axis label for the value
)

get_query_params() returns a single "main" key. When mean_category_name or mean_annotation is set, a __total_count__ aggregation is added automatically.

Supported color selectors: ColorSelectorStrategy, ColorSelectByCategoryName, ColorSelectValuesBelow, ColorSelectValuesAbove, ColorSelectMaxValue, ColorSelectMinValue, ColorSelectValuesBelowMean, ColorSelectValuesAboveMean.

Supported color assigners: ColorAssignerByCategory, ColorAssignerByValue.


GroupedBarChartStrategy

Extends bar charts with a grouping variable that splits each category into separate sub-bars. Supports stacked, relative (100 %), and horizontal layouts.

from orama.strategies.grouped_bar import GroupedBarChartStrategy

strategy = GroupedBarChartStrategy(
    fields_metadata=fields_metadata,
    category_variables=["region"],
    group_variable="product_line",          # str — single categorical field for groups
    aggregation=Aggregation(name="revenue", function="sum", field="revenue"),
    stacked=False,                          # bool — stack bars instead of grouping
    relative=False,                         # bool — relative % (requires stacked=True)
    horizontal=False,
    color_rules=None,
    title=None,                             # str | None
    subtitle=None,                          # str | None
    caption=None,                           # str | None
    category_label=None,                    # str | None — axis label for the category
    value_label=None,                       # str | None — axis label for the value
    group_label=None,                       # str | None — legend title for the grouping variable
)

Supported color selectors: ColorSelectorStrategy, ColorSelectByCategoryName.

Supported color assigners: ColorAssignerByGroup.


LineChartStrategy

Plots one line per category value over a time axis grouped by calendar year and month. The date_variable must be a datetime field whose __year and __month derived fields are present in the fields_metadata map.

from orama.strategies.line import LineChartStrategy

strategy = LineChartStrategy(
    fields_metadata=fields_metadata,
    date_variable="created_at",             # str — a date or datetime field (DateTimeVariable)
    aggregation=Aggregation(name="count", function="count"),
    category_variable="status",             # str | None — splits data into lines
    min_value=None,
    max_value=None,
    color_rules=None,
    title=None,                             # str | None
    subtitle=None,                          # str | None
    caption=None,                           # str | None
    date_label=None,                        # str | None — X-axis label for the date dimension
    value_label=None,                       # str | None — Y-axis label for the value
    category_label=None,                    # str | None — legend title (when category_variable is set)
)

get_query_params() groups by created_at__year, created_at__month, and status.

Supported color selectors: ColorSelectorStrategy, ColorSelectByCategoryName.

Supported color assigners: ColorAssignerByCategory.


CategoricalLineChartStrategy

Plots lines over an ordered categorical X axis (e.g. fiscal quarters, ordinal labels). Unlike LineChartStrategy, the X axis is not calendar-based.

from orama.strategies.categorical_line import CategoricalLineChartStrategy

strategy = CategoricalLineChartStrategy(
    fields_metadata=fields_metadata,
    x_variables=["year", "quarter"],        # list[str] — ordered categorical X axis
    aggregation=Aggregation(name="revenue", function="sum", field="revenue"),
    series_variable="region",              # str | None — optional series split
    color_rules=None,
    title=None,                             # str | None
    subtitle=None,                          # str | None
    caption=None,                           # str | None
    x_label=None,                           # str | None — X-axis label
    value_label=None,                       # str | None — Y-axis label for the value
    series_label=None,                      # str | None — legend title (when series_variable is set)
)

Supported color selectors: ColorSelectorStrategy, ColorSelectByCategoryName.

Supported color assigners: ColorAssignerByCategory.


PieChartStrategy

Circular chart where arc length is proportional to the aggregated value. Setting hole > 0 produces a doughnut chart.

from orama.strategies.pie import PieChartStrategy

strategy = PieChartStrategy(
    fields_metadata=fields_metadata,
    category_variables=["region"],
    aggregation=Aggregation(name="count", function="count"),
    sort_by_value=SortOrder.NONE,
    hole=0.4,           # float in [0, 0.9] — doughnut hole fraction; 0 = full pie
    inner_overall=0.0,  # float — font size for the overall value in the hole; 0 = hidden
    color_rules=None,
    title=None,         # str | None
    subtitle=None,      # str | None
    caption=None,       # str | None
    category_label=None,  # str | None — reserved for future legend/hover use
    value_label=None,     # str | None — reserved for future legend/hover use
)

When hole > 0 and inner_overall > 0, get_query_params() returns both a "main" and an "overall" key.

Supported color selectors: ColorSelectorStrategy, ColorSelectByCategoryName, ColorSelectValuesBelow, ColorSelectValuesAbove, ColorSelectMaxValue, ColorSelectMinValue.

Supported color assigners: ColorAssignerByCategory, ColorAssignerByValue.


SunburstChartStrategy

Multi-level pie chart (sunburst) for hierarchical data. Each entry in path_variables represents one ring, innermost first.

from orama.strategies.sunburst import SunburstChartStrategy

strategy = SunburstChartStrategy(
    fields_metadata=fields_metadata,
    path_variables=["continent", "country"],  # list[str] — hierarchical path, innermost first
    aggregation=Aggregation(name="count", function="count"),
    sort_by_value=SortOrder.NONE,
    color_rules=None,
    title=None,       # str | None
    subtitle=None,    # str | None
    caption=None,     # str | None
    path_label=None,  # str | None — reserved for future use
    value_label=None, # str | None — reserved for future use
)

Supported color selectors: ColorSelectorStrategy, ColorSelectByCategoryName, ColorSelectValuesBelow, ColorSelectValuesAbove, ColorSelectMaxValue, ColorSelectMinValue.

Supported color assigners: ColorAssignerByCategory, ColorAssignerByValue.


HeatMapChartStrategy

Two-dimensional matrix colored by aggregated value. Accepts at most one ColorRule.

from orama.strategies.heatmap import HeatMapChartStrategy

strategy = HeatMapChartStrategy(
    fields_metadata=fields_metadata,
    x_category_variables=["month"],
    y_category_variables=["region"],
    aggregation=Aggregation(name="revenue", function="sum", field="revenue"),
    display_values=True,    # bool — annotate each cell with its numeric value
    color_rules=None,
    title=None,         # str | None
    subtitle=None,      # str | None
    caption=None,       # str | None
    x_label=None,       # str | None — X-axis label
    y_label=None,       # str | None — Y-axis label
    value_label=None,   # str | None — colorbar title
)

Supported color selectors: ColorSelectorStrategy (all entries — select the scale).

Supported color assigners: ColorAssignerByValue (defines the color scale and optional range bounds via range_min, range_max, range_center_at).


BoxplotChartStrategy

Statistical distribution chart showing Q1, median, Q3, and computed IQR fences per category. An optional overall boxplot can be added.

from orama.strategies.boxplot import BoxplotChartStrategy

strategy = BoxplotChartStrategy(
    fields_metadata=fields_metadata,
    category_variables=["region"],
    value_variable="revenue",       # str — a NonDerivedNumericalVariable field
    horizontal=False,
    overall_category_name="",       # str — label for an optional overall boxplot, or ""
    display_mean=False,             # bool — show mean marker
    display_sd=False,               # bool — show standard deviation (requires display_mean)
    color_rules=None,
    title=None,                     # str | None
    subtitle=None,                  # str | None
    caption=None,                   # str | None
    category_label=None,            # str | None — axis label for the category
    value_label=None,               # str | None — axis label for the value
)

get_query_params() aggregates Q1, median, Q3, mean, standard deviation, min, and max. When overall_category_name is set, a second "overall" query is added.

Supported color selectors: ColorSelectorStrategy, ColorSelectByCategoryName, ColorSelectValuesBelow, ColorSelectValuesAbove, ColorSelectMaxValue, ColorSelectMinValue, ColorSelectValuesBelowMean, ColorSelectValuesAboveMean.

Supported color assigners: ColorAssignerByCategory, ColorAssignerByValue.


GroupedBoxplotChartStrategy

Extends the standard boxplot with an additional grouping dimension, producing side-by-side boxplots per category — one per unique value of the grouping variable.

from orama.strategies.grouped_boxplot import GroupedBoxplotChartStrategy

strategy = GroupedBoxplotChartStrategy(
    fields_metadata=fields_metadata,
    category_variables=["region"],      # list[str] — multicategorical
    value_variable="revenue",           # str — NonDerivedNumericalVariable field
    group_variable="product_line",      # str — single categorical field for groups
    horizontal=False,
    display_mean=False,
    display_sd=False,                   # bool — requires display_mean=True
    color_rules=None,
    title=None,                         # str | None
    subtitle=None,                      # str | None
    caption=None,                       # str | None
    category_label=None,                # str | None — axis label for the category
    value_label=None,                   # str | None — axis label for the value
    group_label=None,                   # str | None — legend title for the grouping variable
)

get_query_params() returns a single "main" key.

Supported color selectors: ColorSelectorStrategy, ColorSelectByCategoryName.

Supported color assigners: ColorAssignerByGroup.


ChoroplethMapChartStrategy

Renders country-level data on an interactive world map using ISO 3166-1 alpha-2 country codes. No Mapbox token is required.

from orama.strategies.choropleth_map import ChoroplethMapChartStrategy
from orama.enums import GeoProjection
from orama.variables import Aggregation

strategy = ChoroplethMapChartStrategy(
    fields_metadata=fields_metadata,
    country_variable="country_code",    # str — CountryCodeVariable (ISO 3166-1 alpha-2)
    aggregation=Aggregation(name="count", function="count"),
    map_style=GeoProjection.EQUIRECTANGULAR,  # GeoProjection enum
    fit_bounds=False,                   # bool — auto-zoom to countries with data
    color_rules=None,
    title=None,                         # str | None
    subtitle=None,                      # str | None
    caption=None,                       # str | None
    country_label=None,                 # str | None — label for the country dimension
    value_label=None,                   # str | None — colorbar title
)

get_query_params() returns a single "main" key.

Supported color selectors: ColorSelectorStrategy, ColorSelectByCategoryName, ColorSelectValuesBelow, ColorSelectValuesAbove, ColorSelectMaxValue, ColorSelectMinValue, ColorSelectValuesBelowMean, ColorSelectValuesAboveMean.

Supported color assigners: ColorAssignerByCategory, ColorAssignerByValue.


Variables

Variables describe the roles that DataFrame fields play in a chart. Each strategy declares a fixed _variables map; callers select fields from their metadata to fill those roles.

Aggregation dataclass

from orama.variables import Aggregation

Aggregation(
    name="revenue_sum",   # str — output column name in the result DataFrame
    function="sum",       # str — therismos AggregationFunction value
    field="revenue",      # str | None — source field; None for count-only aggregations
)

Variable types

Class Candidate fields
CategoricalVariable Fields where FieldMetadata.categorical is True
NumericalVariable Fields where FieldMetadata.numeric is True
NonDerivedNumericalVariable Numeric fields that are not derived (FieldMetadata.derived is False)
NumericalAggregationVariable Same as NonDerivedNumericalVariable; used as aggregation targets
DateTimeVariable date or datetime fields for which {field}__year and {field}__month derived fields are present in the metadata map; or standalone integer fields annotated with suggested_validation of "year" or "month"
CountryCodeVariable Fields with extra["suggested_validation"] == "iso_a2" (ISO 3166-1 alpha-2 country codes)

All variable types validate the fields passed by the caller at strategy construction time and raise ValueError if an invalid field is supplied.


Options

Options tune chart appearance and behaviour. Each strategy declares a fixed _options map and validates option values at construction time.

Class Python type Notes
BooleanOption bool Default: False unless declared otherwise
FloatOption float | int Supports optional min_value / max_value bounds
IntOption int Supports optional min_value / max_value bounds
StringOption str Default: None unless declared otherwise
SortOrderOption SortOrder Default: SortOrder.NONE
LinePlacementOption LinePlacement Default: LinePlacement.OVERLAPPING
LineInterpolationOption LineInterpolation Default: LineInterpolation.LINEAR
LineSortOrderOption LineSortOrder Default: LineSortOrder.NONE
GeoProjectionOption GeoProjection Default: GeoProjection.NATURAL_EARTH

Enums

SortOrder

from orama.enums import SortOrder

SortOrder.NONE        # 0 — no sorting applied
SortOrder.ASCENDING   # 1
SortOrder.DESCENDING  # -1

LinePlacement

Controls how multiple line series are positioned relative to each other.

from orama.enums import LinePlacement

LinePlacement.OVERLAPPING       # lines drawn on top of each other (default)
LinePlacement.STACKED           # lines stacked additively
LinePlacement.STACKED_RELATIVE  # lines stacked as relative percentages (100 %)
LinePlacement.RIDGELINE         # lines offset vertically (ridgeline / joy plot)

LineInterpolation

Controls the interpolation mode between data points on a line trace.

from orama.enums import LineInterpolation

LineInterpolation.LINEAR   # straight segments between points (default)
LineInterpolation.SPLINE   # smooth curved interpolation
LineInterpolation.STEP_HV  # horizontal then vertical step ("hv")
LineInterpolation.STEP_VH  # vertical then horizontal step ("vh")

LineSortOrder

Controls how line series or categories are sorted in the legend.

from orama.enums import LineSortOrder

LineSortOrder.NONE              # no sorting (default)
LineSortOrder.ALPHABETICAL_ASC  # alphabetically ascending
LineSortOrder.ALPHABETICAL_DESC # alphabetically descending
LineSortOrder.FIRST_VALUE_ASC   # ascending by first data-point value
LineSortOrder.FIRST_VALUE_DESC  # descending by first data-point value
LineSortOrder.LAST_VALUE_ASC    # ascending by last data-point value
LineSortOrder.LAST_VALUE_DESC   # descending by last data-point value

GeoProjection

Controls the map projection used by ChoroplethMapChartStrategy.

from orama.enums import GeoProjection

GeoProjection.NATURAL_EARTH    # Natural Earth (default for GeoProjectionOption)
GeoProjection.MERCATOR         # Mercator
GeoProjection.ORTHOGRAPHIC     # Orthographic
GeoProjection.EQUIRECTANGULAR  # Equirectangular (default in ChoroplethMapChartStrategy)
GeoProjection.ROBINSON         # Robinson
GeoProjection.KAVRAYSKIY7      # Kavrayskiy VII

Color System

Every color rule is a ColorRule — a pair of one selector (which rows to affect) and one assigner (what colors to apply to those rows). Rules are applied in order; later rules override earlier ones for the selected rows.

from orama.color import (
    ColorRule,
    ColorSelectMaxValue,
    ColorAssignerByCategory,
)
from polychromos.color import HSLColor

rule = ColorRule(
    assigner=ColorAssignerByCategory(
        anchor_colors=[HSLColor.from_hex("#ef4444")],
    ),
    selector=ColorSelectMaxValue(),  # highlight only the maximum bar
)

strategy = BarChartStrategy(
    fields_metadata=fields_metadata,
    category_variables=["region"],
    aggregation=Aggregation(name="count", function="count"),
    color_rules=[rule],
)

Selectors

Class Selects
ColorSelectorStrategy All entries (use as first rule to override defaults)
ColorSelectByCategoryName(names) Entries whose category name is in names
ColorSelectValuesBelow(threshold) Entries with value ≤ threshold
ColorSelectValuesAbove(threshold) Entries with value ≥ threshold
ColorSelectMinValue() Entry/entries with the minimum value
ColorSelectMaxValue() Entry/entries with the maximum value
ColorSelectValuesBelowMean() Entries with value ≤ weighted mean (bar charts only)
ColorSelectValuesAboveMean() Entries with value ≥ weighted mean (bar charts only)

Assigners

Class Assigns
ColorAssignerByCategory(anchor_colors, shuffle=False) One color per distinct category, interpolated from anchor colors; shuffle=True alternates colors across categories
ColorAssignerByGroup(anchor_colors, shuffle=False) One color per distinct group; shuffle=True alternates colors across groups
ColorAssignerByValue(anchor_colors, range_min, range_max, range_center_at) Continuous color scale mapped to values; optionally centered at a pivot

anchor_colors is an HSLColorSequence from polychromos. When two colors are provided and more entries exist, colors are interpolated via cylindrical shortest-path. When more than two colors are provided, a multi-stop color scale is constructed.

Each strategy documents which selectors and assigners it accepts. Passing an unsupported combination raises ValueError at construction time.


Theming

Theme wraps an armonia Theme and exposes colors to chart strategies.

from orama.theme import Theme
from polychromos.color import HSLColor

theme = Theme(colors={
    # Required for all charts
    "background":             HSLColor.from_hex("#1e293b"),
    "primary_color_normal":   HSLColor.from_hex("#3b82f6"),
    # Optional — used by strategies and computed color variants
    "secondary_color_normal": HSLColor.from_hex("#f59e0b"),
    "accent_color_normal":    HSLColor.from_hex("#10b981"),
})

When "background" is provided, the following computed colors are automatically registered:

  • background_inverted, background_inverted_mild, background_inverted_milder, background_inverted_faint
  • {prefix}_color_softer, {prefix}_color_stronger for each of primary, secondary, accent

Use theme.get_color(name) to retrieve any named or computed color as an HSLColor. Use theme.get_all_colors() to list all registered colors.

The theme is passed to strategy.plot(dfs, tr, theme) and applied to every figure via a Plotly template derived from plotly_white.


Internationalization

All strategies accept a babel.support.Translations object as the tr argument in plot(). It is used to translate month names and day-of-week labels in time-based charts.

When internationalization is not needed, pass NullTranslations:

from babel.support import NullTranslations

view = strategy.plot(dfs={"main": df}, tr=NullTranslations(), theme=theme)

To load real translations, use Babel's standard Translations.load():

from babel.support import Translations

tr = Translations.load("locale", locales=["fr"])
view = strategy.plot(dfs={"main": df}, tr=tr, theme=theme)

Strategy Introspection

Every strategy class exposes a describe() class method that returns a FigureStrategyDescription without requiring an instance:

from orama.strategies.bar import BarChartStrategy
from orama.strategies.base import FigureStrategyDescription

desc: FigureStrategyDescription = BarChartStrategy.describe(fields_metadata)

desc.name          # "Bar Chart"
desc.description   # human-readable strategy description
desc.variables     # dict[str, dict[str, Any]] — variable descriptors
desc.options       # dict[str, dict[str, Any]] — option descriptors
desc.colors        # ColorStrategyDescription — supported selectors and assigners

Each variable descriptor includes type, name, description, map_to, candidate_fields, and (for categorical variables) multicategorical, ordered, optional. Each option descriptor includes type, name, description, map_to, default_value, and value_type.

get_query_params() returns a dict[str, QueryParams]. Inspecting each QueryParams reveals the group_fields, aggregations, and dataframe_schema that your data layer must satisfy:

params = strategy.get_query_params()
for query_name, qp in params.items():
    print(query_name, qp.group_fields, qp.dataframe_schema)

Modebar Configuration

Every strategy exposes a modebar_config() instance method that returns a serialisable Plotly config dict. The web layer passes this to Figure.to_html(config=...) when rendering each figure. By default the config:

  • Hides the Plotly logo (displaylogo: False).
  • Removes the box-select and lasso tools (modeBarButtonsToRemove).

The demo additionally injects a Download SVG button next to the default PNG button via a post_script using Plotly.react() (JavaScript callbacks cannot be serialised to JSON and therefore cannot be part of the Python config dict).

Override modebar_config() in a subclass to customise the modebar for a specific strategy:

class MyBarChartStrategy(BarChartStrategy):
    def modebar_config(self) -> dict:
        cfg = super().modebar_config()
        cfg["modeBarButtonsToRemove"].append("zoom2d")
        return cfg

Web Layer

orama.web.FigureView binds a WTForms form to a strategy class and provides serialization, deserialization, and instantiation from form data.

from orama.web.figureview import FigureView
from orama.strategies.bar import BarChartStrategy

# Create from HTTP form data (POST)
view = FigureView(
    figure_strategy=BarChartStrategy,
    fields_metadata=fields_metadata,
    dataset_name="sales",
    formdata=request.form,
)

# Instantiate the strategy from the submitted form
strategy = view.instantiate_figure_strategy(theme)

# Serialize to a JSON-serializable dict (for persistence)
state = view.serialize()

# Reconstruct from a previously serialized dict
view = FigureView.deserialize(
    data=state,
    strategy_registry={"BarChartStrategy": BarChartStrategy},
    fields_metadata_registry={"sales": fields_metadata},
)

The full web binding — dynamic WTForms Form subclasses, color rule forms, and HTTP serialization — is available via pip install 'orama[web]'.


Dependencies

Package Version Role
plotly[express] ~6.3 Primary rendering backend (go.Figure)
polars ~1.33 DataFrame manipulation and schema validation
pandas ~2.3 Plotly bridge (df.to_pandas() at plot boundary)
pyarrow ~22.0 Polars/Pandas interchange
fields-metadata ≥0.1 Field discovery and metadata (FieldMetadata, MetadataExtractor)
therismos ≥0.1 GroupSpec, AggregationSpec, SortSpec
polychromos ~1.4 Color manipulation: HSL, palettes, sequences, scales
armonia ~1.1 Theme color composition and computed color functions
babel ~2.17 i18n and translations

License

This project is licensed under the MIT license.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

orama-1.1.1.tar.gz (72.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

orama-1.1.1-py3-none-any.whl (73.7 kB view details)

Uploaded Python 3

File details

Details for the file orama-1.1.1.tar.gz.

File metadata

  • Download URL: orama-1.1.1.tar.gz
  • Upload date:
  • Size: 72.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","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":null}

File hashes

Hashes for orama-1.1.1.tar.gz
Algorithm Hash digest
SHA256 decc3c992e84793793c1593653e79ab308511757b84657bbaffab88d76e2a40d
MD5 ba8b6d602029386c8745fb2d68d82272
BLAKE2b-256 03ba3a5bb6054c9e996070885a88818a4e788e591e91771f5df79e4a6955adb9

See more details on using hashes here.

File details

Details for the file orama-1.1.1-py3-none-any.whl.

File metadata

  • Download URL: orama-1.1.1-py3-none-any.whl
  • Upload date:
  • Size: 73.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","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":null}

File hashes

Hashes for orama-1.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 52b79cf178791e307f8f368b0a302a939b3129e50408c85dde0ac064d16fb852
MD5 a93ffc53f7b9c157cb8467088f9c53c6
BLAKE2b-256 1c23a8a613eb1315951d5156fcdb9f0e13bc70c0e550cfc0a6d0eea4319f9472

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page