Stacked bar chart
Use a stacked bar chart to show a total and its composition at the same time, such as revenue split by plan tier across months. It works best with two to four series. Beyond that, segments become too thin to compare accurately. If only the composition matters and not the total, use a 100% stacked bar instead.
Minimal dvt spec
The panel body for a stacked bar chart. Add an id and title of your own; the sample data is inline, so it renders the moment you paste it into a dvt dashboard. Point it at your warehouse by swapping that sample data for a data block whose query returns the same shape.
View spec
{
"type": "chart:bar:stacked",
"data": {
"rows": [
{
"month": "Jan",
"starter": 30,
"pro": 50
},
{
"month": "Feb",
"starter": 38,
"pro": 62
},
{
"month": "Mar",
"starter": 44,
"pro": 70
}
]
},
"spec": {
"series": [
{
"type": "bar",
"stack": "plan"
},
{
"type": "bar",
"stack": "plan"
}
]
}
}Customize
Every control in dvt's edit tray writes a field of this spec; the variants below change the minimal spec above, and the reference below lists every control and ChartSpec field this type accepts.
Legend and series colors
Move the legend to the top and pin explicit colors to two categories.
What changed
{
"type": "chart:bar:stacked",
"data": {
"rows": [
{
"month": "Jan",
"starter": 30,
"pro": 50
},
{
"month": "Feb",
"starter": 38,
"pro": 62
},
{
"month": "Mar",
"starter": 44,
"pro": 70
}
]
},
"spec": {
"series": [
{
"type": "bar",
"stack": "plan"
},
{
"type": "bar",
"stack": "plan"
}
- ]
+ ],
+ "legend": {
+ "show": true,
+ "position": "top"
+ },
+ "categoryColors": {
+ "starter": "#818CF8",
+ "pro": "#4F46E5"
+ }
}
}Full spec
{
"type": "chart:bar:stacked",
"data": {
"rows": [
{
"month": "Jan",
"starter": 30,
"pro": 50
},
{
"month": "Feb",
"starter": 38,
"pro": 62
},
{
"month": "Mar",
"starter": 44,
"pro": 70
}
]
},
"spec": {
"series": [
{
"type": "bar",
"stack": "plan"
},
{
"type": "bar",
"stack": "plan"
}
],
"legend": {
"show": true,
"position": "top"
},
"categoryColors": {
"starter": "#818CF8",
"pro": "#4F46E5"
}
}
}Inside data labels
Print each segment value inside its own bar instead of on hover.
What changed
{
"type": "chart:bar:stacked",
"data": {
"rows": [
{
"month": "Jan",
"starter": 30,
"pro": 50
},
{
"month": "Feb",
"starter": 38,
"pro": 62
},
{
"month": "Mar",
"starter": 44,
"pro": 70
}
]
},
"spec": {
"series": [
{
"type": "bar",
- "stack": "plan"
+ "stack": "plan",
+ "label": {
+ "show": true,
+ "position": "inside"
+ }
},
{
"type": "bar",
- "stack": "plan"
+ "stack": "plan",
+ "label": {
+ "show": true,
+ "position": "inside"
+ }
}
]
}
}Full spec
{
"type": "chart:bar:stacked",
"data": {
"rows": [
{
"month": "Jan",
"starter": 30,
"pro": 50
},
{
"month": "Feb",
"starter": 38,
"pro": 62
},
{
"month": "Mar",
"starter": 44,
"pro": 70
}
]
},
"spec": {
"series": [
{
"type": "bar",
"stack": "plan",
"label": {
"show": true,
"position": "inside"
}
},
{
"type": "bar",
"stack": "plan",
"label": {
"show": true,
"position": "inside"
}
}
]
}
}Normalize to 100%
Flip the panel type so every month sums to a full bar of share.
What changed
{
- "type": "chart:bar:stacked",
+ "type": "chart:bar:stacked-percent",
"data": {
"rows": [
{
"month": "Jan",
"starter": 30,
"pro": 50
},
{
"month": "Feb",
"starter": 38,
"pro": 62
},
{
"month": "Mar",
"starter": 44,
"pro": 70
}
]
},
"spec": {
"series": [
{
"type": "bar",
"stack": "plan"
},
{
"type": "bar",
"stack": "plan"
}
]
}
}Full spec
{
"type": "chart:bar:stacked-percent",
"data": {
"rows": [
{
"month": "Jan",
"starter": 30,
"pro": 50
},
{
"month": "Feb",
"starter": 38,
"pro": 62
},
{
"month": "Mar",
"starter": 44,
"pro": 70
}
]
},
"spec": {
"series": [
{
"type": "bar",
"stack": "plan"
},
{
"type": "bar",
"stack": "plan"
}
]
}
}Everything you can set
| Control | Layer | Where it lands |
|---|---|---|
| Data | ||
| Chart type | Core | type |
| Category column | Core | spec.categoryField |
| Series column | Core | spec.seriesField |
| Value column | Core | spec.valueField |
| Style | ||
| Stack colors | Core | spec.categoryColors |
| Color scheme | Core | spec.palette |
| Labels | ||
| Show legend | ECharts | spec.legend.show |
| Legend position | ECharts | spec.legend.position |
| Show data labels | ECharts | spec.series[].label.show |
| Label position | ECharts | spec.series[].label.position |
| Axes | ||
| Stacking | Core | type |
| X-axis name | Core | spec.xAxis.name |
| Y-axis name | Core | spec.yAxis.name |
| Y-axis min | Core | spec.yAxis.min |
| Y-axis max | Core | spec.yAxis.max |
| Y-axis scale | Core | spec.yAxis.type |
| X-axis label rotate | Core | spec.xAxis.axisLabel.rotate |
| X-axis label size | Core | spec.xAxis.axisLabel.fontSize |
| Y-axis label size | Core | spec.yAxis.axisLabel.fontSize |
| X-axis label spacing | Core | spec.xAxis.axisLabel.margin |
| X-axis format | Core | spec.xAxis.axisLabel.format |
| Y-axis format | Core | spec.yAxis.axisLabel.format |
| X-axis gridlines | Core | spec.gridlines.x.show |
| Y-axis gridlines | Core | spec.gridlines.y.show |
| Plot density | Core | spec.density |
| Advanced | ||
| Boundary gap | Core | spec.xAxis.boundaryGap |
| Annotations | Core | spec.annotations |
Chart spec fields
Every chart type accepts these ChartSpec fields. Most are dvt spec fields the renderer compiles for you. grid passes straight through to ECharts; series mixes ECharts series options with dvt's dataField; and any key on xAxis, yAxis, legend, or tooltip that is not a documented dvt key passes through too.
| Field | What it does |
|---|---|
xAxis | One axis, or an array of axes, via AxisSpec: type, name, min, max, scale, log, and label formatting. |
yAxis | Same shape as xAxis, for the value axis: type, name, min, max, scale, log, and label formatting. |
series | Per-series overrides: type, dataField, name, stack, smooth, itemStyle, lineStyle, areaStyle, label. |
legend | Show or hide the legend, set its position, and format an aggregated value shown next to each entry. |
tooltip | Choose which fields appear, a totals row, field order, a custom template, and crosshair behavior. |
grid | A raw ECharts grid box (left, right, top, bottom, containLabel), passed through unmodified. |
funnelRate | Show a conversion-rate label on a funnel: step, overall, total, or none, with a precision. |
label | Data labels: show, position, format, a derived value (percent of total, delta), rotation, size, color. |
gridlines | Toggle and style gridlines per axis: show, dashed or solid style, width, and color. |
banding | Alternating background bands along one axis, to make long rows or columns easier to scan. |
plotArea | Set the plot area's background fill and border color. |
gridPadding | Explicit left, right, top, and bottom padding around the plot area, each a number or a string. |
density | Overall plot density: comfortable (default spacing) or compact (tighter margins). |
palette | Name a registered color scheme to apply to the series, instead of the default categorical colors. |
categoryColors | Map specific category values to explicit colors, overriding the palette for just those categories. |
colorRules | Apply a color to a data point the first time one of its ordered when conditions matches. |
colorScale | A continuous color scale (sequential, diverging, or piecewise) with a scheme, domain, and buckets. |
annotations | Fixed or computed reference lines, bands, points, or text, placed at an axis value or a stat of a field. |
trendline | Overlay a fitted trend line: true for a default linear fit, or an object naming the method and order. |
footnotes | Up to 50 footnote entries, each a marker plus explanatory text, rendered below the chart. |
sourceNote | A single line of source-attribution text rendered below the chart. |
Build it in dvt
Every chart type on this site is a few lines of the same declarative JSON spec. Read the full spec format, or connect an AI agent to your warehouse and build one live in the quickstart.
Follow the build
dvt is in founding research. Leave your email and we'll reach out when there's something to see — no newsletters, no noise.