Appearance
Arrows
Difference, CAGR, and level arrows live under annotations as id-keyed maps. Each entry is one arrow.
| Kind | chartData path | What it shows |
|---|---|---|
| Difference | annotations.differenceArrows.<id> | Absolute and/or % delta between two columns |
| CAGR | annotations.cagrs.<id> | Compound annual growth between two columns |
| Level | annotations.levelArrows.<id> | Horizontal compare between two anchor points |
CAGR and difference use simple column indices. Level arrows need fuller anchorPoints geometry (barTop / linePoint, columnIndex, seriesIndex), so they are the easiest of the three to mis-wire by hand.
Chart type support
| Kind | Stacked / clustered bar | 100% stacked | Waterfall | Combo (bars) | Line / area | Pie / scatter / mekko |
|---|---|---|---|---|---|---|
| Difference | yes | yes* | yes | yes | no | no |
| CAGR | yes | no | yes | yes | no | no |
| Level | yes | yes | yes | yes | yes | no |
*Engine can draw difference on 100% stacked; the editor add menu may not offer it on every type. Combo arrows attach to bar series only.
Shared styling
These fields are common across the three arrow maps:
| Field | Type | Typical default | Notes |
|---|---|---|---|
lineColor | string | '#000000' | Stroke color |
lineWidth | number | 1.5 | Stroke width (px) |
lineStyle | string | 'solid' | solid · dashed · densely-dashed · dotted · dash-dot |
labelShape | string | 'pill' | none · rounded · pill · circle |
fontSize | number | 8–10 | Label size (pt) |
chartType | string | current type | Snapshotted when the arrow was created |
Category anchors (start / end / columnIndex) are 0-based column indices in seriesData (after the header row / label column).
Labels
displayMode (and default labelParts) drive the computed absolute / % text. Optional custom copy uses label on level arrows, and text or label on difference / CAGR arrows.
If you set custom text and leave a computed mode active, both can render (for example +36 next to another +36, or Expansion plus a computed +15%). For custom text only:
js
label: 'Expansion',
labelParts: ['label'],To show custom text plus a computed part: labelParts: ['label', 'percentage'] (or 'absolute').
Layout offsets
Fields like verticalLinePositionIndex, horizontalYOffset, and CAGR’s startYOffset / endYOffset are usually set by dragging in edit mode. When hand-authoring, start with null / omit offsets where possible, or copy them from an exported config.
Difference arrows (annotations.differenceArrows)
U-shaped connector between two columns. Label is usually a computed delta (displayMode / labelParts).
js
annotations: {
differenceArrows: {
q1VsQ2: {
start: 0,
end: 1,
startSeries: null, // null = bar tops / stack totals
endSeries: null,
displayMode: 'percentage',
chartType: 'stackedBar',
lineColor: '#111827',
lineWidth: 1.5,
lineStyle: 'solid',
labelShape: 'pill',
},
},
}| Field | Meaning |
|---|---|
start / end | Column indices |
startSeries / endSeries | Series index, or null for tops / totals |
displayMode | percentage · absolute · absoluteWithPercentage · percentageWithAbsolute |
labelParts | Composite label parts (e.g. ['percentage'], ['label','percentage']) |
label / text | Optional custom text |
numberFormatAbsolute / numberFormatPercentage | Per-arrow number specs — see Number formats |
horizontalYOffset | Drag offset for the horizontal segment |
CAGR arrows (annotations.cagrs)
Diagonal growth-rate arrow. Duration comes from the category span (|end - start|), or the series span when comparing within a column.
js
annotations: {
cagrs: {
fySpan: {
start: 0,
end: 5,
startSeries: null,
endSeries: null,
chartType: 'stackedBar',
lineColor: '#000000',
lineWidth: 1.5,
lineStyle: 'solid',
startYOffset: -8,
endYOffset: -8,
},
},
}| Field | Meaning |
|---|---|
start / end | Column indices |
startSeries / endSeries | Series index, or null for tops / totals |
text | Optional custom label override |
startYOffset / endYOffset | Per-endpoint vertical drag offsets |
On waterfall, CAGR uses cumulative totals (not step deltas).
Level arrows (annotations.levelArrows)
Horizontal comparison between two explicit anchors (bar tops, segments, or line points).
js
annotations: {
levelArrows: {
seriesCompare: {
anchorPoints: [
{ columnIndex: 4, seriesIndex: 2, type: 'barTop' },
{ columnIndex: 5, seriesIndex: 2, type: 'barTop' },
],
verticalLinePositionIndex: null, // or a snap index from the editor
displayMode: 'percentage',
chartType: 'stackedBar',
lineColor: '#000000',
lineWidth: 1.5,
lineStyle: 'solid',
label: '',
labelShape: 'pill',
fontSize: 8,
},
},
}Custom label only (no computed delta beside it):
js
label: '+36',
labelParts: ['label'],Line-chart variant:
js
anchorPoints: [
{ columnIndex: 0, seriesIndex: 0, type: 'linePoint' },
{ columnIndex: 4, seriesIndex: 0, type: 'linePoint' },
]| Field | Meaning |
|---|---|
anchorPoints | Two anchors: columnIndex, seriesIndex (or 'total' / null), optional type |
verticalLinePositionIndex | Snap index for the vertical rail / label (null = engine default) |
displayMode / labelParts | What the label shows |
label | Optional custom text |
levelArrows is not seeded in defaults; the bag appears when the first level arrow is added.
Authoring tip
Add arrows in edit mode (or Hub MCP manage_annotations), then snapshot with getChartData() / exportConfig(). That picks up anchors and layout offsets without guessing snap indices.
See also Level lines.