mapi_plots¶
This package has a sanitizer and templates that use only JSON. These are in the core install.
The package also has recipes and converters. These are in the recipes extra.
Sanitizer¶
The JSON-only PlotSpec sanitizer (plan §10.3, step 1).
This is the control-plane half of plot safety. It depends only on mapi-spec and
jsonschema and never imports numpy, pandas, altair or plotly (enforced by import-linter
and a subprocess test), so mapi_server can run it on every plot it stores or serves.
from mapi_plots.sanitize import sanitize_plot result = sanitize_plot(plot_json) # PlotSpec | dict | str | bytes result.plot # the sanitized PlotSpec result.changes # what was rewritten (HTML stripped, ...)
Anything unsafe raises :class:SanitizeError (a MapiError) whose issues carry the
JSON pointer, the :class:SanitizeRule (in context["rule"]) and any user-supplied text in
untrusted_text. :func:check_plot returns the same issues without raising (for editors).
SanitizeResult
¶
A sanitized plot plus the list of changes the sanitizer made (severity info).
SanitizeError
¶
SanitizeError(
issues: list[Issue], detail: str | None = None
)
SanitizeLimits
¶
SanitizeLimits(
max_document_bytes: int = 16 * 1024 * 1024,
max_spec_bytes: int = 2 * 1024 * 1024,
max_depth: int = 48,
max_nodes: int = 200000,
max_string_bytes: int = 64 * 1024,
max_array_items: int = 100000,
max_expr_chars: int = 2000,
max_typed_array_bytes: int = 8 * 1024 * 1024,
max_issues: int = 50,
)
Caps applied before anything else looks at a plot document.
SanitizeRule
¶
Bases: StrEnum
Why the sanitizer rejected (or changed) part of a plot spec. Values are stable.
sanitize_plot
¶
sanitize_plot(
plot: PlotInput,
*,
limits: SanitizeLimits = DEFAULT_LIMITS,
validate_schema: bool = True,
) -> SanitizeResult
Validate and sanitize a plot document; raise :class:SanitizeError if it is unsafe.
check_plot
¶
check_plot(
plot: PlotInput,
*,
limits: SanitizeLimits = DEFAULT_LIMITS,
validate_schema: bool = True,
) -> list[Issue]
Every error and change the sanitizer would report, without raising.
sanitize_spec
¶
sanitize_spec(
kind: str,
spec: Mapping[str, Any],
*,
limits: SanitizeLimits = DEFAULT_LIMITS,
validate_schema: bool = True,
) -> tuple[dict[str, Any], list[Issue]]
Sanitize a bare Vega-Lite or Plotly spec (no envelope); returns (spec, changes).
Templates¶
Role/dim-bound plot templates (plan §10.1).
A template is an ordinary :class:~mapi_spec.plots.PlotSpec whose bindings say what to
plot in schema terms ({x: "dim:time", y: "role:target", facet: "dim:geo"}) instead of
naming one bundle's variables. :func:apply_template resolves the bindings against a
:class:~mapi_spec.schema.DataSchema and fills the placeholders in the spec, so one
user-authored "fan chart of the target over time, faceted by geo" applies to every model of a
family. This module is JSON-only (it runs in the control plane).
Placeholders inside spec:
{"$bind": "y"}- replaced by a Vega-Lite field definition{"field": ..., "type": ..., "title": ...}; dropped entirely when the channel is optional and unresolved (soencoding.facetsimply disappears for a model withoutgeo);"{{y}}"- the bound name;"{{y.field}}"- the name escaped for Vega-Lite field access (dots);"{{y.type}}"- the Vega-Lite type;"{{y.title}}"- name plus units. Placeholders may be embedded in longer strings (titles, captions).
BoundField
¶
BoundField(
channel: str,
expr: str,
kind: Literal["dim", "var", "coord", "stat"],
name: str,
vl_type: VlType,
units: str | None = None,
)
One binding resolved to a concrete dataset column.
apply_template
¶
apply_template(
plot: PlotSpec,
schema: DataSchema,
*,
optional: Iterable[str] = (),
title: str | None = None,
) -> PlotSpec
Resolve plot.bindings against schema and fill the spec's placeholders.
resolve_bindings
¶
resolve_bindings(
bindings: PlotBindings,
schema: DataSchema,
*,
optional: Iterable[str] = (),
) -> dict[str, BoundField | list[BoundField]]
Resolve every binding; tooltips resolve to lists. Optional channels may be missing.
template_issues
¶
template_issues(
plot: PlotSpec,
schema: DataSchema,
*,
optional: Iterable[str] = (),
) -> list[Issue]
Why a template does not apply to schema (empty list when it does).
Converters¶
Build :class:~mapi_spec.plots.PlotSpec envelopes from Altair charts and Plotly figures.
Extra recipes only (needs altair, plotly, numpy, pandas and pyarrow).
- :func:
from_altairmoves every inline dataset (Altair's consolidateddatasetsand anydata.values) into named Arrow datasets and rewrites the references to{"name": ...}; named data (alt.Data(name=...)) is filled fromdatasets=. - :func:
from_plotlydecodes plotly.py 6 typed arrays ({dtype, bdata, shape}) and inline data arrays into named Arrow datasets, one per trace (and array length), and binds them with{"$ref": "<dataset>.<column>"}. String columns get the inline-text rules here (HTML stripped to the Plotly allow-list, script/CSS URLs rejected) because the control-plane sanitizer never decodes Arrow; the plot iframe must still re-strip dataset strings.
Both run the result through the sanitizer unless sanitize=False. Datasets are base64 Arrow
IPC streams (:class:~mapi_spec.plots.InlineArrow); datetimes are stored as UTC
millisecond timestamps, which JavaScript reads as numbers.
from_altair
¶
from_altair(
chart: Any,
*,
id: str,
title: str | None = None,
datasets: Mapping[str, TableLike] | None = None,
persona: Persona = "both",
caption_plain: str | None = None,
bindings: PlotBindings | None = None,
recipe: str | None = None,
plugin: str | None = None,
author: str | None = None,
bundle_id: str | None = None,
sanitize: bool = True,
compact: bool = False,
) -> PlotSpec
Convert an Altair chart (or a Vega-Lite dict) into a sanitized PlotSpec.
from_plotly
¶
from_plotly(
fig: Any,
*,
id: str,
title: str | None = None,
persona: Persona = "both",
caption_plain: str | None = None,
bindings: PlotBindings | None = None,
recipe: str | None = None,
plugin: str | None = None,
author: str | None = None,
bundle_id: str | None = None,
keep_template: bool = False,
min_length: int = 1,
sanitize: bool = True,
) -> PlotSpec
Convert a Plotly figure (go.Figure or dict) into a sanitized PlotSpec.
Data arrays (typed or inline) move into Arrow datasets named trace<i> (frame<f>_
trace<i> for animation frames); arrays shorter than min_length stay inline. The
default plotly.py template is dropped unless keep_template (the renderer themes).
Recipes¶
The standard plot recipe catalog (plan §10.2). Extra recipes only.
Every recipe is a function (DataTree-like, settings) -> PlotSpec with its data in named
Arrow datasets (never URLs), a persona and a plain-language caption. They are generated in
the runner from a fitted bundle or a score output, so every plugin gets them for free:
============================ ==========================================================
core.data_overview series per variable, missingness heatmap, group sizes
core.prior_predictive prior predictive densities (+ fan over time)
core.convergence R-hat / ESS per parameter, worst-parameter table, trace, rank
core.posterior_forest point, 50% and 94% HDI per parameter
core.ppc predictive density overlay, PIT histogram, coverage
core.ppc_fan fit + forecast fan with the split line
core.fit_vs_actual predicted against actual
core.residuals_over_time actual minus predicted over time
core.decomposition stacked contributions and totals
core.model_comparison elpd forest (precomputed LOO table, or WAIC)
core.counterfactual scenario vs baseline band + total-change distribution
core.decision_distribution scalar decision objective per option
core.pair corner plot of a few parameters, divergences highlighted
core.ridge one density per level of a dim, ordered by mean
core.rank_matrix P(rank = k) per item and pairwise P(i above j)
core.calibration PIT-ECDF difference with an envelope, or a reliability diagram
core.state_sequence observed series above stacked state probabilities
core.roc_pr ROC and precision-recall curves of a binary classifier
core.confusion 2 x 2 confusion matrix at a probability threshold
core.loss_curve loss of an iterative fit (ADVI) per step, moving average
core.loo_pit LOO-PIT calibration and the PSIS Pareto k per observation
core.energy marginal energy vs energy transitions, E-BFMI per chain
core.prior_posterior prior and posterior densities per parameter, ranked by shift
============================ ==========================================================
The MMM domain pack lives in :mod:mapi_plots.packs.mmm and the tabular-ML pack
(ml.feature_importance) in :mod:mapi_plots.packs.ml, and the scientific-model pack
(sci.field_map, sci.vector_field, sci.profile_bands, sci.ensemble; Plotly) in
:mod:mapi_plots.packs.sci, with the map, tree and polar recipes in
:mod:mapi_plots.packs.sci_maps, the image and spectrum recipes in
:mod:mapi_plots.packs.sci_images and sci.point_pattern in
:mod:mapi_plots.packs.sci_points, the network pack (net.graph; Plotly) in
:mod:mapi_plots.packs.net, and the causal pack (causal.impact, causal.pre_fit,
causal.donor_weights, causal.event_study) in :mod:mapi_plots.packs.causal. All are
exposed through the mapi.plots entry-point group, so
mapi_sdk.registry.default_registry() finds them.
Recipe
¶
Recipe(
id: str,
title: str,
settings: type[S],
fn: Callable[[RecipeInput, S], Figure],
version: str = "1.0.0",
persona: Persona = "both",
requires_groups: tuple[str, ...] = (),
requires_representation: tuple[
Representation, ...
] = (),
summary: str = "",
tags: tuple[str, ...] = tuple(),
about: About | None = None,
example: ExampleFn | None = None,
)
RecipeSettings
¶
Bases: BaseModel
Common recipe options. Subclasses add their own (extra keys are rejected).
Figure
¶
Figure(
chart: Any,
datasets: dict[str, DataFrame],
caption: str,
title: str | None = None,
bindings: PlotBindings | None = None,
kind: Literal["auto", "vega-lite", "plotly"] = "auto",
)
What a recipe function returns.
chart is an Altair chart (or a Vega-Lite dict), or a Plotly figure (go.Figure or a
{data, layout, frames} dict). kind tells them apart; auto detects it.
get_recipe
¶
get_recipe(id: str) -> Recipe[Any]
Look a bundled recipe up by id (e.g. "core.ppc_fan").
standard_recipes
¶
standard_recipes() -> list[Recipe[Any]]
The core catalog (entry point mapi.plots: core).
render_plots
¶
render_plots(
data: Any,
refs: Iterable[str],
*,
registry: Any | None = None,
options: Mapping[str, Mapping[str, Any]] | None = None,
) -> RenderReport
Render every ref against data; problems become warnings (plugins: errors).
options maps a recipe id to its settings. registry is a mapi_sdk Registry
(plugin-provided recipes); without one only the bundled recipes resolve.
recipe
¶
recipe(
id: str,
*,
title: str,
settings: type[RecipeSettings] = RecipeSettings,
version: str = "1.0.0",
persona: Persona = "both",
requires_groups: Iterable[str] = (),
requires_representation: Iterable[Representation] = (),
summary: str = "",
tags: Iterable[str] = (),
about: About | None = None,
example: ExampleFn | None = None,
) -> Callable[
[Callable[[RecipeInput, Any], Figure]], Recipe[Any]
]
Decorator turning fn(inp, settings) -> Figure into a registered :class:Recipe.
about is the plot's About card (built-in recipes take theirs from
:data:mapi_spec.plot_abouts.PLOT_ABOUTS); example returns synthetic (data,
options) for the catalog's example render.
render_template
¶
render_template(
template: PlotSpec,
data: Any,
schema: DataSchema | None = None,
*,
group: str | None = None,
value: str | None = None,
optional: Iterable[str] = (),
dataset: str | None = None,
max_rows: int = 50000,
) -> PlotSpec
Fill a template's placeholders and its dataset from data; returns a sanitized plot.
value names the variable whose stat coordinate feeds stat: bindings (default:
the single target, else the first variable with a stat dim).