Income as columns and expenses as a line on one chart, so the gap between them shows year by year. The last year is a projection, drawn faint and dashed.
Two measures on one chart read best when they look different: columns for the amounts, a line for what they are compared against. Here both share one axis, so the gap between income and expenses is the story, and the faint, dashed last year keeps the plan apart from the results.
Built with amCharts 5, a JavaScript library. The JavaScript includes your theme and renderer (canvas or SVG); the JSON also follows the changes you make with the controls.
npm install @amcharts/amcharts5. See Getting started.
The same chart as a JSON config. Chart Maker reads and writes this format, the library's JSON parser turns it into a chart, and the <am5-chart> element puts it on a page with no code."type" is the class to create and "settings" what you would pass to its new() in code; "properties" fill in its parts, lists such as series and axes or templates such as columns.template; "refs" holds objects used in more than one place ("#series-0" stands for the one named series-0); a string starting with "@" takes a value from another object, such as "@series.get('fill')".
A complete page. Save it as an .html file and open it in a browser.
Paste this into an AI coding assistant to build the chart from scratch. More in amCharts & AI.
// Create root element
// https://www.amcharts.com/docs/v5/getting-started/#Root_element
var root = am5.Root.new("chartdiv");
// Set themes
// https://www.amcharts.com/docs/v5/concepts/themes/
root.setThemes([am5themes_Animated.new(root), am5themes_Responsive.new(root)]);
// Create chart
// https://www.amcharts.com/docs/v5/charts/xy-chart/
var chart = root.container.children.push(
am5xy.XYChart.new(root, {
panX: false, // the plot doesn't pan when dragged
panY: false,
wheelX: "panX", // a horizontal wheel or trackpad swipe pans the years
wheelY: "zoomX", // the vertical wheel zooms in on them
paddingLeft: 0, // the value labels sit at the chart's left edge
layout: root.verticalLayout
})
);
var data = [
{
year: "2020",
income: 23.5,
expenses: 21.1
},
{
year: "2021",
income: 26.2,
expenses: 30.5
},
{
year: "2022",
income: 30.1,
expenses: 34.9
},
{
year: "2023",
income: 29.5,
expenses: 31.1
},
{
year: "2024",
income: 30.6,
expenses: 28.2,
// from this point on the line is dashed: the projected part
strokeSettings: {
stroke: chart.get("colors").getIndex(1),
strokeWidth: 3,
strokeDasharray: [5, 5]
}
},
{
year: "2025",
income: 34.1,
expenses: 32.9,
// the projected year's column: faint, with a dashed outline
columnSettings: {
strokeWidth: 1,
strokeDasharray: [5],
fillOpacity: 0.2
},
info: "(projection)" // shown after the value in the tooltips
}
];
// Create axes
// https://www.amcharts.com/docs/v5/charts/xy-chart/axes/
var xRenderer = am5xy.AxisRendererX.new(root, {
minorGridEnabled: true, // a grid line for every year, even one whose label is skipped
minGridDistance: 60 // at least 60px between labels; on narrow screens some are skipped
});
var xAxis = chart.xAxes.push(
am5xy.CategoryAxis.new(root, {
categoryField: "year",
renderer: xRenderer,
tooltip: am5.Tooltip.new(root, {}) // the cursor shows the year on the axis
})
);
xRenderer.grid.template.setAll({
// draw each grid line at the end of its year's cell instead of at its start
location: 1
})
xAxis.data.setAll(data);
var yAxis = chart.yAxes.push(
am5xy.ValueAxis.new(root, {
min: 0, // the columns start at zero
// 10% of room above the highest value
extraMax: 0.1,
renderer: am5xy.AxisRendererY.new(root, {
strokeOpacity: 0.1 // a faint axis line
})
})
);
// Add series
// https://www.amcharts.com/docs/v5/charts/xy-chart/series/
var series1 = chart.series.push(
am5xy.ColumnSeries.new(root, {
name: "Income",
xAxis: xAxis,
yAxis: yAxis,
valueYField: "income",
categoryXField: "year",
tooltip: am5.Tooltip.new(root, {
pointerOrientation: "horizontal", // the tooltip points sideways
labelText: "{name} in {categoryX}: {valueY} {info}" // {info} adds "(projection)" for the last year
})
})
);
series1.columns.template.setAll({
tooltipY: am5.percent(10), // the tooltip points near the top of the column
templateField: "columnSettings" // the projected year's look from its columnSettings
});
series1.data.setAll(data);
var series2 = chart.series.push(
am5xy.LineSeries.new(root, {
name: "Expenses",
xAxis: xAxis,
yAxis: yAxis,
valueYField: "expenses",
categoryXField: "year",
tooltip: am5.Tooltip.new(root, {
pointerOrientation: "horizontal", // the tooltip points sideways
labelText: "{name} in {categoryX}: {valueY} {info}"
})
})
);
series2.strokes.template.setAll({
strokeWidth: 3, // a 3px line
templateField: "strokeSettings" // dashed from the point that has strokeSettings
});
series2.data.setAll(data);
// a circle on each point, filled with the background color so it looks hollow
series2.bullets.push(function () {
return am5.Bullet.new(root, {
sprite: am5.Circle.new(root, {
strokeWidth: 3, // a 3px outline...
stroke: series2.get("stroke"), // ...in the line's color
radius: 5, // 5px
fill: root.interfaceColors.get("background")
})
});
});
chart.set("cursor", am5xy.XYCursor.new(root, {})); // both series' tooltips show for the hovered year
// Add legend, in the top left corner of the plot area
// https://www.amcharts.com/docs/v5/charts/xy-chart/legend-xy-series/
var legend = chart.plotContainer.children.push(
am5.Legend.new(root, {
x: 0, // the legend's top left corner...
y: 0, // ...at the plot's top left corner
paddingTop: 10, // 10px in from the corner
paddingLeft: 10
})
);
legend.data.setAll(chart.series.values);
// Make stuff animate on load
// https://www.amcharts.com/docs/v5/concepts/animations/
chart.appear(1000, 100);
series1.appear();
series2.appear();
{
"type": "XYChart",
"settings": {
"panX": false,
"panY": false,
"wheelX": "panX",
"wheelY": "zoomX",
"paddingLeft": 0,
"layout": {
"type": "VerticalLayout"
},
"cursor": {
"type": "XYCursor"
}
},
"properties": {
"series": [
"#series-0",
"#series-1"
],
"xAxes": [
"#axis-0"
],
"yAxes": [
"#axis-1"
],
"plotContainer": {
"children": [
{
"type": "Legend",
"settings": {
"x": 0,
"y": 0,
"paddingTop": 10,
"paddingLeft": 10
},
"properties": {
"data": [
"#series-0",
"#series-1"
]
},
"index": 5
}
]
}
},
"refs": [
{
"data-0": [
{
"year": "2020",
"income": 23.5,
"expenses": 21.1
},
{
"year": "2021",
"income": 26.2,
"expenses": 30.5
},
{
"year": "2022",
"income": 30.1,
"expenses": 34.9
},
{
"year": "2023",
"income": 29.5,
"expenses": 31.1
},
{
"year": "2024",
"income": 30.6,
"expenses": 28.2,
"strokeSettings": {
"stroke": {
"type": "Color",
"value": "#6794dc"
},
"strokeWidth": 3,
"strokeDasharray": [
5,
5
],
"__parse": true
},
"__parse": true
},
{
"year": "2025",
"income": 34.1,
"expenses": 32.9,
"columnSettings": {
"strokeWidth": 1,
"strokeDasharray": [
5
],
"fillOpacity": 0.2,
"__parse": true
},
"info": "(projection)",
"__parse": true
}
]
},
{
"axis-0": {
"type": "CategoryAxis",
"settings": {
"categoryField": "year",
"renderer": {
"type": "AxisRendererX",
"settings": {
"minorGridEnabled": true,
"minGridDistance": 60
},
"properties": {
"grid": {
"properties": {
"template": {
"settings": {
"location": 1
}
}
}
}
}
},
"tooltip": {
"type": "Tooltip"
}
},
"properties": {
"data": "#data-0"
}
}
},
{
"axis-1": {
"type": "ValueAxis",
"settings": {
"min": 0,
"extraMax": 0.1,
"renderer": {
"type": "AxisRendererY",
"settings": {
"strokeOpacity": 0.1
}
}
}
}
},
{
"series-0": {
"type": "ColumnSeries",
"settings": {
"name": "Income",
"xAxis": "#axis-0",
"yAxis": "#axis-1",
"valueYField": "income",
"categoryXField": "year",
"tooltip": {
"type": "Tooltip",
"settings": {
"pointerOrientation": "horizontal",
"labelText": "{name} in {categoryX}: {valueY} {info}"
}
}
},
"properties": {
"data": "#data-0",
"columns": {
"properties": {
"template": {
"settings": {
"tooltipY": {
"type": "Percent",
"value": 10
},
"templateField": "columnSettings"
}
}
}
}
}
}
},
{
"series-1": {
"type": "LineSeries",
"settings": {
"name": "Expenses",
"xAxis": "#axis-0",
"yAxis": "#axis-1",
"valueYField": "expenses",
"categoryXField": "year",
"tooltip": {
"type": "Tooltip",
"settings": {
"pointerOrientation": "horizontal",
"labelText": "{name} in {categoryX}: {valueY} {info}"
}
}
},
"properties": {
"data": "#data-0",
"strokes": {
"properties": {
"template": {
"settings": {
"strokeWidth": 3,
"templateField": "strokeSettings"
}
}
}
},
"bullets": [
{
"type": "Bullet",
"settings": {
"sprite": {
"type": "Circle",
"settings": {
"strokeWidth": 3,
"stroke": "@series.get('stroke')",
"radius": 5,
"fill": "@root.interfaceColors.get('background')"
}
}
}
}
]
}
}
}
]
}