A line chart that never sits still: every second a new point slides in on the right and the oldest one drops off the left.
A moving window keeps the latest readings in view without the chart growing forever, and animating each new point in lets the eye follow the change instead of watching the line jump. It suits values watched in real time, where the latest trend matters more than the history.
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)
]);
// Generate random data
var value = 100; // the random values start at 100
// 16 daily points from 1000 days ago, each moving up or down by up to 10
function generateChartData() {
var chartData = [];
var firstDate = new Date();
firstDate.setDate(firstDate.getDate() - 1000);
firstDate.setHours(0, 0, 0, 0);
for (var i = 0; i < 16; i++) {
var newDate = new Date(firstDate);
newDate.setDate(newDate.getDate() + i);
value += (Math.random() < 0.5 ? 1 : -1) * Math.random() * 10;
chartData.push({
date: newDate.getTime(),
value: value
});
}
return chartData;
}
var data = generateChartData();
// 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, // drag the plot sideways to pan through the days
panY: true, // and up and down
wheelX: "panX", // a horizontal wheel or trackpad swipe pans...
wheelY: "zoomX" // ...and the vertical wheel zooms in on the days
}));
var easing = am5.ease.linear; // the new points glide in at a steady speed
// 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.5, // pans up to half the visible range past the first and last day
groupData: false, // every day stays a point of its own, never grouped into weeks
baseInterval: { // one data point per day
timeUnit: "day",
count: 1
},
renderer: am5xy.AxisRendererX.new(root, {
minorGridEnabled: true, // fainter grid lines between the labeled ones
minGridDistance: 60 // at least 60px between labels; on narrow screens some are skipped
}),
tooltip: am5.Tooltip.new(root, {}) // a date label follows the cursor along the axis
}));
var yAxis = chart.yAxes.push(am5xy.ValueAxis.new(root, {
renderer: am5xy.AxisRendererY.new(root, {})
}));
// Add series
// https://www.amcharts.com/docs/v5/charts/xy-chart/series/
var series = chart.series.push(am5xy.LineSeries.new(root, {
// bullets hide when points are closer than 10px, as when zoomed out
minBulletDistance: 10,
name: "Series 1",
xAxis: xAxis,
yAxis: yAxis,
valueYField: "value",
valueXField: "date",
tooltip: am5.Tooltip.new(root, {
pointerOrientation: "horizontal", // the tooltip sits beside the point, not above it
labelText: "{valueY.formatNumber('#.00')}" // the value with two decimals
})
}));
series.data.setAll(data);
// The area under the line is there but clear; raise its opacity to fill it
series.fills.template.setAll({
fillOpacity: 0,
visible: true
});
// All bullets share one template, so setting its radius resizes them at once
var bulletTemplate = am5.Template.new({
radius: 4 // 4px radius
});
// a dot on each point
series.bullets.push(function () {
return am5.Bullet.new(root, {
// no fixed location, so the bullet follows its point as it slides in
locationX: undefined,
sprite: am5.Circle.new(root, {
fill: series.get("fill") // in the line's color
}, bulletTemplate)
})
});
// Add cursor
// https://www.amcharts.com/docs/v5/charts/xy-chart/cursor/
var cursor = chart.set("cursor", am5xy.XYCursor.new(root, {
xAxis: xAxis // the cursor snaps to the days of this axis
}));
cursor.lineY.set("visible", false); // only the vertical cursor line, no horizontal one
// Hide the zoom-out button: as old points drop off, the value axis can count as
// zoomed for a moment and the button would flash. Scroll back to zoom out.
// https://www.amcharts.com/docs/v5/charts/xy-chart/zoom-and-pan/#Zoom_out_button
chart.zoomOutButton.set("forceHidden", true);
// Update data every second
setInterval(function () {
addData();
}, 1000)
// adds the next day's point and drops the oldest one, animating the change
function addData() {
var lastDataItem = series.dataItems[series.dataItems.length - 1]; // the newest point so far
var lastValue = lastDataItem.get("valueY");
// the next value wanders from the last one
var newValue = lastValue + ((Math.random() < 0.5 ? 1 : -1) * Math.random() * 6);
var lastDate = new Date(lastDataItem.get("valueX"));
var time = am5.time.add(new Date(lastDate), "day", 1).getTime(); // one day after the last point
// drop the oldest point, so the chart keeps showing the same number of days
series.data.removeIndex(0);
series.data.push({
date: time,
value: newValue
})
// the new point starts where the last one is and glides to its own date and value
var newDataItem = series.dataItems[series.dataItems.length - 1];
newDataItem.animate({
key: "valueYWorking",
to: newValue,
from: lastValue,
duration: 600, // 0.6 seconds, done before the next update
easing: easing
});
var animation = newDataItem.animate({
key: "locationX", // position within its day...
to: 0.5, // ...ends in the middle of it...
from: -0.5, // ...starting from where the last point was
duration: 600 // 0.6 seconds, like the value
});
// once the point stops, a visible axis tooltip is refreshed to the shifted dates
if (animation) {
var tooltip = xAxis.get("tooltip");
if (tooltip && !tooltip.isHidden()) {
animation.events.on("stopped", function () {
xAxis.updateTooltip();
})
}
}
}
// Make stuff animate on load
// https://www.amcharts.com/docs/v5/concepts/animations/
chart.appear(1000, 100);
{
"type": "XYChart",
"settings": {
"focusable": true,
"panX": true,
"panY": true,
"wheelX": "panX",
"wheelY": "zoomX",
"scrollbarX": {
"type": "Scrollbar",
"settings": {
"orientation": "horizontal",
"forceHidden": true
}
},
"cursor": {
"type": "XYCursor",
"settings": {
"xAxis": "#axis-0"
},
"properties": {
"lineY": {
"settings": {
"visible": false
}
}
}
}
},
"properties": {
"series": [
"#series-0"
],
"xAxes": [
"#axis-0"
],
"yAxes": [
"#axis-1"
],
"zoomOutButton": {
"settings": {
"forceHidden": true
}
}
},
"refs": [
{
"data-0": [
{
"date": 1705356000000,
"value": 79.3850103958516
},
{
"date": 1705442400000,
"value": 79.51766206298407
},
{
"date": 1705528800000,
"value": 77.17693067348964
},
{
"date": 1705615200000,
"value": 72.66553289400376
},
{
"date": 1705701600000,
"value": 80.85261461543783
},
{
"date": 1705788000000,
"value": 82.37998258980241
},
{
"date": 1705874400000,
"value": 78.72456236291315
},
{
"date": 1705960800000,
"value": 76.3959173838867
},
{
"date": 1706047200000,
"value": 78.02416404202594
},
{
"date": 1706133600000,
"value": 84.1914537225941
},
{
"date": 1706220000000,
"value": 84.21898106094997
},
{
"date": 1706306400000,
"value": 80.28640830738773
},
{
"date": 1706392800000,
"value": 75.12233561294896
},
{
"date": 1706479200000,
"value": 69.9693673640704
},
{
"date": 1706565600000,
"value": 67.94144860061328
},
{
"date": 1706652000000,
"value": 72.69399844164148
}
]
},
{
"template-0": {
"type": "Template",
"settings": {
"radius": 4
}
}
},
{
"axis-0": {
"type": "DateAxis",
"settings": {
"maxDeviation": 0.5,
"groupData": false,
"baseInterval": {
"timeUnit": "day",
"count": 1
},
"renderer": {
"type": "AxisRendererX",
"settings": {
"minorGridEnabled": true,
"minGridDistance": 60
}
},
"tooltip": {
"type": "Tooltip"
}
}
}
},
{
"axis-1": {
"type": "ValueAxis",
"settings": {
"renderer": {
"type": "AxisRendererY"
}
}
}
},
{
"series-0": {
"type": "LineSeries",
"settings": {
"minBulletDistance": 10,
"name": "Series 1",
"xAxis": "#axis-0",
"yAxis": "#axis-1",
"valueYField": "value",
"valueXField": "date",
"tooltip": {
"type": "Tooltip",
"settings": {
"pointerOrientation": "horizontal",
"labelText": "{valueY.formatNumber('#.00')}"
}
}
},
"properties": {
"data": "#data-0",
"fills": {
"properties": {
"template": {
"settings": {
"fillOpacity": 0,
"visible": true
}
}
}
},
"bullets": [
{
"type": "Bullet",
"settings": {
"sprite": {
"type": "Circle",
"settings": {
"fill": "@series.get('fill')"
},
"properties": {
"template": "#template-0"
}
}
}
}
]
}
}
}
]
}