Skip to content

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
├── "storytelling_context"  str | None                 Assign to override FigureView.storytelling_context
├── "color_rules"           list[dict]                 Serialised color rules  (read-only)
├── "variables"             dict[str, dict]            Variable name/description/value map  (read-only)
└── "options"               dict[str, dict]            Option name/description/value map    (read-only)

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
├── "storytelling_context"  str | None       Assign to override FigureView.storytelling_context
├── "metadata"              dict[str, Any]   Strategy metadata snapshot  (read-only)
│   ├── "strategy", "title", "subtitle", "caption", "description",
│   └── "storytelling_context", "color_rules"
└── "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["storytelling_context"] = 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.storytelling_context 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

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