A step counter in the style of a fitness app: one column per day, gray below the 6,000-step goal and colored above it. Drag the chart sideways, and the day under the fixed cursor shows its count.
Daily figures mean more next to a goal: the reader sees at once which days made it, and the gray columns show the misses without a single number. A fixed cursor with the chart sliding under it is a pattern people know from phone apps, and it works well with touch.
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)
]);
var data = [
{
date: "2026-01-02",
steps: 4561
},
{
date: "2026-01-03",
steps: 5687
},
{
date: "2026-01-04",
steps: 6348
},
{
date: "2026-01-05",
steps: 4878
},
{
date: "2026-01-06",
steps: 9867
},
{
date: "2026-01-07",
steps: 7561
},
{
date: "2026-01-08",
steps: 1287
},
{
date: "2026-01-09",
steps: 3298
},
{
date: "2026-01-10",
steps: 5697
},
{
date: "2026-01-11",
steps: 4878
},
{
date: "2026-01-12",
steps: 8788
},
{
date: "2026-01-13",
steps: 9560
},
{
date: "2026-01-14",
steps: 11687
},
{
date: "2026-01-15",
steps: 5878
},
{
date: "2026-01-16",
steps: 9789
},
{
date: "2026-01-17",
steps: 3987
},
{
date: "2026-01-18",
steps: 5898
},
{
date: "2026-01-19",
steps: 9878
},
{
date: "2026-01-20",
steps: 13687
},
{
date: "2026-01-21",
steps: 6789
},
{
date: "2026-01-22",
steps: 4531
},
{
date: "2026-01-23",
steps: 5856
},
{
date: "2026-01-24",
steps: 5737
},
{
date: "2026-01-25",
steps: 9987
},
{
date: "2026-01-26",
steps: 16457
},
{
date: "2026-01-27",
steps: 7878
},
{
date: "2026-01-28",
steps: 6845
},
{
date: "2026-01-29",
steps: 4659
},
{
date: "2026-01-30",
steps: 7892
},
{
date: "2026-01-31",
steps: 7362
},
{
date: "2026-02-01",
steps: 3268
}
];
// Create chart
// https://www.amcharts.com/docs/v5/charts/xy-chart/
var chart = root.container.children.push(
am5xy.XYChart.new(root, {
focusable: true, // the chart can be reached with the Tab key
panX: true, // a drag scrolls through the days
panY: false,
wheelX: "panX", // a horizontal wheel or trackpad swipe scrolls too...
wheelY: "none", // ...but the vertical wheel scrolls the page, not the chart
paddingLeft: 0, // no gap at the left edge...
paddingRight: 0 // ...or the right edge
})
);
// hide zoomout button
chart.zoomOutButton.set("forceHidden", true);
// Create axes
// https://www.amcharts.com/docs/v5/charts/xy-chart/axes/
var xRenderer = am5xy.AxisRendererX.new(root, {
minGridDistance: 50, // at least 50px between the date labels
strokeOpacity: 0.2, // a faint axis line
minorGridEnabled: true
});
xRenderer.grid.template.set("forceHidden", true); // no vertical grid lines
var xAxis = chart.xAxes.push(
am5xy.DateAxis.new(root, {
maxDeviation: 0.49, // the first and last day can be panned almost to the middle, under the cursor
snapTooltip: false, // the date tooltip follows the cursor instead of snapping to the day
baseInterval: { // one column a day
timeUnit: "day",
count: 1
},
renderer: xRenderer,
tooltip: am5.Tooltip.new(root, {}) // shows the cursor's date on the axis
})
);
// labels inside the plot: the goal labels below are placed by the plot's width
var yRenderer = am5xy.AxisRendererY.new(root, { inside: true });
yRenderer.grid.template.set("forceHidden", true); // no horizontal grid lines
// no value labels, which the first column would cover: the goal lines give the scale,
// the tooltip the exact count
yRenderer.labels.template.set("forceHidden", true);
var yAxis = chart.yAxes.push(
am5xy.ValueAxis.new(root, {
maxDeviation: 0, // no panning up or down past the data
// columns start at zero, so their heights compare truly
min: 0,
renderer: yRenderer
})
);
// Add series
// https://www.amcharts.com/docs/v5/charts/xy-chart/series/
var series = chart.series.push(
am5xy.ColumnSeries.new(root, {
xAxis: xAxis,
yAxis: yAxis,
valueYField: "steps",
valueXField: "date",
tooltip: am5.Tooltip.new(root, {
pointerOrientation: "vertical", // the tooltip sits above or below its point
labelText: "{valueY}" // the day's step count
})
})
);
series.columns.template.setAll({
cornerRadiusTL: 15, // rounded top corners...
cornerRadiusTR: 15, // ...on both sides
maxWidth: 30, // columns at most 30px wide
strokeOpacity: 0 // no outline
});
// days under the 6,000-step goal are gray
series.columns.template.adapters.add("fill", function (fill, target) {
if (target.dataItem.get("valueY") < 6000) {
// the theme's disabled color, so the misses stay gray in dark mode too
return root.interfaceColors.get("disabled");
}
return fill;
});
// Set up data processor to parse string dates
// https://www.amcharts.com/docs/v5/concepts/data/#Pre_processing_data
series.data.processor = am5.DataProcessor.new(root, {
dateFormat: "yyyy-MM-dd",
dateFields: ["date"]
});
series.data.setAll(data);
// do not allow tooltip to move horizontally
series.get("tooltip").adapters.add("x", function (x) {
return chart.plotContainer.toGlobal({
x: chart.plotContainer.width() / 2,
y: 0
}).x;
});
// add ranges
var goalRange = yAxis.createAxisRange(yAxis.makeDataItem({
value: 6000 // a goal line at 6,000 steps
}));
goalRange.get("grid").setAll({
forceHidden: false, // shown, though the axis grid lines are hidden
strokeOpacity: 0.2 // faint
});
var goalLabel = goalRange.get("label");
goalLabel.setAll({
forceHidden: false, // shown, though the axis labels are hidden
centerY: am5.p100, // the label sits on the line...
centerX: am5.p100, // ...and ends where it is placed: at the plot's right edge, by the adapter below
text: "Goal"
});
// put to other side
goalLabel.adapters.add("x", function (x) {
return chart.plotContainer.width();
});
var goalRange2 = yAxis.createAxisRange(yAxis.makeDataItem({
value: 12000 // a second line at twice the goal
}));
goalRange2.get("grid").setAll({
forceHidden: false, // shown...
strokeOpacity: 0.2 // ...and faint
});
var goalLabel2 = goalRange2.get("label");
goalLabel2.setAll({
forceHidden: false, // shown, on the line, ending at the plot's right edge
centerY: am5.p100,
centerX: am5.p100,
text: "2 x Goal"
});
// put to other side
goalLabel2.adapters.add("x", function (x) {
return chart.plotContainer.width();
});
// reposition when width changes
chart.plotContainer.onPrivate("width", function () {
goalLabel.markDirtyPosition();
goalLabel2.markDirtyPosition();
});
// Add cursor
// https://www.amcharts.com/docs/v5/charts/xy-chart/cursor/
var cursor = chart.set("cursor", am5xy.XYCursor.new(root, {
// the cursor stays on when the pointer leaves the chart, so the middle day's tooltip always shows
alwaysShow: true,
behavior: "none", // a drag pans the chart; the cursor doesn't zoom
positionX: 0.5 // make it always be at the center
}));
cursor.lineY.set("visible", false); // no horizontal cursor line
// zoom to last 11 days
series.events.on("datavalidated", function () {
var toTime =
series.dataItems[series.dataItems.length - 1].get("valueX") +
am5.time.getDuration("day", 1);
var fromTime = series.dataItems[series.dataItems.length - 11].get("valueX");
xAxis.zoomToValues(fromTime, toTime);
});
// when plot area is released, round zoom to nearest days
chart.plotContainer.events.on("globalpointerup", function () {
var dayDuration = am5.time.getDuration("day", 1);
var firstTime = am5.time
.round(new Date(series.dataItems[0].get("valueX")), "day", 1)
.getTime();
var lastTime =
series.dataItems[series.dataItems.length - 1].get("valueX") + dayDuration;
var totalTime = lastTime - firstTime;
var days = totalTime / dayDuration;
var roundedStart =
firstTime + Math.round(days * xAxis.get("start")) * dayDuration; // start rounded to a whole day...
var roundedEnd =
firstTime + Math.round(days * xAxis.get("end")) * dayDuration; // ...and the end too
xAxis.zoomToValues(roundedStart, roundedEnd);
});
// Make stuff animate on load
// https://www.amcharts.com/docs/v5/concepts/animations/
chart.appear(1000, 50);
{
"type": "XYChart",
"settings": {
"focusable": true,
"panX": true,
"panY": false,
"wheelX": "panX",
"wheelY": "none",
"paddingLeft": 0,
"paddingRight": 0,
"cursor": {
"type": "XYCursor",
"settings": {
"alwaysShow": true,
"behavior": "none",
"positionX": 0.5
},
"properties": {
"lineY": {
"settings": {
"visible": false
}
}
}
}
},
"properties": {
"series": [
"#series-0"
],
"xAxes": [
"#axis-0"
],
"yAxes": [
"#axis-1"
],
"zoomOutButton": {
"settings": {
"forceHidden": true
}
}
},
"refs": [
{
"data-0": [
{
"date": 1767304800000,
"steps": 4561
},
{
"date": 1767391200000,
"steps": 5687
},
{
"date": 1767477600000,
"steps": 6348
},
{
"date": 1767564000000,
"steps": 4878
},
{
"date": 1767650400000,
"steps": 9867
},
{
"date": 1767736800000,
"steps": 7561
},
{
"date": 1767823200000,
"steps": 1287
},
{
"date": 1767909600000,
"steps": 3298
},
{
"date": 1767996000000,
"steps": 5697
},
{
"date": 1768082400000,
"steps": 4878
},
{
"date": 1768168800000,
"steps": 8788
},
{
"date": 1768255200000,
"steps": 9560
},
{
"date": 1768341600000,
"steps": 11687
},
{
"date": 1768428000000,
"steps": 5878
},
{
"date": 1768514400000,
"steps": 9789
},
{
"date": 1768600800000,
"steps": 3987
},
{
"date": 1768687200000,
"steps": 5898
},
{
"date": 1768773600000,
"steps": 9878
},
{
"date": 1768860000000,
"steps": 13687
},
{
"date": 1768946400000,
"steps": 6789
},
{
"date": 1769032800000,
"steps": 4531
},
{
"date": 1769119200000,
"steps": 5856
},
{
"date": 1769205600000,
"steps": 5737
},
{
"date": 1769292000000,
"steps": 9987
},
{
"date": 1769378400000,
"steps": 16457
},
{
"date": 1769464800000,
"steps": 7878
},
{
"date": 1769551200000,
"steps": 6845
},
{
"date": 1769637600000,
"steps": 4659
},
{
"date": 1769724000000,
"steps": 7892
},
{
"date": 1769810400000,
"steps": 7362
},
{
"date": 1769896800000,
"steps": 3268
}
]
},
{
"axis-0": {
"type": "DateAxis",
"settings": {
"maxDeviation": 0.49,
"snapTooltip": false,
"baseInterval": {
"timeUnit": "day",
"count": 1
},
"renderer": {
"type": "AxisRendererX",
"settings": {
"minGridDistance": 50,
"strokeOpacity": 0.2,
"minorGridEnabled": true
},
"properties": {
"grid": {
"properties": {
"template": {
"settings": {
"forceHidden": true
}
}
}
}
}
},
"tooltip": {
"type": "Tooltip"
}
}
}
},
{
"axis-1": {
"type": "ValueAxis",
"settings": {
"maxDeviation": 0,
"min": 0,
"renderer": {
"type": "AxisRendererY",
"settings": {
"inside": true
},
"properties": {
"labels": {
"properties": {
"template": {
"settings": {
"forceHidden": true
}
}
}
},
"grid": {
"properties": {
"template": {
"settings": {
"forceHidden": true
}
}
}
}
}
}
}
}
},
{
"series-0": {
"type": "ColumnSeries",
"settings": {
"xAxis": "#axis-0",
"yAxis": "#axis-1",
"valueYField": "steps",
"valueXField": "date",
"tooltip": {
"type": "Tooltip",
"settings": {
"pointerOrientation": "vertical",
"labelText": "{valueY}"
}
}
},
"properties": {
"data": "#data-0",
"columns": {
"properties": {
"template": {
"settings": {
"cornerRadiusTL": 15,
"cornerRadiusTR": 15,
"maxWidth": 30,
"strokeOpacity": 0
}
}
}
}
}
}
},
{
"axisRange-0": {
"axis": "#axis-1",
"settings": {
"value": 6000,
"label": {
"type": "AxisLabel",
"settings": {
"forceHidden": false,
"centerX": {
"type": "Percent",
"value": 100
},
"centerY": {
"type": "Percent",
"value": 100
},
"text": "Goal"
}
},
"grid": {
"type": "Grid",
"settings": {
"forceHidden": false,
"strokeOpacity": 0.2
}
}
},
"__parseLogic": "axisRange"
}
},
{
"axisRange-1": {
"axis": "#axis-1",
"settings": {
"value": 12000,
"label": {
"type": "AxisLabel",
"settings": {
"forceHidden": false,
"centerX": {
"type": "Percent",
"value": 100
},
"centerY": {
"type": "Percent",
"value": 100
},
"text": "2 x Goal"
}
},
"grid": {
"type": "Grid",
"settings": {
"forceHidden": false,
"strokeOpacity": 0.2
}
}
},
"__parseLogic": "axisRange"
}
}
]
}