This is the contract for embedding a chart in a generated dashboard markdown
file. It replaces the previous ```mermaid fences. It is written for
whoever implements rendering in the BudgetKey viewer — nothing in this repo
renders these blocks yet except the local preview script described at the
bottom.
A fenced code block tagged plotly, containing one JSON object, fully
hardcoded (no server round-trip, no external data references):
```plotly
{
"data": [
{ "type": "scatter", "mode": "lines+markers", "name": "תקציב מקורי",
"x": [2016, 2017, 2018], "y": [41671518000, 47630805000, 48033338000] }
],
"layout": {
"title": "מגמה תקציבית לאורך זמן",
"xaxis": { "title": "שנה", "type": "category" },
"yaxis": { "title": "תקציב ב-₪", "rangemode": "tozero", "separatethousands": true }
}
}
```The object has two required keys and one optional one:
data— an array of Plotly trace objects, exactly as passed toPlotly.newPlot.chartis accepted as an alias fordata— BudgetKey's own item descriptors usechartfor this array (see the live example below), so either key name must work.layout— a Plotly layout object, merged into the renderer's own base layout (see "Renderer implementation" below).config(optional) — a Plotly config object, merged into the renderer's base config.
All numbers are bare JSON numerals — no ₪, no thousands separators, no
quoted strings for values. All text (titles, labels, trace names) is a plain
JSON string; standard JSON escaping (\" for an embedded quote) is all that
is ever needed — there is no analog to Mermaid's fragile label-quoting rules.
This is what BudgetKey's own Angular component does for the equivalent
descriptor-driven charts (budgetkey-app,
src/app/charts/chart-plotly/chart-plotly.component.ts), and what the
markdown-fence renderer should replicate:
const layout = Object.assign({ height: 600, font: { size: 10 } }, spec.layout);
const config = Object.assign({ responsive: true }, spec.config);
Plotly.newPlot(el, spec.data ?? spec.chart, layout, config);On a JSON parse failure, render the raw fence text as a <pre> block rather
than failing the whole page (same fallback behavior MermaidBlock.tsx uses
today for a bad Mermaid diagram).
Bundle: every trace type used by the generator (scatter, pie, bar)
is registered in plotly-basic, the same slim bundle BudgetKey already loads
from https://cdn.plot.ly/plotly-basic-2.26.1.min.js. No larger bundle is
required for this change. (Trace types like sankey, treemap, and
sunburst are not in plotly-basic — if a future dashboard wants a true
hierarchy visual instead of the flattened pie described below, that's the
trigger to switch to the full plotly-2.26.1.min.js.)
Three of the four fences a dashboard contains are emitted by
agent_engineering/blocks.py from phase 1's CSVs — the trend chart, the
top-10 items pie and the budget-sources pie. Their JSON is produced by
json.dumps, so escaping and bare numerals are guaranteed rather than
requested. Only the top-15 suppliers pie is still written by the synthesis
model, because its data comes from phase 2's agent rather than from a file.
One trace per budget series (original / after changes / executed), x =
years, y = amounts. Mirrors the live chart at
https://next.obudget.org/i/budget/C111/2026:
{
"data": [
{ "type": "scatter", "mode": "lines+markers", "name": "תקציב מקורי", "x": [2016, 2017], "y": [41671518000, 47630805000] },
{ "type": "scatter", "mode": "lines+markers", "name": "אחרי שינויים", "x": [2016, 2017], "y": [55624343000, 57373185000] }
],
"layout": {
"xaxis": { "title": "שנה", "type": "category" },
"yaxis": { "title": "תקציב ב-₪", "rangemode": "tozero", "separatethousands": true }
}
}Used both for the top-15-suppliers breakdown and for the budget-sources breakdown (see below).
{
"data": [
{ "type": "pie", "textinfo": "label+percent",
"labels": ["ספק א", "ספק ב", "אותי (ע\"ר)"],
"values": [1674159804.8, 893112482.5, 250000000] }
],
"layout": { "title": "התפלגות היקפי התקשרויות לפי ספק" }
}The original hackathon brief (Topic Page.md) asked for a Sankey diagram of
budget sources. This isn't used: the obudget site's own "sources" visuals
aren't Plotly Sankey diagrams, and sankey isn't in plotly-basic anyway.
Instead this section uses the same pie shape as above — one slice per
top-level office/program the hierarchy rolls up to, values = each branch's
total budget. The actual parent→child structure is conveyed separately by
the nested bulleted list of budget line items that follows this chart in the
template, not by the chart itself.
Available for any section that needs a ranked comparison (e.g. as an alternative to a pie when there are too many categories to read as slices):
{
"data": [
{ "type": "bar", "x": ["ספק א", "ספק ב", "ספק ג"], "y": [1674159804.8, 893112482.5, 250000000] }
],
"layout": { "yaxis": { "title": "תקציב ב-₪", "separatethousands": true } }
}langgraph-module/preview_plots.py extracts every ```plotly fence from a
generated report, validates it's parseable JSON, and renders a standalone
HTML page using the same plotly-basic bundle and layout/config merge
described above — see that script for usage. This is a development aid only;
it is not part of the contract above and the BudgetKey viewer doesn't need
to match its HTML output, only the newPlot call semantics.