One solid line for what has happened, then three dashed lines for what might: a projection, and what easing or tightening the rules could do by December.
A divergent line chart shows one history and several futures. Starting every scenario from the last real value makes clear where the facts end and the guesses begin, and the dashes keep the forecasts from passing as data.
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: true, // drag the plot sideways to pan...
panY: true, // ...or up and down
wheelX: "panX", // a horizontal wheel or trackpad swipe pans
wheelY: "zoomX", // the vertical wheel zooms in on the months
layout: root.verticalLayout, // the legend on top, the plot under it
pinchZoomX:true // pinch to zoom on touch screens
}));
// Data
var data = [{
"date": new Date(2025, 0, 1).getTime(),
"observed": 0
}, {
"date": new Date(2025, 1, 1).getTime(),
"observed": 4000
}, {
"date": new Date(2025, 2, 1).getTime(),
"observed": 55000
}, {
"date": new Date(2025, 3, 1).getTime(),
"observed": 220000
}, {
"date": new Date(2025, 4, 1).getTime(),
"observed": 390000
}, {
"date": new Date(2025, 5, 1).getTime(),
"observed": 550000
}, {
"date": new Date(2025, 6, 1).getTime(),
"observed": 720000,
// the three outlooks start at the last observed value, so their lines branch off here
"easing": 720000,
"projection": 720000,
"stricter": 720000
}, {
"date": new Date(2025, 7, 1).getTime(),
"easing": 900000,
"projection": 900000,
"stricter": 900000
}, {
"date": new Date(2025, 8, 1).getTime(),
"easing": 1053000,
"projection": 1053000,
"stricter": 1053000
}, {
"date": new Date(2025, 9, 1).getTime(),
"easing": 1252000,
"projection": 1249000,
"stricter": 1232000
}, {
"date": new Date(2025, 10, 1).getTime(),
"easing": 1674000,
"projection": 1604000,
"stricter": 1415000
}, {
"date": new Date(2025, 11, 1).getTime(),
"easing": 3212000,
"projection": 2342000,
"stricter": 1751000
}];
// Add cursor
// https://www.amcharts.com/docs/v5/charts/xy-chart/cursor/
var cursor = chart.set("cursor", am5xy.XYCursor.new(root, {}));
cursor.lineY.set("visible", false); // no horizontal cursor line, only the vertical one
// Add scrollbar, hidden at first
// https://www.amcharts.com/docs/v5/charts/xy-chart/scrollbars/
chart.set("scrollbarX", am5.Scrollbar.new(root, { orientation: "horizontal", forceHidden: true }));
// Create axes
// https://www.amcharts.com/docs/v5/charts/xy-chart/axes/
var xAxis = chart.xAxes.push(am5xy.DateAxis.new(root, {
maxDeviation: 0.3, // pan up to 30% of the visible range past the first and last month
baseInterval: { // one point per month
timeUnit: "month",
count: 1
},
renderer: am5xy.AxisRendererX.new(root, {
minorGridEnabled: true, // fainter grid lines between the labeled months
minGridDistance: 60 // at least 60px between labels; on narrow screens some are skipped
}),
tooltip: am5.Tooltip.new(root, {}) // the cursor shows the month on the axis
}));
var yAxis = chart.yAxes.push(am5xy.ValueAxis.new(root, {
maxDeviation: 0.3, // the values can be panned 30% past their range too
renderer: am5xy.AxisRendererY.new(root, {})
}));
// Add series
// https://www.amcharts.com/docs/v5/charts/xy-chart/series/
function createSeries(field, name, color, dashed) {
var series = chart.series.push(am5xy.SmoothedXLineSeries.new(root, {
name: name,
xAxis: xAxis,
yAxis: yAxis,
valueYField: field,
valueXField: "date",
stroke: color, // the line's own color
tooltip: am5.Tooltip.new(root, {
pointerOrientation: "horizontal", // the tooltip sits beside the line
// the tooltip takes the line's color, set below, rather than the series' fill
getFillFromSprite: false,
// the name in bold, then the month and the value
labelText: "[bold]{name}[/]\n{valueX}: [bold]{valueY}[/]"
})
}));
series.get("tooltip").get("background").setAll({
fillOpacity: 0.7, // a see-through box...
fill: color, // ...in the line's color
// no pointer: a plain box beside the cursor
pointerBaseWidth: 0
});
series.strokes.template.setAll({
strokeWidth: 2 // a 2px line
});
if (dashed) {
series.strokes.template.set("strokeDasharray", [5, 5]); // dashed: 5px dash, 5px gap
}
series.data.setAll(data);
series.appear(1000);
return series;
}
// Colors from the theme, every third one so the lines are easy to tell apart. Observed and Projection share a
// color: the projection carries the observed line on
var colors = chart.get("colors");
createSeries("observed", "Observed", colors.getIndex(0));
createSeries("easing", "Easing rules", colors.getIndex(3), true);
createSeries("stricter", "Stricter rules", colors.getIndex(6), true);
createSeries("projection", "Projection", colors.getIndex(0), true);
// Show dates in tooltips as month and year
// https://www.amcharts.com/docs/v5/concepts/formatters/formatting-dates/
root.dateFormatter.setAll({
dateFormat: "MMM yyyy",
dateFields: ["valueX"]
});
// Add legend above the chart
// https://www.amcharts.com/docs/v5/charts/xy-chart/legend-xy-series/
var legend = chart.children.unshift(
am5.Legend.new(root, {
centerX: am5.p50, // the legend's middle...
x: am5.p50, // ...at the middle of the chart
marginBottom: 15 // space between the legend and the plot
})
);
legend.data.setAll(chart.series.values);
// Make stuff animate on load
// https://www.amcharts.com/docs/v5/concepts/animations/
chart.appear(1000, 100);
{
"type": "XYChart",
"settings": {
"panX": true,
"panY": true,
"wheelX": "panX",
"wheelY": "zoomX",
"layout": {
"type": "VerticalLayout"
},
"pinchZoomX": true,
"cursor": {
"type": "XYCursor",
"properties": {
"lineY": {
"settings": {
"visible": false
}
}
}
},
"scrollbarX": {
"type": "Scrollbar",
"settings": {
"orientation": "horizontal",
"forceHidden": true
}
}
},
"properties": {
"series": [
"#series-0",
"#series-1",
"#series-2",
"#series-3"
],
"xAxes": [
"#axis-0"
],
"yAxes": [
"#axis-1"
]
},
"children": [
{
"type": "Legend",
"settings": {
"centerX": {
"type": "Percent",
"value": 50
},
"x": {
"type": "Percent",
"value": 50
},
"marginBottom": 15
},
"properties": {
"data": [
"#series-0",
"#series-1",
"#series-2",
"#series-3"
]
},
"index": 0
}
],
"refs": [
{
"data-0": [
{
"date": 1735682400000,
"observed": 0
},
{
"date": 1738360800000,
"observed": 4000
},
{
"date": 1740780000000,
"observed": 55000
},
{
"date": 1743454800000,
"observed": 220000
},
{
"date": 1746046800000,
"observed": 390000
},
{
"date": 1748725200000,
"observed": 550000
},
{
"date": 1751317200000,
"observed": 720000,
"easing": 720000,
"projection": 720000,
"stricter": 720000
},
{
"date": 1753995600000,
"easing": 900000,
"projection": 900000,
"stricter": 900000
},
{
"date": 1756674000000,
"easing": 1053000,
"projection": 1053000,
"stricter": 1053000
},
{
"date": 1759266000000,
"easing": 1252000,
"projection": 1249000,
"stricter": 1232000
},
{
"date": 1761948000000,
"easing": 1674000,
"projection": 1604000,
"stricter": 1415000
},
{
"date": 1764540000000,
"easing": 3212000,
"projection": 2342000,
"stricter": 1751000
}
]
},
{
"axis-0": {
"type": "DateAxis",
"settings": {
"maxDeviation": 0.3,
"baseInterval": {
"timeUnit": "month",
"count": 1
},
"renderer": {
"type": "AxisRendererX",
"settings": {
"minorGridEnabled": true,
"minGridDistance": 60
}
},
"tooltip": {
"type": "Tooltip"
}
}
}
},
{
"axis-1": {
"type": "ValueAxis",
"settings": {
"maxDeviation": 0.3,
"renderer": {
"type": "AxisRendererY"
}
}
}
},
{
"series-0": {
"type": "SmoothedXLineSeries",
"settings": {
"name": "Observed",
"xAxis": "#axis-0",
"yAxis": "#axis-1",
"valueYField": "observed",
"valueXField": "date",
"stroke": {
"type": "Color",
"value": "#67b7dc"
},
"tooltip": {
"type": "Tooltip",
"settings": {
"pointerOrientation": "horizontal",
"getFillFromSprite": false,
"labelText": "[bold]{name}[/]\n{valueX}: [bold]{valueY}[/]"
},
"properties": {
"background": {
"settings": {
"fillOpacity": 0.7,
"fill": {
"type": "Color",
"value": "#67b7dc"
},
"pointerBaseWidth": 0
}
}
}
}
},
"properties": {
"data": "#data-0",
"strokes": {
"properties": {
"template": {
"settings": {
"strokeWidth": 2
}
}
}
}
}
}
},
{
"series-1": {
"type": "SmoothedXLineSeries",
"settings": {
"name": "Easing rules",
"xAxis": "#axis-0",
"yAxis": "#axis-1",
"valueYField": "easing",
"valueXField": "date",
"stroke": {
"type": "Color",
"value": "#8067dc"
},
"tooltip": {
"type": "Tooltip",
"settings": {
"pointerOrientation": "horizontal",
"getFillFromSprite": false,
"labelText": "[bold]{name}[/]\n{valueX}: [bold]{valueY}[/]"
},
"properties": {
"background": {
"settings": {
"fillOpacity": 0.7,
"fill": {
"type": "Color",
"value": "#8067dc"
},
"pointerBaseWidth": 0
}
}
}
}
},
"properties": {
"data": "#data-0",
"strokes": {
"properties": {
"template": {
"settings": {
"strokeWidth": 2,
"strokeDasharray": [
5,
5
]
}
}
}
}
}
}
},
{
"series-2": {
"type": "SmoothedXLineSeries",
"settings": {
"name": "Stricter rules",
"xAxis": "#axis-0",
"yAxis": "#axis-1",
"valueYField": "stricter",
"valueXField": "date",
"stroke": {
"type": "Color",
"value": "#dc67ce"
},
"tooltip": {
"type": "Tooltip",
"settings": {
"pointerOrientation": "horizontal",
"getFillFromSprite": false,
"labelText": "[bold]{name}[/]\n{valueX}: [bold]{valueY}[/]"
},
"properties": {
"background": {
"settings": {
"fillOpacity": 0.7,
"fill": {
"type": "Color",
"value": "#dc67ce"
},
"pointerBaseWidth": 0
}
}
}
}
},
"properties": {
"data": "#data-0",
"strokes": {
"properties": {
"template": {
"settings": {
"strokeWidth": 2,
"strokeDasharray": [
5,
5
]
}
}
}
}
}
}
},
{
"series-3": {
"type": "SmoothedXLineSeries",
"settings": {
"name": "Projection",
"xAxis": "#axis-0",
"yAxis": "#axis-1",
"valueYField": "projection",
"valueXField": "date",
"stroke": {
"type": "Color",
"value": "#67b7dc"
},
"tooltip": {
"type": "Tooltip",
"settings": {
"pointerOrientation": "horizontal",
"getFillFromSprite": false,
"labelText": "[bold]{name}[/]\n{valueX}: [bold]{valueY}[/]"
},
"properties": {
"background": {
"settings": {
"fillOpacity": 0.7,
"fill": {
"type": "Color",
"value": "#67b7dc"
},
"pointerBaseWidth": 0
}
}
}
}
},
"properties": {
"data": "#data-0",
"strokes": {
"properties": {
"template": {
"settings": {
"strokeWidth": 2,
"strokeDasharray": [
5,
5
]
}
}
}
}
}
}
}
],
"root": {
"properties": {
"dateFormatter": {
"settings": {
"dateFormat": "MMM yyyy",
"dateFields": [
"valueX"
]
}
}
}
}
}