Columns and lines on two value axes: daily sales on the left, a narrow actual over a wide, pale target like a bullet graph, and two market lines on the right.
Columns suit amounts per period, like sales per day, and lines suit rates or levels that run on between them. Putting both on one chart, each with its own value axis, shows whether they move together, as long as the reader can tell which axis belongs to which series.
Built with amCharts 5, a JavaScript library. The code includes your theme and renderer (canvas or SVG), but not 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.
// Data
var data = [
{
date: "2026-01-14",
market0: 71,
market1: 75,
sales0: 5,
sales1: 9
},
{
date: "2026-01-15",
market0: 74,
market1: 78,
sales0: 4,
sales1: 6
},
{
date: "2026-01-16",
market0: 78,
market1: 88,
sales0: 5,
sales1: 2
},
{
date: "2026-01-17",
market0: 85,
market1: 89,
sales0: 8,
sales1: 9
},
{
date: "2026-01-18",
market0: 82,
market1: 89,
sales0: 9,
sales1: 6
},
{
date: "2026-01-19",
market0: 83,
market1: 85,
sales0: 3,
sales1: 5
},
{
date: "2026-01-20",
market0: 88,
market1: 92,
sales0: 5,
sales1: 7
},
{
date: "2026-01-21",
market0: 85,
market1: 90,
sales0: 7,
sales1: 6
},
{
date: "2026-01-22",
market0: 85,
market1: 91,
sales0: 9,
sales1: 5
},
{
date: "2026-01-23",
market0: 80,
market1: 84,
sales0: 5,
sales1: 8
},
{
date: "2026-01-24",
market0: 87,
market1: 92,
sales0: 4,
sales1: 8
},
{
date: "2026-01-25",
market0: 84,
market1: 87,
sales0: 3,
sales1: 4
},
{
date: "2026-01-26",
market0: 83,
market1: 88,
sales0: 5,
sales1: 7
},
{
date: "2026-01-27",
market0: 84,
market1: 87,
sales0: 5,
sales1: 8
},
{
date: "2026-01-28",
market0: 81,
market1: 85,
sales0: 4,
sales1: 7
}
];
// 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)
]);
root.dateFormatter.setAll({
dateFormat: "yyyy-MM-dd", // dates in texts look like 2026-01-14...
dateFields: ["valueX"] // ...in the ones that use valueX
});
// 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 days
wheelY: "zoomX", // the vertical wheel zooms in on them
layout: root.verticalLayout // the plot on top, the legend under it
})
);
// Add cursor
// https://www.amcharts.com/docs/v5/charts/xy-chart/cursor/
var cursor = chart.set("cursor", am5xy.XYCursor.new(root, {
// drag across the plot to zoom into those days
behavior: "zoomX"
}));
cursor.lineY.set("visible", false); // no horizontal cursor line, only the vertical one
// Create axes
// https://www.amcharts.com/docs/v5/charts/xy-chart/axes/
var xAxis = chart.xAxes.push(
am5xy.DateAxis.new(root, {
baseInterval: { timeUnit: "day", count: 1 }, // one point per day
renderer: am5xy.AxisRendererX.new(root, {
minorGridEnabled: true // fainter grid lines between the labeled dates
}),
tooltip: am5.Tooltip.new(root, {}), // the cursor shows the date on the axis...
tooltipDateFormat: "yyyy-MM-dd" // ...written out in full
})
);
// Sales on the left; columns start at zero, so their heights compare fairly
var yAxis0 = chart.yAxes.push(
am5xy.ValueAxis.new(root, {
min: 0,
renderer: am5xy.AxisRendererY.new(root, {
pan: "zoom" // drag along the value labels to zoom them
})
})
);
var yRenderer1 = am5xy.AxisRendererY.new(root, {
opposite: true // its labels on the right side of the plot
});
// no grid lines of its own, so they don't clash with the left axis's
yRenderer1.grid.template.set("forceHidden", true);
// Market days on the right
var yAxis1 = chart.yAxes.push(
am5xy.ValueAxis.new(root, {
renderer: yRenderer1
})
);
// Add series
// https://www.amcharts.com/docs/v5/charts/xy-chart/series/
// Target sales: a wide, pale column behind
var columnSeries1 = chart.series.push(
am5xy.ColumnSeries.new(root, {
name: "Target sales",
xAxis: xAxis,
yAxis: yAxis0,
valueYField: "sales1",
valueXField: "date",
// both column series stand on the same spot, one behind the other, not side by side
clustered: false,
tooltip: am5.Tooltip.new(root, {
pointerOrientation: "horizontal", // the tooltip points sideways
labelText: "{name}: {valueY}" // the series name and its value
})
})
);
columnSeries1.columns.template.setAll({
width: am5.percent(60), // 60% of the day's width...
fillOpacity: 0.5, // ...and pale
strokeOpacity: 0 // no outline
});
// turns the date strings into timestamps in the data itself, so the other series get them too
columnSeries1.data.processor = am5.DataProcessor.new(root, {
dateFields: ["date"],
dateFormat: "yyyy-MM-dd"
});
// Actual sales: a narrow column in front, like the bar of a bullet graph
var columnSeries0 = chart.series.push(
am5xy.ColumnSeries.new(root, {
name: "Actual sales",
xAxis: xAxis,
yAxis: yAxis0,
valueYField: "sales0",
valueXField: "date",
clustered: false, // on the same spot as the target column
tooltip: am5.Tooltip.new(root, {
pointerOrientation: "horizontal",
labelText: "{name}: {valueY}"
})
})
);
columnSeries0.columns.template.set("width", am5.percent(40)); // narrower than the target column, in front of it
// market days: a smoothed line on the right axis
var series0 = chart.series.push(
am5xy.SmoothedXLineSeries.new(root, {
name: "Market days",
xAxis: xAxis,
yAxis: yAxis1,
valueYField: "market0",
valueXField: "date",
tooltip: am5.Tooltip.new(root, {
pointerOrientation: "horizontal",
labelText: "{name}: {valueY}"
})
})
);
series0.strokes.template.setAll({
strokeWidth: 2 // a 2px line
});
// Add bullet
// https://www.amcharts.com/docs/v5/charts/xy-chart/series/#Bullets
series0.bullets.push(function () {
return am5.Bullet.new(root, {
sprite: am5.Circle.new(root, {
stroke: series0.get("fill"), // an outline in the line's color...
strokeWidth: 2, // ...2px wide
fill: root.interfaceColors.get("background"), // filled with the background color, so it looks hollow
radius: 5 // 5px
})
});
});
// all market days: a dotted line on the same axis
var series1 = chart.series.push(
am5xy.SmoothedXLineSeries.new(root, {
name: "Market days all",
xAxis: xAxis,
yAxis: yAxis1,
valueYField: "market1",
valueXField: "date"
})
);
series1.strokes.template.setAll({
strokeWidth: 2,
strokeDasharray: [2, 2] // dotted: 2px dash, 2px gap
});
var tooltip1 = series1.set("tooltip", am5.Tooltip.new(root, {
pointerOrientation: "horizontal"
}));
// the text goes on the tooltip's label here, instead of labelText
tooltip1.label.set("text", "{name}: {valueY}");
// Add bullet
// https://www.amcharts.com/docs/v5/charts/xy-chart/series/#Bullets
series1.bullets.push(function () {
return am5.Bullet.new(root, {
sprite: am5.Circle.new(root, {
stroke: series1.get("fill"),
strokeWidth: 2,
fill: root.interfaceColors.get("background"),
radius: 5
})
});
});
// Add scrollbar
// https://www.amcharts.com/docs/v5/charts/xy-chart/scrollbars/
var scrollbar = chart.set("scrollbarX", am5xy.XYChartScrollbar.new(root, {
orientation: "horizontal", // drag its grips to zoom in on a range of days
height: 60 // 60px tall, with a preview of sales and market days
}));
// the scrollbar has a chart of its own: these axes and series draw the preview inside it
var sbDateAxis = scrollbar.chart.xAxes.push(
am5xy.DateAxis.new(root, {
baseInterval: {
timeUnit: "day",
count: 1
},
renderer: am5xy.AxisRendererX.new(root, {})
})
);
var sbValueAxis0 = scrollbar.chart.yAxes.push(
am5xy.ValueAxis.new(root, {
renderer: am5xy.AxisRendererY.new(root, {})
})
);
var sbValueAxis1 = scrollbar.chart.yAxes.push(
am5xy.ValueAxis.new(root, {
renderer: am5xy.AxisRendererY.new(root, {})
})
);
var sbSeries0 = scrollbar.chart.series.push(
am5xy.ColumnSeries.new(root, {
valueYField: "sales0",
valueXField: "date",
xAxis: sbDateAxis,
yAxis: sbValueAxis0
})
);
sbSeries0.columns.template.setAll({ fillOpacity: 0.5, strokeOpacity: 0 }); // pale columns, no outline
var sbSeries1 = scrollbar.chart.series.push(
am5xy.LineSeries.new(root, {
valueYField: "market0",
valueXField: "date",
xAxis: sbDateAxis,
yAxis: sbValueAxis1
})
);
var legend = chart.children.push(
am5.Legend.new(root, {
x: am5.p50, // the legend's middle at the middle of the chart
centerX: am5.p50
})
);
legend.data.setAll(chart.series.values);
columnSeries1.data.setAll(data);
columnSeries0.data.setAll(data);
series0.data.setAll(data);
series1.data.setAll(data);
sbSeries0.data.setAll(data);
sbSeries1.data.setAll(data);
// Make stuff animate on load
// https://www.amcharts.com/docs/v5/concepts/animations/
series0.appear(1000);
series1.appear(1000);
chart.appear(1000, 100);