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.Figureobjects 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_strongerfor each ofprimary,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
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 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
decc3c992e84793793c1593653e79ab308511757b84657bbaffab88d76e2a40d
|
|
| MD5 |
ba8b6d602029386c8745fb2d68d82272
|
|
| BLAKE2b-256 |
03ba3a5bb6054c9e996070885a88818a4e788e591e91771f5df79e4a6955adb9
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
52b79cf178791e307f8f368b0a302a939b3129e50408c85dde0ac064d16fb852
|
|
| MD5 |
a93ffc53f7b9c157cb8467088f9c53c6
|
|
| BLAKE2b-256 |
1c23a8a613eb1315951d5156fcdb9f0e13bc70c0e550cfc0a6d0eea4319f9472
|