Skip to main content

Squillify development is paused indefinitely. Read why, and discover what this experiment achieved. Read the story

MicroSkills Reference

Waterfall chart

chart.waterfall

ProductionBounded custom (Class B)Deterministicstrictfastv1.0.0
No network

Overview

Pass an opening balance, the movements between, and a closing total — we draw the bridge and return embeddable SVG. Values are decimal strings and the running totals are computed in exact decimal, so a bridge that adds up in your ledger adds up here. State your own subtotals and totals and each one is reconciled: a figure that does not follow from its own steps is refused, naming both numbers, rather than drawn. A delta can carry contributors to show what drove it. Defaults include canvas 800×480, connectors on, and a domain that includes zero. Do not use for unordered category comparison (use chart.bar), a trend over time (use chart.line), or weighted flows between stages (use chart.sankey). svg is a complete static, script-free chart. Mark highlight, the dotted guide, and the value panel are not in that SVG, and entry motion, focus, and updates stay inactive there. Pass the entire output.behavior.artifact unchanged to SquillifyChartRuntime.mount(container, artifact, options), then call the returned handle's replay() once to play the entry. A later value change uses update(nextArtifact, {intent}). Injecting svg directly leaves hover and motion off. Interaction is on by default for every host chart: after mount, pointer and keyboard focus open the value panel and the dotted guide. Set behavior.interaction.enabled to false to turn that off. motion.preset accepts only the family's entrance.

A bridge drawn by hand is a bridge nobody checked. We compute the running totals in exact decimal, reconcile the figures you assert against them, and refuse the chart when they disagree — because a waterfall whose total does not follow from its own steps looks right to every reader who verifies it.

Reliability

Identical input always produces the same bridge, colours, and SVG; the arithmetic is exact decimal, so the published totals are reproducible. Declared failures: CHART_RECONCILIATION_MISMATCH — trigger: see the error details for this code; retryable: no; unchanged input: will not succeed; next: follow the recovery intent in the error details. CHART_SEMANTIC_MISMATCH — trigger: see the error details for this code; retryable: no; unchanged input: will not succeed; next: follow the recovery intent in the error details. CHART_CAPACITY_EXCEEDED — trigger: too many steps for the canvas at the chosen size and density, or an asserted subtotal or total that does not reconcile; retryable: no; unchanged input: will not succeed; next: follow the recovery intent in the error details. CHART_DIMENSIONS_TOO_SMALL — trigger: see the error details for this code; retryable: no; unchanged input: will not succeed; next: follow the recovery intent in the error details. CHART_OUTPUT_TOO_LARGE — trigger: the inline SVG byte budget would be exceeded at the requested size and data density; retryable: no; unchanged input: will not succeed; next: follow the recovery intent in the error details. CHART_THEME_UNKNOWN — trigger: theme or surface names a visual language we do not register for this chart family; retryable: no; unchanged input: will not succeed; next: follow the recovery intent in the error details. CHART_UNSAFE_OUTPUT — trigger: the finished SVG failed our final safety re-validation (this is on us, not your data); retryable: no; unchanged input: will not succeed; next: contact support with the request id — retrying the same input will not help. CHART_OUTPUT_TOO_COMPLEX — trigger: the render hit a structural complexity ceiling (elements, depth, text volume, or path commands) separate from the byte budget; retryable: no; unchanged input: will not succeed; next: contact support with the request id — retrying the same input will not help.

Context footprint

Prefer descriptor.contractSections.recommendedRendering. Pass output.behavior.artifact unchanged to SquillifyChartRuntime.mount; resolve artifact.runtimeRequirement and every resourceRequirements item first. motionPlan.requiredRuntime is not a resolver input. A successful call costs 4 execution credits.

When to use it

When the question is how one figure became another, and the steps in between have to add up.

When the categories do not accumulate, the axis is time, or the data is a flow graph.

Scope

Note
Cumulative bridge charts: an opening figure, ordered movements, optional restated subtotals, and a closing total, computed in exact decimal.

Non-goals

  • Unordered category comparison (use chart.bar)
  • Trends over time (use chart.line)
  • Weighted flows between stages (use chart.sankey)
  • Sorting, top-N or 'other' aggregation of steps — those would change the arithmetic

Properties

IdempotentRetry safeStreaming supportedFile input supportedRequires external service

Capabilities

generationimage-processing

Cost & billing

Flat rate per invocation

MetercallsBillable onsuccessCost per call4 credits

File handling

Accepts file_refProduces file_refMax input195.3 KB

How we handle your data

Sensitive output possible
What this means

This skill may receive sensitive content and may return submitted, transformed, extracted, or reformatted content.

Squillify guarantees
  • Runs inside Squillify
  • This skill runs inside Squillify.
  • Payloads are not sent to an external service by this skill.
  • Squillify does not permanently store submitted payloads.
  • Telemetry records metadata only, not submitted content.
  • Submitted payloads are not used for training.
  • Submitted payloads are not reviewed by humans.
  • This skill does not target sensitive data.
  • The response may contain submitted or transformed content.
Your responsibility

Squillify does not determine whether submitted content is sensitive. Your application is responsible for deciding what data it is allowed to submit. Your application is responsible for securing the response.

Read the Microskill Data Handling Policy

Input schema

FieldTypeRequiredDescription
stepsarrayYesThe bridge, in reading order (required, 2..500 entries). Each entry is an object with id (default step-1, step-2, …), label (defaults to id), shortLabel (used when the axis runs out of room), kind and value. kind is 'start' (the opening balance, allowed only first), 'delta' (a movement, the default), 'subtotal' (a restated running total mid-bridge) or 'total' (the closing balance, required last). The first step must be 'start' and the last must be 'total'. value is a decimal string in plain notation — optional '-', digits, optional '.' and digits (for example "-1200.50"). At most 38 digits in total and 18 after the point. Scientific notation, thousands separators, currency symbols, NaN and Infinity are rejected. A JSON number is rejected too, with the string form to use: the arithmetic here is exact decimal, and a double would silently change your figures. Whether value is required depends on kind and totalPolicy — see totalPolicy. A delta may instead carry contributors: 1..24 objects of {id, label, value} on that step, each value in the same decimal grammar, showing what drove the movement. The delta is then the exact sum of its contributors, and the step's own value, if you also send one, is checked against that sum. Every non-zero contributor in one step must share a sign: a step drawn as one movement cannot contain both directions without its segments crossing the step's own endpoint, so mixed signs are refused rather than drawn. Contributor ids repeat across steps to mean the same driver, which is what fixes each one's colour and legend entry. At most 2000 contributors per request.
totalPolicystringNoHow strictly your subtotal and total figures are demanded. It is a strictness ladder, and every figure you do supply is reconciled under all three — no policy switches the check off, because a bridge that looks reconciled without being reconciled is the one thing this chart must not produce. 'validate' (default) lets you state a total or omit it: stated figures are checked against the computed running total, omitted ones are computed. 'calculate' refuses a value on any subtotal or total, for a caller who does not have them and wants to be told if their pipeline starts inventing them. 'explicit' requires one on every subtotal and total, so a forgotten assertion is refused instead of quietly becoming an unchecked figure. In every policy a 'start' step requires a value and a plain 'delta' requires either a value or contributors.
reconciliationobjectNoHow close an asserted figure has to be. reconciliation.tolerance is an absolute allowance and .relativeTolerance a fraction of the computed value (0..0.01); both are decimal strings and both default to zero, which means exact equality. The allowance is the larger of the two, so a rounded export can be accepted without loosening small figures. A difference beyond it is refused with CHART_RECONCILIATION_MISMATCH — never rendered with a warning, because a bridge whose stated total does not follow from its own steps looks right to every reader who checks it.
orientationstringNoBridge direction: vertical (default) reads left to right, horizontal reads top to bottom.
widthintegerNoCanvas width in pixels, 100..1600 (optional; default 800). Outside that range the request is refused as invalid input rather than clamped. The ceiling is the size a chart is read at — a full-HD dashboard or a report page; beyond it the canvas stops being what limits how much fits, and type sized for the canvas is illegible once the export is displayed smaller. It applies per side, so a tall portrait canvas for a long horizontal ranking is allowed.
heightintegerNoCanvas height in pixels, 100..1600 (optional; default 480). Outside that range the request is refused as invalid input rather than clamped. The ceiling is the size a chart is read at — a full-HD dashboard or a report page; beyond it the canvas stops being what limits how much fits, and type sized for the canvas is illegible once the export is displayed smaller. It applies per side, so a tall portrait canvas for a long horizontal ranking is allowed.
themestringNoVisual language: basic, modern or squillify (optional; default squillify). An unregistered value is rejected with the registered names listed — we never substitute silently.
surfacestringNoSurface the theme renders on: dark or light (optional; default light).
densitystringNoHow tightly the chrome is packed: compact, standard or spacious (optional; default standard). Density scales spacing only — it never lowers a legibility minimum.
visualProfilestringNoWhat the SVG promises about the host font: portable or certified (optional; default portable). The default is layout-deterministic and tolerates substitution; certified refuses text the certified metrics cannot fully measure.
categoryAxisobjectNoThe axis the steps are named along. categoryAxis.visible, .title, .position (top/bottom when vertical, left/right when horizontal), .labels ({visible, rotation, showEvery, maxLines 1..5, overflow}) and .ticks/.grid ({visible}). Omit it and the axis is drawn below a vertical bridge in step order, with labels that wrap to two lines and adapt when space runs short: overflow 'auto' wraps, then uses your shortLabel, then thins the labels, reporting every substitution. It never reorders or drops a step — order is the data in a bridge. categoryAxis.sort and .reversed are rejected for the same reason.
valueAxisobjectNoThe axis the steps are measured against. valueAxis.visible, .title, .position, .scale ({type, minimum, maximum, includeZero, nice, reversed}), .ticks ({visible, mode auto/count/interval/values, count 2..20, interval, values, minorVisible}), .grid ({visible, minorVisible}), .baseline ({visible, default true}) and .format ({style, notation, currency, unit, minimumFractionDigits, maximumFractionDigits, useGrouping, prefix, suffix}). Omit it and the axis is drawn on the left with a domain that includes zero, since the opening and closing marks are measured from it. A bound that would cut off any part of the bridge is refused, not clipped, and the check is made in exact decimal so it cannot flip on a rounding artifact. .baseline is the zero line the steps are measured from and has its own switch on purpose: valueAxis.visible false removes the ticks, labels, grid and title but leaves it standing, because when the bridge crosses zero it floats inside the plot instead of sitting under the axis. Set valueAxis.baseline.visible false to drop it.
barsobjectNoStep mark thickness and spacing. bars.width is 'auto' (default — marks fill their band) or a 0.05..1 share of the band, centred. bars.categoryGap (0..0.9, default 0.2) is the empty share between steps, and it is also what a connector runs across: close it to zero and there is no gap left to draw accumulation in. bars.minimumWidth (1..200 px) is the narrowest mark you accept — ask for more than fits and the request is refused with the width it would have had. bars.maximumWidth (1..500 px) caps a needlessly wide mark. Fill and stroke belong to style, not here.
connectorsobjectNoThe thin lines joining one step's closing edge to the next step's opening edge — the visual claim that makes a waterfall a bridge rather than a row of floating bars. connectors.visible (default true). connectors.subtotalBreak (default false) can omit the connector into a subtotal or total when a caller wants a visual break before a restated figure. connectors.overflow decides what happens when the steps are packed too tightly for a run to read as a line rather than a dot: 'auto' (default) suppresses them all and reports it, 'keep' draws them anyway, 'reject' refuses. Suppression is all-or-nothing, so direction never becomes partially readable.
dataLabelsobjectNoNumbers on the marks. Off unless dataLabels.visible. content: 'delta' (default — what the step moved) or 'runningTotal' (where the bridge stood after it); a subtotal or total states its own figure under either, since it has no movement of its own. position: 'auto' (default), 'insideStart', 'insideCenter', 'insideEnd', 'outsideEnd'. format is the number-format object, defaulting to valueAxis.format. showZero (default false) chooses whether an unchanged step is labelled. overflow: 'hide' (default), 'outside', 'reject'.
localizationobjectNolocalization.locale (a supported BCP 47 tag, default 'en') selects the frozen number-format table, so the same request formats identically on every host. .direction accepts 'auto' or 'leftToRight'. Omit it and numbers are written the 'en' way. Formatting affects the labels only — the figures in stepResults are always canonical decimal strings.
titlestringNoHeading drawn above the plot (optional; at most 1024 code points). Omit it and the chart renders untitled — we never invent a title from your data. One code point past that ceiling is refused as TOOL_INPUT_INVALID.
subtitlestringNoSecond line under the title, useful for the period covered or the unit (optional; at most 1024 code points). Omit it and no second line is drawn.
legendobjectNoThe contributor key, drawn only for a bridge with contributors — a plain waterfall has no series to name. legend.visible defaults to true once there are two or more distinct contributors. legend.title heads the items. position: 'auto' (default), 'top', 'right', 'bottom', 'left'. orientation: 'auto' (default), 'horizontal', 'vertical'. alignment: 'start' (default), 'center', 'end'. order: 'series' (default), 'reverse', or an array naming every contributor id once. wrap (default true) allows extra rows.
referenceLinesarrayNoValue-axis marks to read the bridge against — a budget, a covenant, a prior close. Each entry: value (required, finite, and inside the drawn domain or the render is refused), label, labelPosition ('auto' default, 'start', 'center', 'end'), layer ('behind' default, 'front'), meaning ('reference' default, 'target', 'warning', 'critical', published as data-sq-meaning). Omitted draws none; at most 24 lines and bands together.
referenceBandsarrayNoShaded value ranges, drawn behind the marks. Each entry: from and to (both required and finite, from below to, both inside the drawn domain or the render is refused), label, meaning ('reference' default, 'target', 'warning', 'critical'). Omitted draws none; at most 24 lines and bands together.
accessibilityobjectNoWhat a reader who cannot see the chart is told. accessibility.title is the accessible name, defaulting to the visible title and then to a generated one. accessibility.description is your business context, placed ahead of the generated summary and never replacing it. Each is limited to 256 code points, and both share the summary's 1000-character budget: a long context makes the generated summary step down to a coarser rung. The generated part always states the opening, the closing, the net change and the running total after each step, because a bridge a reader cannot follow in order is a bridge they cannot check.
layoutobjectNoSpace around the chart and what to do when there is not enough. layout.margin is 'auto' (default), a whole number of pixels, or an object of top, right, bottom and left, each 0..1000. layout.plotPadding is a number or the same four-sided object, each 0..200, and defaults to the design profile. layout.insufficientSpace must be 'reject' (the default): a canvas too small for the requested chrome is refused with CHART_DIMENSIONS_TOO_SMALL, which names the minimum that would have worked and what is spending the space. Unlike a bar chart there is no 'hideOptionalLabels' — the step labels are the sequence the bridge states, so no band here is optional.
captionstringNoReading note printed under the chart. Omitted or empty reserves no space.
sourcestringNoData attribution printed under the caption. Omitted or empty reserves no space.
periodstringNoCovered period printed under the source. Omitted or empty reserves no space.
styleobjectNoOptional bounded paint and typography styling; omit it to use the selected theme defaults. text.size is expressed in normalized design pixels at an 800-pixel reference width, so equivalent roles match at equal CSS width. It never changes data or accessibility. 'contract' selects the published semantics. 'tokens' sets design tokens; 'rules' applies typed properties to a role and optional series/category facets; 'palette' accepts a digest-checked palette; 'css' is a bounded post-layout stylesheet. The styleCapabilities section publishes this family's accepted roles, properties and copyable examples. For Bar, target one mark with role 'bar' plus seriesIndex and categoryIndex; categoryIndex alone targets that category across series. A minimal rule is {"target":{"role":"bar"},"set":{"paint.fill":"#2563eb"}}. Unsupported typed rules are refused; unsupported CSS is dropped and reported. Do not discover the grammar through trial calls: style validation failures are currently billable. Before calling, read this contract's styleSurface and styleCapabilities sections for the complete rule shape, accepted roles and properties, limits, and copyable examples.
behaviorobjectNoOptional host-behavior request; omit it for canonical static SVG. behavior.motion.preset accepts only enter_bridge, the entrance replay() plays after mount. behavior.interaction defaults to enabled with focus_step, focus_subtotal_range and trace_bridge. Set enabled to false to turn hover, focus, the guide and the value panel off. update_bridge is an update() intent, not a preset. svg is a complete static, script-free chart. Mark highlight, the dotted guide, and the value panel are not in that SVG, and entry motion, focus, and updates stay inactive there. Pass the entire output.behavior.artifact unchanged to SquillifyChartRuntime.mount(container, artifact, options), then call the returned handle's replay() once to play the entry. A later value change uses update(nextArtifact, {intent}). Injecting svg directly leaves hover and motion off. Interaction is on by default for every host chart: after mount, pointer and keyboard focus open the value panel and the dotted guide. Set behavior.interaction.enabled to false to turn that off. motion.preset accepts only the family's entrance.
outputobjectNoOutput scoping. output.idNamespace ([a-z][a-z0-9-]{0,31}) is prefixed onto every generated element id so several charts can share one host document without colliding; omit it for a standalone chart. An invalid value is rejected, never corrected. output.includeTransformationDetails (default false) adds the item-level transformationDetails object; transformationSummary is always returned and cannot be switched off.

Output schema

FieldTypeRequiredDescription
svgstringYesAllowlist-checked script-free SVG. svg is a complete static, script-free chart. Mark highlight, the dotted guide, and the value panel are not in that SVG, and entry motion, focus, and updates stay inactive there. Pass the entire output.behavior.artifact unchanged to SquillifyChartRuntime.mount(container, artifact, options), then call the returned handle's replay() once to play the entry. A later value change uses update(nextArtifact, {intent}). Injecting svg directly leaves hover and motion off. Interaction is on by default for every host chart: after mount, pointer and keyboard focus open the value panel and the dotted guide. Set behavior.interaction.enabled to false to turn that off. motion.preset accepts only the family's entrance.
widthintegerYesRendered width in pixels.
heightintegerYesRendered height in pixels.
viewBoxstringYesSVG viewBox string (for example "0 0 800 480").
stepResultsarrayYesAlways present. The computed bridge in request order: id, kind, calculatedDelta and calculatedValue, plus contributors ({id, value}) on a decomposed step. Every figure is a canonical decimal string — plain notation, no insignificant trailing zeros, a single "0" for every zero — so the number you read is the number that was added. It is named stepResults rather than steps because a response field that reuses an input name with a different shape is a field callers mis-send.
reconciliationReportobjectYesAlways present. policy, matched, difference and allowedTolerance for the closing total, plus checks[] naming every asserted step with its own asserted, calculated, difference, allowedTolerance and matched. A returned chart has matched every check — a failure is a refusal, not a flag — so this is the audit trail, not a verdict to branch on. Named reconciliationReport rather than reconciliation for the same reason stepResults is not steps: the input field of that name is the tolerance you set, and a response that answered to it with a different shape is a field callers mis-send.
renderSummaryobjectYesAlways present. What was actually drawn: variant, orientation, stacked, stepCount, markCount (one rectangle per plain step, one per contributor on a decomposed one) and connectorCount (after subtotal breaks and any suppression). Scalars only, so the object does not grow with the data.
accessibilityobjectYesAlways present. 'title' is the accessible name, 'description' the generated summary with any caller context ahead of it, and 'detail' the rung it reached: 'full' names every running total and movement, 'grouped' the trajectory, 'shape' the opening, closing, net change and dominant movements. The rung is the richest that fits a 1000-character budget, so a longer bridge returns a coarser summary rather than a truncated one.
transformationSummaryobjectYesAlways present. States whether the picture differs from the data you sent — 'applied', the operations that ran, and how many steps and labels they touched. The operations a bridge can report are label adaptation, optional label removal and connector suppression; it has no data transforms, because sorting or aggregating steps would change what the arithmetic says. 'applied: false' is the common answer and is the point.
behaviorobjectNoPresent when behavior is requested. Contains the resolved motion report and, for the recommended full rendering profile, the complete digest-bound artifact at behavior.artifact. Pass that artifact unchanged to the host runtime. svg is a complete static, script-free chart. Mark highlight, the dotted guide, and the value panel are not in that SVG, and entry motion, focus, and updates stay inactive there. Pass the entire output.behavior.artifact unchanged to SquillifyChartRuntime.mount(container, artifact, options), then call the returned handle's replay() once to play the entry. A later value change uses update(nextArtifact, {intent}). Injecting svg directly leaves hover and motion off. Interaction is on by default for every host chart: after mount, pointer and keyboard focus open the value panel and the dotted guide. Set behavior.interaction.enabled to false to turn that off. motion.preset accepts only the family's entrance.
styleReportobjectNoPresent only when a 'style' input was supplied. Reports tokensApplied, rulesApplied, typedRulesApplied, typedAssignmentsApplied, rulesDropped, declarationsDropped and, when anything was dropped, a bounded list of reasons — so a stylesheet that was modified never looks like one that was applied whole.
warningsarrayNoPresent when the render succeeded with a known degradation. Each entry is an object with a stable code, a message and details. Every chart can emit FONT_SUBSTITUTION_POSSIBLE under visualProfile 'portable', FONT_GLYPH_UNMEASURED when a code point falls outside the certified metrics, and CHART_STYLE_PARTIALLY_APPLIED when part of your styling was dropped — styleReport then names which part and why. Codes are stable for agent routing. Adaptations to your labels or connectors are reported through transformationSummary instead of here, so the two channels cannot disagree.
transformationDetailsobjectNoPresent only when 'output.includeTransformationDetails' is true. The item-level version of the summary, in your own step ids. Bounded: a request whose details would exceed the response limit is refused rather than truncated.

API contract

POST /v1/execute
curl
curl -sS -X POST "$BASE/v1/execute" \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"skillKey":"chart.waterfall","input":{"steps":[{"id":"open","kind":"start","value":"500"},{"id":"sales","value":"180"},{"id":"cost","value":"-120"},{"id":"close","kind":"total"}],"title":"From opening to closing margin","behavior":{"motion":{"enabled":true,"preset":"enter_bridge","reducedMotion":"respect"}}}}'
Integrations
TypeScript SDK
import { Squillify } from "@squillify/sdk";

const sq = new Squillify({ apiKey: process.env.SQUILLIFY_API_KEY });

const { output } = await sq.execute("chart.waterfall", {"title":"From opening to closing margin","subtitle":"in thousands (USD)","totalPolicy":"validate","steps":[{"value":"500","id":"open","label":"Opening","kind":"start"},{"id":"volume","label":"Volume","value":"180"},{"id":"price","label":"Price","value":"60"},{"id":"cost","label":"Cost","value":"-195"},{"value":"545","id":"close","label":"Closing","kind":"total"}]});

Errors

CodeHTTPRetryableDescription
VALIDATION_ERROR400NoThe request payload failed schema validation.
UNAUTHORIZED401NoMissing or invalid API key.
SKILL_NOT_ACCESSIBLE403NoThe API key is not granted access to this skill.
SKILL_NOT_AVAILABLE_FOR_PLAN403NoThis skill is not included in your current plan.
PLAN_ELIGIBILITY_UNAVAILABLE503YesPlan eligibility could not be decided. Retry after backoff.
SKILL_NOT_FOUND404NoThe requested skill or version does not exist.
TENANT_RATE_LIMITED429YesRequest rate limit exceeded. Retry after backoff.
QUOTA_EXCEEDED429YesPlan quota exhausted. Retryable once the quota period resets.
EXECUTION_TIMEOUT504YesExecution exceeded the allowed time budget.
CHART_RECONCILIATION_MISMATCH412NoThe input is valid but cannot be processed as-is.
CHART_SEMANTIC_MISMATCH412NoThe input is valid but cannot be processed as-is.
CHART_CAPACITY_EXCEEDED429NoA size, quota, rate, or time limit was exceeded.
CHART_DIMENSIONS_TOO_SMALL429NoA size, quota, rate, or time limit was exceeded.
CHART_OUTPUT_TOO_LARGE429NoA size, quota, rate, or time limit was exceeded.
CHART_THEME_UNKNOWN400NoThe request payload failed this skill's input validation.
CHART_UNSAFE_OUTPUT500NoThe skill encountered an unexpected execution failure.
CHART_OUTPUT_TOO_COMPLEX500NoThe skill encountered an unexpected execution failure.

Other limits

LimitValue
Recommended inline bytes32 KiB

Limitations

  • Unordered category comparison (use chart.bar)
  • Trends over time (use chart.line)
  • Weighted flows between stages (use chart.sankey)
  • Sorting, top-N or 'other' aggregation of steps — those would change the arithmetic
  • Comparing categories that do not accumulate
  • Hundreds of movements — we refuse an illegible bridge
  • Figures you only have as floating-point numbers you cannot reproduce

How to call

POST /v1/execute (requires authentication)

Request bodyjson
{
  "skillKey": "chart.waterfall",
  "input": {
    "title": "From opening to closing margin",
    "subtitle": "in thousands (USD)",
    "totalPolicy": "validate",
    "steps": [
      {
        "value": "500",
        "id": "open",
        "label": "Opening",
        "kind": "start"
      },
      {
        "id": "volume",
        "label": "Volume",
        "value": "180"
      },
      {
        "id": "price",
        "label": "Price",
        "value": "60"
      },
      {
        "id": "cost",
        "label": "Cost",
        "value": "-195"
      },
      {
        "value": "545",
        "id": "close",
        "label": "Closing",
        "kind": "total"
      }
    ]
  }
}

Examples

Profit bridge with a validated closing total

Inputjson
{
  "title": "From opening to closing margin",
  "subtitle": "in thousands (USD)",
  "totalPolicy": "validate",
  "steps": [
    {
      "value": "500",
      "id": "open",
      "label": "Opening",
      "kind": "start"
    },
    {
      "id": "volume",
      "label": "Volume",
      "value": "180"
    },
    {
      "id": "price",
      "label": "Price",
      "value": "60"
    },
    {
      "id": "cost",
      "label": "Cost",
      "value": "-195"
    },
    {
      "value": "545",
      "id": "close",
      "label": "Closing",
      "kind": "total"
    }
  ]
}
Expected outputjson
{
  "transformationSummary": {
    "applied": false,
    "operations": [],
    "affectedCategoryCount": 0,
    "affectedPointCount": 0,
    "affectedLabelCount": 0
  },
  "width": 800,
  "stepResults": [
    {
      "id": "open",
      "kind": "start",
      "calculatedDelta": "0",
      "calculatedValue": "500"
    },
    {
      "id": "volume",
      "kind": "delta",
      "calculatedDelta": "180",
      "calculatedValue": "680"
    },
    {
      "id": "price",
      "kind": "delta",
      "calculatedDelta": "60",
      "calculatedValue": "740"
    },
    {
      "id": "cost",
      "kind": "delta",
      "calculatedDelta": "-195",
      "calculatedValue": "545"
    },
    {
      "id": "close",
      "kind": "total",
      "calculatedDelta": "0",
      "calculatedValue": "545"
    }
  ],
  "reconciliationReport": {
    "policy": "validate",
    "matched": true,
    "difference": "0",
    "allowedTolerance": "0",
    "checks": [
      {
        "path": "steps[4]",
        "id": "close",
        "kind": "total",
        "asserted": "545",
        "calculated": "545",
        "difference": "0",
        "allowedTolerance": "0",
        "matched": true
      }
    ]
  },
  "renderSummary": {
    "variant": "standard-bridge",
    "orientation": "vertical",
    "stacked": false,
    "stepCount": 5,
    "markCount": 5,
    "connectorCount": 4
  },
  "height": 480,
  "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"800\" height=\"480\" viewBox=\"0 0 800 480\" role=\"img\" class=\"sq-chart sq-chart--waterfall sq-family--cartesian sq-theme--squillify sq-surface--light sq-density--standard\" data-sq-style-contract=\"CHART_STYLE@1\" data-sq-chart=\"waterfall\" data-sq-family=\"cartesian\" data-sq-theme=\"squillify\" data-sq-theme-version=\"1\" data-sq-surface=\"light\" data-sq-density=\"standard\" data-sq-a11y-detail=\"full\" aria-labelledby=\"sq-86a0b7e184e4-title-6 sq-86a0b7e184e4-desc-7\"><title id=\"sq-86a0b7e184e4-title-6\">From opening to closing margin</title><desc id=\"sq-86a0b7e184e4-desc-7\">Waterfall chart. 5 steps, bridging 500 to 545, a net up of 45, vertical. Running total: Opening 500, Volume 680, Price 740, Cost 545, Closing 545. Movements: Volume up 180, Price up 60, Cost down 195. 2 increases and 1 decrease; largest increase 180 at Volume; largest decrease 195 at Cost. All values are listed.</desc><style>.sq-chart{--sq-chart-axis:#c9d1df;--sq-chart-font-family:'Squillify Mono', 'IBM Plex Mono', monospace;--sq-chart-grid:#d8dee9;--sq-chart-surface:#ffffff;--sq-chart-text-muted:#667085;--sq-chart-text-primary:#101828;--sq-series-1:#2563eb;--sq-series-2:#6d5ce7;--sq-series-3:#168a5b;--sq-series-4:#c45412;--sq-series-5:#d92d20;--sq-series-6:#667085;font-family:var(--sq-chart-font-family);font-weight:500;}.sq-chart .sq-background{fill:var(--sq-chart-surface);}.sq-chart .sq-grid{stroke:var(--sq-chart-grid);}.sq-chart .sq-axis{stroke:var(--sq-chart-axis);}.sq-chart .sq-title{fill:var(--sq-chart-text-primary);}.sq-chart .sq-subtitle{fill:var(--sq-chart-text-muted);}.sq-chart .sq-tick-label{fill:var(--sq-chart-text-muted);}.sq-chart .sq-category-label{fill:var(--sq-chart-text-muted);}.sq-chart .sq-legend-label{fill:var(--sq-chart-text-primary);}</style><defs><linearGradient id=\"sq-86a0b7e184e4-fh-5\" gradientUnits=\"objectBoundingBox\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\"><stop offset=\"0\" stop-color=\"#f0f5fe\"/><stop offset=\"0.54\" stop-color=\"#ffffff\"/></linearGradient><linearGradient id=\"sq-86a0b7e184e4-gradient-0\" gradientUnits=\"objectBoundingBox\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop offset=\"0\" stop-color=\"#6f9bff\"/><stop offset=\"1\" stop-color=\"#c9d8ff\"/></linearGradient><linearGradient id=\"sq-86a0b7e184e4-gradient-1\" gradientUnits=\"objectBoundingBox\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop offset=\"0\" stop-color=\"#426cad\"/><stop offset=\"1\" stop-color=\"#b7c8e3\"/></linearGradient><linearGradient id=\"sq-86a0b7e184e4-gradient-2\" gradientUnits=\"objectBoundingBox\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop offset=\"0\" stop-color=\"#54bc8b\"/><stop offset=\"1\" stop-color=\"#c8ebda\"/></linearGradient><pattern id=\"sq-86a0b7e184e4-down-3\" width=\"8\" height=\"8\" patternUnits=\"userSpaceOnUse\"><rect x=\"0\" y=\"0\" width=\"8\" height=\"8\" fill=\"#f7d4ba\"/><line x1=\"-2\" y1=\"2\" x2=\"2\" y2=\"-2\" stroke=\"#f09a5b\" stroke-width=\"1.5\"/><line x1=\"0\" y1=\"8\" x2=\"8\" y2=\"0\" stroke=\"#f09a5b\" stroke-width=\"1.5\"/><line x1=\"6\" y1=\"10\" x2=\"10\" y2=\"6\" stroke=\"#f09a5b\" stroke-width=\"1.5\"/></pattern><linearGradient id=\"sq-86a0b7e184e4-gradient-4\" gradientUnits=\"objectBoundingBox\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop offset=\"0\" stop-color=\"#98a2b3\"/><stop offset=\"1\" stop-color=\"#e4e7ec\"/></linearGradient></defs><rect x=\"0\" y=\"0\" width=\"800\" height=\"480\" fill=\"#ffffff\" class=\"sq-background\" data-sq-role=\"background\"/><g class=\"sq-plot\" data-sq-role=\"plot\"><rect x=\"0\" y=\"0\" width=\"800\" height=\"80\" fill=\"url(#sq-86a0b7e184e4-fh-5)\" class=\"sq-brand-band\"/><rect x=\"0\" y=\"80\" width=\"800\" height=\"400\" fill=\"#f8fafd\" class=\"sq-plot\" data-sq-role=\"plot\"/><line x1=\"0\" y1=\"80\" x2=\"800\" y2=\"80\" stroke=\"#c9d1df\" stroke-width=\"1\" class=\"sq-card-frame\"/><text x=\"12\" y=\"16\" font-size=\"10\" text-anchor=\"start\" font-family=\"&#39;Squillify Mono&#39;, &#39;IBM Plex Mono&#39;, monospace\" font-weight=\"600\" letter-spacing=\"1.2\" textLength=\"108\" lengthAdjust=\"spacing\" fill=\"#2563eb\" class=\"sq-caption\" data-sq-role=\"caption\">WATERFALL CHART</text><text x=\"12\" y=\"43\" font-size=\"16\" text-anchor=\"start\" font-family=\"&#39;Squillify Mono&#39;, &#39;IBM Plex Mono&#39;, monospace\" font-weight=\"600\" letter-spacing=\"-0.32\" textLength=\"280\" lengthAdjust=\"spacing\" fill=\"#101828\" class=\"sq-title\" data-sq-role=\"title\">From opening to closing margin</text><text x=\"12\" y=\"58\" font-size=\"10\" text-anchor=\"start\" font-family=\"&#39;Squillify Mono&#39;, &#39;IBM Plex Mono&#39;, monospace\" font-weight=\"500\" textLength=\"109\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-subtitle\" data-sq-role=\"subtitle\">in thousands (USD)</text><g class=\"sq-legend\" data-sq-role=\"legend\"><g class=\"sq-legend-item\"><g transform=\"translate(596.4 29.8) rotate(45)\"><rect x=\"-3.4\" y=\"-3.4\" width=\"6.8\" height=\"6.8\" fill=\"#54bc8b\" stroke=\"#168a5b\" stroke-width=\"0.8\" class=\"sq-legend-swatch\" data-sq-role=\"legend-swatch\" data-sq-series-index=\"0\"/></g><text x=\"607\" y=\"33.2\" font-size=\"10\" text-anchor=\"start\" font-family=\"&#39;Squillify Mono&#39;, &#39;IBM Plex Mono&#39;, monospace\" font-weight=\"500\" textLength=\"49\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-legend-label\" data-sq-role=\"legend-label\" data-sq-series-index=\"0\">Increase</text></g><g class=\"sq-legend-item\"><g transform=\"translate(671.4 29.8) rotate(45)\"><rect x=\"-3.4\" y=\"-3.4\" width=\"6.8\" height=\"6.8\" fill=\"#f7d4ba\" stroke=\"#c45412\" stroke-width=\"0.8\" class=\"sq-legend-swatch\" data-sq-role=\"legend-swatch\" data-sq-series-index=\"1\"/></g><text x=\"682\" y=\"33.2\" font-size=\"10\" text-anchor=\"start\" font-family=\"&#39;Squillify Mono&#39;, &#39;IBM Plex Mono&#39;, monospace\" font-weight=\"500\" textLength=\"49\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-legend-label\" data-sq-role=\"legend-label\" data-sq-series-index=\"1\">Decrease</text></g><g class=\"sq-legend-item\"><g transform=\"translate(746.4 29.8) rotate(45)\"><rect x=\"-3.4\" y=\"-3.4\" width=\"6.8\" height=\"6.8\" fill=\"#426cad\" stroke=\"#1e4079\" stroke-width=\"0.8\" class=\"sq-legend-swatch\" data-sq-role=\"legend-swatch\" data-sq-series-index=\"2\"/></g><text x=\"757\" y=\"33.2\" font-size=\"10\" text-anchor=\"start\" font-family=\"&#39;Squillify Mono&#39;, &#39;IBM Plex Mono&#39;, monospace\" font-weight=\"500\" textLength=\"31\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-legend-label\" data-sq-role=\"legend-label\" data-sq-series-index=\"2\">Total</text></g></g><rect x=\"0.5\" y=\"0.5\" width=\"799\" height=\"479\" fill=\"none\" stroke=\"#c9d1df\" stroke-width=\"1\" class=\"sq-card-frame\"/><line x1=\"47\" y1=\"446\" x2=\"788\" y2=\"446\" stroke=\"#d8dee9\" stroke-width=\"1\" stroke-dasharray=\"3 3\" stroke-linecap=\"butt\" class=\"sq-grid\" data-sq-role=\"grid-line\"/><line x1=\"47\" y1=\"357.25\" x2=\"788\" y2=\"357.25\" stroke=\"#d8dee9\" stroke-width=\"1\" stroke-dasharray=\"3 3\" stroke-linecap=\"butt\" class=\"sq-grid\" data-sq-role=\"grid-line\"/><line x1=\"47\" y1=\"268.5\" x2=\"788\" y2=\"268.5\" stroke=\"#d8dee9\" stroke-width=\"1\" stroke-dasharray=\"3 3\" stroke-linecap=\"butt\" class=\"sq-grid\" data-sq-role=\"grid-line\"/><line x1=\"47\" y1=\"179.75\" x2=\"788\" y2=\"179.75\" stroke=\"#d8dee9\" stroke-width=\"1\" stroke-dasharray=\"3 3\" stroke-linecap=\"butt\" class=\"sq-grid\" data-sq-role=\"grid-line\"/><line x1=\"47\" y1=\"91\" x2=\"788\" y2=\"91\" stroke=\"#d8dee9\" stroke-width=\"1\" stroke-dasharray=\"3 3\" stroke-linecap=\"butt\" class=\"sq-grid\" data-sq-role=\"grid-line\"/><line x1=\"43\" y1=\"446\" x2=\"47\" y2=\"446\" stroke=\"#c9d1df\" stroke-width=\"1\" class=\"sq-tick\" data-sq-role=\"tick\"/><line x1=\"43\" y1=\"357.25\" x2=\"47\" y2=\"357.25\" stroke=\"#c9d1df\" stroke-width=\"1\" class=\"sq-tick\" data-sq-role=\"tick\"/><line x1=\"43\" y1=\"268.5\" x2=\"47\" y2=\"268.5\" stroke=\"#c9d1df\" stroke-width=\"1\" class=\"sq-tick\" data-sq-role=\"tick\"/><line x1=\"43\" y1=\"179.75\" x2=\"47\" y2=\"179.75\" stroke=\"#c9d1df\" stroke-width=\"1\" class=\"sq-tick\" data-sq-role=\"tick\"/><line x1=\"43\" y1=\"91\" x2=\"47\" y2=\"91\" stroke=\"#c9d1df\" stroke-width=\"1\" class=\"sq-tick\" data-sq-role=\"tick\"/><text x=\"41\" y=\"449.5\" font-size=\"10\" text-anchor=\"end\" textLength=\"24\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-label sq-tick-label\" data-sq-role=\"tick-label\">0.00</text><text x=\"41\" y=\"360.75\" font-size=\"10\" text-anchor=\"end\" textLength=\"36\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-label sq-tick-label\" data-sq-role=\"tick-label\">200.00</text><text x=\"41\" y=\"272\" font-size=\"10\" text-anchor=\"end\" textLength=\"36\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-label sq-tick-label\" data-sq-role=\"tick-label\">400.00</text><text x=\"41\" y=\"183.25\" font-size=\"10\" text-anchor=\"end\" textLength=\"36\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-label sq-tick-label\" data-sq-role=\"tick-label\">600.00</text><text x=\"41\" y=\"94.5\" font-size=\"10\" text-anchor=\"end\" textLength=\"36\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-label sq-tick-label\" data-sq-role=\"tick-label\">800.00</text><line x1=\"180.38\" y1=\"224.125\" x2=\"210.02\" y2=\"224.125\" stroke=\"#c9d1df\" stroke-width=\"1\" stroke-opacity=\"0.85\" stroke-dasharray=\"4 3\" stroke-linecap=\"butt\" class=\"sq-connector\" data-sq-role=\"waterfall-connector\" data-sq-target-key=\"t-connector-788404d4bb6dae89\" data-sq-category-index=\"0\"/><line x1=\"328.58\" y1=\"144.25\" x2=\"358.22\" y2=\"144.25\" stroke=\"#c9d1df\" stroke-width=\"1\" stroke-opacity=\"0.85\" stroke-dasharray=\"4 3\" stroke-linecap=\"butt\" class=\"sq-connector\" data-sq-role=\"waterfall-connector\" data-sq-target-key=\"t-connector-ea3d24200e615862\" data-sq-category-index=\"1\"/><line x1=\"476.78\" y1=\"117.625\" x2=\"506.42\" y2=\"117.625\" stroke=\"#c9d1df\" stroke-width=\"1\" stroke-opacity=\"0.85\" stroke-dasharray=\"4 3\" stroke-linecap=\"butt\" class=\"sq-connector\" data-sq-role=\"waterfall-connector\" data-sq-target-key=\"t-connector-b7f746bcb8bb78b9\" data-sq-category-index=\"2\"/><line x1=\"624.98\" y1=\"204.156\" x2=\"654.62\" y2=\"204.156\" stroke=\"#c9d1df\" stroke-width=\"1\" stroke-opacity=\"0.85\" stroke-dasharray=\"4 3\" stroke-linecap=\"butt\" class=\"sq-connector\" data-sq-role=\"waterfall-connector\" data-sq-target-key=\"t-connector-87269ac1f3c4106c\" data-sq-category-index=\"3\"/><rect x=\"61.82\" y=\"224.125\" width=\"118.56\" height=\"221.875\" fill=\"url(#sq-86a0b7e184e4-gradient-0)\" stroke=\"#2563eb\" stroke-width=\"1\" class=\"sq-mark sq-bar\" data-sq-role=\"waterfall-start\" data-sq-target-key=\"t-step-d8bbab58fcf438f1\" data-sq-category-index=\"0\" data-sq-palette-index=\"0\"/><rect x=\"210.02\" y=\"144.25\" width=\"118.56\" height=\"79.875\" fill=\"url(#sq-86a0b7e184e4-gradient-2)\" stroke=\"#168a5b\" stroke-width=\"1\" class=\"sq-mark sq-bar\" data-sq-role=\"waterfall-increase\" data-sq-target-key=\"t-step-63426b9eaabdc677\" data-sq-category-index=\"1\" data-sq-palette-index=\"1\"/><rect x=\"358.22\" y=\"117.625\" width=\"118.56\" height=\"26.625\" fill=\"url(#sq-86a0b7e184e4-gradient-2)\" stroke=\"#168a5b\" stroke-width=\"1\" class=\"sq-mark sq-bar\" data-sq-role=\"waterfall-increase\" data-sq-target-key=\"t-step-e3b5fbaac7f546c1\" data-sq-category-index=\"2\" data-sq-palette-index=\"1\"/><rect x=\"506.42\" y=\"117.625\" width=\"118.56\" height=\"86.531\" fill=\"url(#sq-86a0b7e184e4-down-3)\" stroke=\"#c45412\" stroke-width=\"1\" class=\"sq-mark sq-bar\" data-sq-role=\"waterfall-decrease\" data-sq-target-key=\"t-step-8ff64b5b541b5743\" data-sq-category-index=\"3\" data-sq-palette-index=\"3\"/><rect x=\"654.62\" y=\"204.156\" width=\"118.56\" height=\"241.844\" fill=\"url(#sq-86a0b7e184e4-gradient-1)\" stroke=\"#1e4079\" stroke-width=\"1\" class=\"sq-mark sq-bar\" data-sq-role=\"waterfall-total\" data-sq-target-key=\"t-step-6654cc4287a02129\" data-sq-category-index=\"4\" data-sq-palette-index=\"0\"/><line x1=\"47\" y1=\"446\" x2=\"788\" y2=\"446\" stroke=\"#c9d1df\" stroke-width=\"1.5\" class=\"sq-axis\" data-sq-role=\"axis-line\"/><line x1=\"121.1\" y1=\"446\" x2=\"121.1\" y2=\"450\" stroke=\"#c9d1df\" stroke-width=\"1\" class=\"sq-tick\" data-sq-role=\"tick\"/><line x1=\"269.3\" y1=\"446\" x2=\"269.3\" y2=\"450\" stroke=\"#c9d1df\" stroke-width=\"1\" class=\"sq-tick\" data-sq-role=\"tick\"/><line x1=\"417.5\" y1=\"446\" x2=\"417.5\" y2=\"450\" stroke=\"#c9d1df\" stroke-width=\"1\" class=\"sq-tick\" data-sq-role=\"tick\"/><line x1=\"565.7\" y1=\"446\" x2=\"565.7\" y2=\"450\" stroke=\"#c9d1df\" stroke-width=\"1\" class=\"sq-tick\" data-sq-role=\"tick\"/><line x1=\"713.9\" y1=\"446\" x2=\"713.9\" y2=\"450\" stroke=\"#c9d1df\" stroke-width=\"1\" class=\"sq-tick\" data-sq-role=\"tick\"/><text x=\"121.1\" y=\"462\" font-size=\"10\" text-anchor=\"middle\" textLength=\"42\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-label sq-category-label\" data-sq-role=\"category-label\" data-sq-category-index=\"0\">Opening</text><text x=\"269.3\" y=\"462\" font-size=\"10\" text-anchor=\"middle\" textLength=\"36\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-label sq-category-label\" data-sq-role=\"category-label\" data-sq-category-index=\"1\">Volume</text><text x=\"417.5\" y=\"462\" font-size=\"10\" text-anchor=\"middle\" textLength=\"30\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-label sq-category-label\" data-sq-role=\"category-label\" data-sq-category-index=\"2\">Price</text><text x=\"565.7\" y=\"462\" font-size=\"10\" text-anchor=\"middle\" textLength=\"24\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-label sq-category-label\" data-sq-role=\"category-label\" data-sq-category-index=\"3\">Cost</text><text x=\"713.9\" y=\"462\" font-size=\"10\" text-anchor=\"middle\" textLength=\"42\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-label sq-category-label\" data-sq-role=\"category-label\" data-sq-category-index=\"4\">Closing</text></g></svg>",
  "accessibility": {
    "title": "From opening to closing margin",
    "detail": "full",
    "description": "Waterfall chart. 5 steps, bridging 500 to 545, a net up of 45, vertical. Running total: Opening 500, Volume 680, Price 740, Cost 545, Closing 545. Movements: Volume up 180, Price up 60, Cost down 195. 2 increases and 1 decrease; largest increase 180 at Volume; largest decrease 195 at Cost. All values are listed."
  },
  "viewBox": "0 0 800 480"
}

Cost movement decomposed into its drivers

Inputjson
{
  "title": "Cost drivers between quarters",
  "steps": [
    {
      "value": "1000",
      "id": "open",
      "label": "Q1",
      "kind": "start"
    },
    {
      "contributors": [
        {
          "id": "labour",
          "label": "Labour",
          "value": "-120"
        },
        {
          "id": "freight",
          "label": "Freight",
          "value": "-45"
        }
      ],
      "id": "cost",
      "label": "Cost"
    },
    {
      "id": "close",
      "label": "Q2",
      "kind": "total"
    }
  ]
}
Expected outputjson
{
  "transformationSummary": {
    "applied": false,
    "operations": [],
    "affectedCategoryCount": 0,
    "affectedPointCount": 0,
    "affectedLabelCount": 0
  },
  "width": 800,
  "stepResults": [
    {
      "id": "open",
      "kind": "start",
      "calculatedDelta": "0",
      "calculatedValue": "1000"
    },
    {
      "id": "cost",
      "kind": "delta",
      "calculatedDelta": "-165",
      "calculatedValue": "835",
      "contributors": [
        {
          "id": "labour",
          "value": "-120"
        },
        {
          "id": "freight",
          "value": "-45"
        }
      ]
    },
    {
      "id": "close",
      "kind": "total",
      "calculatedDelta": "0",
      "calculatedValue": "835"
    }
  ],
  "reconciliationReport": {
    "policy": "validate",
    "matched": true,
    "difference": "0",
    "allowedTolerance": "0",
    "checks": []
  },
  "renderSummary": {
    "variant": "standard-bridge",
    "orientation": "vertical",
    "stacked": true,
    "stepCount": 3,
    "markCount": 4,
    "connectorCount": 2
  },
  "height": 480,
  "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"800\" height=\"480\" viewBox=\"0 0 800 480\" role=\"img\" class=\"sq-chart sq-chart--waterfall sq-family--cartesian sq-theme--squillify sq-surface--light sq-density--standard\" data-sq-style-contract=\"CHART_STYLE@1\" data-sq-chart=\"waterfall\" data-sq-family=\"cartesian\" data-sq-theme=\"squillify\" data-sq-theme-version=\"1\" data-sq-surface=\"light\" data-sq-density=\"standard\" data-sq-a11y-detail=\"full\" aria-labelledby=\"sq-8e19c06d5bc6-title-6 sq-8e19c06d5bc6-desc-7\"><title id=\"sq-8e19c06d5bc6-title-6\">Cost drivers between quarters</title><desc id=\"sq-8e19c06d5bc6-desc-7\">Waterfall chart. 3 steps, bridging 1000 to 835, a net down of 165, vertical. Running total: Q1 1000, Cost 835, Q2 835. Movements: Cost down 165. Cost is Labour -120 plus Freight -45. 0 increases and 1 decrease; largest decrease 165 at Cost. All values are listed.</desc><style>.sq-chart{--sq-chart-axis:#c9d1df;--sq-chart-font-family:'Squillify Mono', 'IBM Plex Mono', monospace;--sq-chart-grid:#d8dee9;--sq-chart-surface:#ffffff;--sq-chart-text-muted:#667085;--sq-chart-text-primary:#101828;--sq-series-1:#2563eb;--sq-series-2:#6d5ce7;--sq-series-3:#168a5b;--sq-series-4:#c45412;--sq-series-5:#d92d20;--sq-series-6:#667085;font-family:var(--sq-chart-font-family);font-weight:500;}.sq-chart .sq-background{fill:var(--sq-chart-surface);}.sq-chart .sq-grid{stroke:var(--sq-chart-grid);}.sq-chart .sq-axis{stroke:var(--sq-chart-axis);}.sq-chart .sq-title{fill:var(--sq-chart-text-primary);}.sq-chart .sq-tick-label{fill:var(--sq-chart-text-muted);}.sq-chart .sq-category-label{fill:var(--sq-chart-text-muted);}.sq-chart .sq-legend-label{fill:var(--sq-chart-text-primary);}.sq-chart .sq-bar[data-sq-palette-index=\"2\"]{fill:var(--sq-series-3);}.sq-chart .sq-bar[data-sq-palette-index=\"4\"]{fill:var(--sq-series-5);}</style><defs><linearGradient id=\"sq-8e19c06d5bc6-fh-5\" gradientUnits=\"objectBoundingBox\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\"><stop offset=\"0\" stop-color=\"#f0f5fe\"/><stop offset=\"0.54\" stop-color=\"#ffffff\"/></linearGradient><linearGradient id=\"sq-8e19c06d5bc6-gradient-0\" gradientUnits=\"objectBoundingBox\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop offset=\"0\" stop-color=\"#6f9bff\"/><stop offset=\"1\" stop-color=\"#c9d8ff\"/></linearGradient><linearGradient id=\"sq-8e19c06d5bc6-gradient-1\" gradientUnits=\"objectBoundingBox\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop offset=\"0\" stop-color=\"#426cad\"/><stop offset=\"1\" stop-color=\"#b7c8e3\"/></linearGradient><linearGradient id=\"sq-8e19c06d5bc6-gradient-2\" gradientUnits=\"objectBoundingBox\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop offset=\"0\" stop-color=\"#54bc8b\"/><stop offset=\"1\" stop-color=\"#c8ebda\"/></linearGradient><pattern id=\"sq-8e19c06d5bc6-down-3\" width=\"8\" height=\"8\" patternUnits=\"userSpaceOnUse\"><rect x=\"0\" y=\"0\" width=\"8\" height=\"8\" fill=\"#f7d4ba\"/><line x1=\"-2\" y1=\"2\" x2=\"2\" y2=\"-2\" stroke=\"#f09a5b\" stroke-width=\"1.5\"/><line x1=\"0\" y1=\"8\" x2=\"8\" y2=\"0\" stroke=\"#f09a5b\" stroke-width=\"1.5\"/><line x1=\"6\" y1=\"10\" x2=\"10\" y2=\"6\" stroke=\"#f09a5b\" stroke-width=\"1.5\"/></pattern><linearGradient id=\"sq-8e19c06d5bc6-gradient-4\" gradientUnits=\"objectBoundingBox\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop offset=\"0\" stop-color=\"#98a2b3\"/><stop offset=\"1\" stop-color=\"#e4e7ec\"/></linearGradient></defs><rect x=\"0\" y=\"0\" width=\"800\" height=\"480\" fill=\"#ffffff\" class=\"sq-background\" data-sq-role=\"background\"/><g class=\"sq-plot\" data-sq-role=\"plot\"><rect x=\"0\" y=\"0\" width=\"800\" height=\"60\" fill=\"url(#sq-8e19c06d5bc6-fh-5)\" class=\"sq-brand-band\"/><rect x=\"0\" y=\"60\" width=\"800\" height=\"420\" fill=\"#f8fafd\" class=\"sq-plot\" data-sq-role=\"plot\"/><line x1=\"0\" y1=\"60\" x2=\"800\" y2=\"60\" stroke=\"#c9d1df\" stroke-width=\"1\" class=\"sq-card-frame\"/><text x=\"12\" y=\"16\" font-size=\"10\" text-anchor=\"start\" font-family=\"&#39;Squillify Mono&#39;, &#39;IBM Plex Mono&#39;, monospace\" font-weight=\"600\" letter-spacing=\"1.2\" textLength=\"108\" lengthAdjust=\"spacing\" fill=\"#2563eb\" class=\"sq-caption\" data-sq-role=\"caption\">WATERFALL CHART</text><text x=\"12\" y=\"43\" font-size=\"16\" text-anchor=\"start\" font-family=\"&#39;Squillify Mono&#39;, &#39;IBM Plex Mono&#39;, monospace\" font-weight=\"600\" letter-spacing=\"-0.32\" textLength=\"271\" lengthAdjust=\"spacing\" fill=\"#101828\" class=\"sq-title\" data-sq-role=\"title\">Cost drivers between quarters</text><g class=\"sq-legend\" data-sq-role=\"legend\"><g class=\"sq-legend-item\"><g transform=\"translate(596.4 29.8) rotate(45)\"><rect x=\"-3.4\" y=\"-3.4\" width=\"6.8\" height=\"6.8\" fill=\"#54bc8b\" stroke=\"#168a5b\" stroke-width=\"0.8\" class=\"sq-legend-swatch\" data-sq-role=\"legend-swatch\" data-sq-series-index=\"0\"/></g><text x=\"607\" y=\"33.2\" font-size=\"10\" text-anchor=\"start\" font-family=\"&#39;Squillify Mono&#39;, &#39;IBM Plex Mono&#39;, monospace\" font-weight=\"500\" textLength=\"49\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-legend-label\" data-sq-role=\"legend-label\" data-sq-series-index=\"0\">Increase</text></g><g class=\"sq-legend-item\"><g transform=\"translate(671.4 29.8) rotate(45)\"><rect x=\"-3.4\" y=\"-3.4\" width=\"6.8\" height=\"6.8\" fill=\"#f7d4ba\" stroke=\"#c45412\" stroke-width=\"0.8\" class=\"sq-legend-swatch\" data-sq-role=\"legend-swatch\" data-sq-series-index=\"1\"/></g><text x=\"682\" y=\"33.2\" font-size=\"10\" text-anchor=\"start\" font-family=\"&#39;Squillify Mono&#39;, &#39;IBM Plex Mono&#39;, monospace\" font-weight=\"500\" textLength=\"49\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-legend-label\" data-sq-role=\"legend-label\" data-sq-series-index=\"1\">Decrease</text></g><g class=\"sq-legend-item\"><g transform=\"translate(746.4 29.8) rotate(45)\"><rect x=\"-3.4\" y=\"-3.4\" width=\"6.8\" height=\"6.8\" fill=\"#426cad\" stroke=\"#1e4079\" stroke-width=\"0.8\" class=\"sq-legend-swatch\" data-sq-role=\"legend-swatch\" data-sq-series-index=\"2\"/></g><text x=\"757\" y=\"33.2\" font-size=\"10\" text-anchor=\"start\" font-family=\"&#39;Squillify Mono&#39;, &#39;IBM Plex Mono&#39;, monospace\" font-weight=\"500\" textLength=\"31\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-legend-label\" data-sq-role=\"legend-label\" data-sq-series-index=\"2\">Total</text></g></g><rect x=\"0.5\" y=\"0.5\" width=\"799\" height=\"479\" fill=\"none\" stroke=\"#c9d1df\" stroke-width=\"1\" class=\"sq-card-frame\"/><line x1=\"47\" y1=\"446\" x2=\"788\" y2=\"446\" stroke=\"#d8dee9\" stroke-width=\"1\" stroke-dasharray=\"3 3\" stroke-linecap=\"butt\" class=\"sq-grid\" data-sq-role=\"grid-line\"/><line x1=\"47\" y1=\"371\" x2=\"788\" y2=\"371\" stroke=\"#d8dee9\" stroke-width=\"1\" stroke-dasharray=\"3 3\" stroke-linecap=\"butt\" class=\"sq-grid\" data-sq-role=\"grid-line\"/><line x1=\"47\" y1=\"296\" x2=\"788\" y2=\"296\" stroke=\"#d8dee9\" stroke-width=\"1\" stroke-dasharray=\"3 3\" stroke-linecap=\"butt\" class=\"sq-grid\" data-sq-role=\"grid-line\"/><line x1=\"47\" y1=\"221\" x2=\"788\" y2=\"221\" stroke=\"#d8dee9\" stroke-width=\"1\" stroke-dasharray=\"3 3\" stroke-linecap=\"butt\" class=\"sq-grid\" data-sq-role=\"grid-line\"/><line x1=\"47\" y1=\"146\" x2=\"788\" y2=\"146\" stroke=\"#d8dee9\" stroke-width=\"1\" stroke-dasharray=\"3 3\" stroke-linecap=\"butt\" class=\"sq-grid\" data-sq-role=\"grid-line\"/><line x1=\"47\" y1=\"71\" x2=\"788\" y2=\"71\" stroke=\"#d8dee9\" stroke-width=\"1\" stroke-dasharray=\"3 3\" stroke-linecap=\"butt\" class=\"sq-grid\" data-sq-role=\"grid-line\"/><line x1=\"43\" y1=\"446\" x2=\"47\" y2=\"446\" stroke=\"#c9d1df\" stroke-width=\"1\" class=\"sq-tick\" data-sq-role=\"tick\"/><line x1=\"43\" y1=\"371\" x2=\"47\" y2=\"371\" stroke=\"#c9d1df\" stroke-width=\"1\" class=\"sq-tick\" data-sq-role=\"tick\"/><line x1=\"43\" y1=\"296\" x2=\"47\" y2=\"296\" stroke=\"#c9d1df\" stroke-width=\"1\" class=\"sq-tick\" data-sq-role=\"tick\"/><line x1=\"43\" y1=\"221\" x2=\"47\" y2=\"221\" stroke=\"#c9d1df\" stroke-width=\"1\" class=\"sq-tick\" data-sq-role=\"tick\"/><line x1=\"43\" y1=\"146\" x2=\"47\" y2=\"146\" stroke=\"#c9d1df\" stroke-width=\"1\" class=\"sq-tick\" data-sq-role=\"tick\"/><line x1=\"43\" y1=\"71\" x2=\"47\" y2=\"71\" stroke=\"#c9d1df\" stroke-width=\"1\" class=\"sq-tick\" data-sq-role=\"tick\"/><text x=\"41\" y=\"449.5\" font-size=\"10\" text-anchor=\"end\" textLength=\"24\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-label sq-tick-label\" data-sq-role=\"tick-label\">0.00</text><text x=\"41\" y=\"374.5\" font-size=\"10\" text-anchor=\"end\" textLength=\"36\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-label sq-tick-label\" data-sq-role=\"tick-label\">200.00</text><text x=\"41\" y=\"299.5\" font-size=\"10\" text-anchor=\"end\" textLength=\"36\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-label sq-tick-label\" data-sq-role=\"tick-label\">400.00</text><text x=\"41\" y=\"224.5\" font-size=\"10\" text-anchor=\"end\" textLength=\"36\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-label sq-tick-label\" data-sq-role=\"tick-label\">600.00</text><text x=\"41\" y=\"149.5\" font-size=\"10\" text-anchor=\"end\" textLength=\"36\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-label sq-tick-label\" data-sq-role=\"tick-label\">800.00</text><text x=\"41\" y=\"74.5\" font-size=\"10\" text-anchor=\"end\" textLength=\"48\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-label sq-tick-label\" data-sq-role=\"tick-label\">1,000.00</text><line x1=\"269.3\" y1=\"71\" x2=\"318.7\" y2=\"71\" stroke=\"#c9d1df\" stroke-width=\"1\" stroke-opacity=\"0.85\" stroke-dasharray=\"4 3\" stroke-linecap=\"butt\" class=\"sq-connector\" data-sq-role=\"waterfall-connector\" data-sq-target-key=\"t-connector-572a9e2cbfb5311a\" data-sq-category-index=\"0\"/><line x1=\"516.3\" y1=\"132.875\" x2=\"565.7\" y2=\"132.875\" stroke=\"#c9d1df\" stroke-width=\"1\" stroke-opacity=\"0.85\" stroke-dasharray=\"4 3\" stroke-linecap=\"butt\" class=\"sq-connector\" data-sq-role=\"waterfall-connector\" data-sq-target-key=\"t-connector-87269ac1f3c4106c\" data-sq-category-index=\"1\"/><rect x=\"71.7\" y=\"71\" width=\"197.6\" height=\"375\" fill=\"url(#sq-8e19c06d5bc6-gradient-0)\" stroke=\"#2563eb\" stroke-width=\"1\" class=\"sq-mark sq-bar\" data-sq-role=\"waterfall-start\" data-sq-target-key=\"t-step-d8bbab58fcf438f1\" data-sq-category-index=\"0\" data-sq-palette-index=\"0\"/><rect x=\"318.7\" y=\"71\" width=\"197.6\" height=\"45\" fill=\"#d92d20\" stroke=\"#d92d20\" stroke-width=\"1\" class=\"sq-mark sq-bar\" data-sq-role=\"waterfall-contribution\" data-sq-target-key=\"t-segment-1c5d433cef2cfbbe\" data-sq-series-index=\"0\" data-sq-category-index=\"1\" data-sq-palette-index=\"4\"/><rect x=\"318.7\" y=\"116\" width=\"197.6\" height=\"16.875\" fill=\"#168a5b\" stroke=\"#168a5b\" stroke-width=\"1\" class=\"sq-mark sq-bar\" data-sq-role=\"waterfall-contribution\" data-sq-target-key=\"t-segment-3ef1a89d7c289556\" data-sq-series-index=\"1\" data-sq-category-index=\"1\" data-sq-palette-index=\"2\"/><rect x=\"565.7\" y=\"132.875\" width=\"197.6\" height=\"313.125\" fill=\"url(#sq-8e19c06d5bc6-gradient-1)\" stroke=\"#1e4079\" stroke-width=\"1\" class=\"sq-mark sq-bar\" data-sq-role=\"waterfall-total\" data-sq-target-key=\"t-step-6654cc4287a02129\" data-sq-category-index=\"2\" data-sq-palette-index=\"0\"/><line x1=\"47\" y1=\"446\" x2=\"788\" y2=\"446\" stroke=\"#c9d1df\" stroke-width=\"1.5\" class=\"sq-axis\" data-sq-role=\"axis-line\"/><line x1=\"170.5\" y1=\"446\" x2=\"170.5\" y2=\"450\" stroke=\"#c9d1df\" stroke-width=\"1\" class=\"sq-tick\" data-sq-role=\"tick\"/><line x1=\"417.5\" y1=\"446\" x2=\"417.5\" y2=\"450\" stroke=\"#c9d1df\" stroke-width=\"1\" class=\"sq-tick\" data-sq-role=\"tick\"/><line x1=\"664.5\" y1=\"446\" x2=\"664.5\" y2=\"450\" stroke=\"#c9d1df\" stroke-width=\"1\" class=\"sq-tick\" data-sq-role=\"tick\"/><text x=\"170.5\" y=\"462\" font-size=\"10\" text-anchor=\"middle\" textLength=\"12\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-label sq-category-label\" data-sq-role=\"category-label\" data-sq-category-index=\"0\">Q1</text><text x=\"417.5\" y=\"462\" font-size=\"10\" text-anchor=\"middle\" textLength=\"24\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-label sq-category-label\" data-sq-role=\"category-label\" data-sq-category-index=\"1\">Cost</text><text x=\"664.5\" y=\"462\" font-size=\"10\" text-anchor=\"middle\" textLength=\"12\" lengthAdjust=\"spacing\" fill=\"#667085\" class=\"sq-label sq-category-label\" data-sq-role=\"category-label\" data-sq-category-index=\"2\">Q2</text></g></svg>",
  "accessibility": {
    "title": "Cost drivers between quarters",
    "detail": "full",
    "description": "Waterfall chart. 3 steps, bridging 1000 to 835, a net down of 165, vertical. Running total: Q1 1000, Cost 835, Q2 835. Movements: Cost down 165. Cost is Labour -120 plus Freight -45. 0 increases and 1 decrease; largest decrease 165 at Cost. All values are listed."
  },
  "viewBox": "0 0 800 480"
}
  • Use the published full rendering profile for the best result: Squillify styling, certified bridge motion and a mountable host artifact
  • Explain how last period's figure became this period's
  • Show the drivers of a margin, cash or headcount movement
  • Publish a bridge whose subtotals you want checked, not just drawn

Avoid for

  • Comparing categories that do not accumulate
  • Hundreds of movements — we refuse an illegible bridge
  • Figures you only have as floating-point numbers you cannot reproduce

Composable with

Safety & privacy

  • Runs without calling any external service.
  • Your payloads are never stored permanently.
  • Telemetry captures metadata only — never your payload contents.
  • We never train models on your payloads.
  • No human reviews your payloads.

Tags

waterfall-chartvisualizationsvgchart