A line chart fed live: every second a new point slides in on the right, the oldest drops off the left, and a pulsing dot marks the latest value.
A rolling window keeps the chart the same size while data keeps coming: the newest values in view, the old ones gone. It suits screens people glance at, where the question is what is happening right now.
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;
// 50 points, one second apart, up to now
function generateChartData() {
var chartData = [];
var firstDate = new Date(Date.now() - 50 * 1000);
firstDate.setMilliseconds(0); // on the whole second
for (var i = 0; i < 50; i++) {
var newDate = new Date(firstDate);
newDate.setSeconds(newDate.getSeconds() + 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 take keyboard focus
panX: true, // drag the plot to pan...
panY: true, // ...in any direction
wheelX: "panX", // a horizontal wheel or trackpad swipe pans...
wheelY: "zoomX", // ...and the vertical wheel zooms in on the time
pinchZoomX:true, // pinch with two fingers to zoom on touch screens
paddingLeft: 0 // the value labels sit at the chart's left edge
}));
var easing = am5.ease.linear; // an even pace for the new points' animation
// 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, // panning can go up to half the width past the data
// keep every point as it is, never merged into longer intervals
groupData: false,
extraMax:0.1, // this adds some space in front
extraMin:-0.1, // this removes some space from the beginning so that the line would not be cut off
baseInterval: { // one point per second
timeUnit: "second",
count: 1
},
renderer: am5xy.AxisRendererX.new(root, {
minorGridEnabled: true, // faint grid lines between the main ones
minGridDistance: 50 // at least 50px between time labels
}),
tooltip: am5.Tooltip.new(root, {}) // shows the time at the cursor
}));
var yAxis = chart.yAxes.push(am5xy.ValueAxis.new(root, {
// the value axis keeps fitting itself to the points in view; that should not bring up the zoom out button
zoomOut: false,
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, {
name: "Series 1",
xAxis: xAxis,
yAxis: yAxis,
valueYField: "value",
valueXField: "date",
tooltip: am5.Tooltip.new(root, {
pointerOrientation: "horizontal", // the tooltip points sideways at the line
labelText: "{valueY.formatNumber('#.00')}" // the value, with two decimals
})
}));
// The area under the line is there but clear; raise its opacity to fill it
series.fills.template.setAll({
visible: true,
fillOpacity: 0
});
// tell that the last data item must create bullet
data[data.length - 1].bullet = true;
series.data.setAll(data);
// Create animating bullet by adding two circles in a bullet container and
// animating radius and opacity of one of them.
series.bullets.push(function(root, series, dataItem) {
// only create sprite if bullet == true in data context
if (dataItem.dataContext.bullet) {
var container = am5.Container.new(root, {});
var circle0 = container.children.push(am5.Circle.new(root, {
radius: 5, // a dot in the line's color...
fill: series.get("stroke")
}));
var circle1 = container.children.push(am5.Circle.new(root, {
radius: 5, // ...and a second one on top of it...
fill: series.get("stroke")
}));
circle1.animate({
key: "radius",
to: 20, // ...that grows to a 20px radius...
duration: 1000, // ...over a second...
easing: am5.ease.out(am5.ease.cubic), // ...fast at first, then slower...
loops: Infinity // ...over and over
});
circle1.animate({
key: "opacity",
to: 0, // fading out as it grows
from: 1,
duration: 1000,
easing: am5.ease.out(am5.ease.cubic),
loops: Infinity
});
return am5.Bullet.new(root, {
locationX:undefined, // no fixed spot: the data item's own locationX, animated below
sprite: container
})
}
})
// Add cursor
// https://www.amcharts.com/docs/v5/charts/xy-chart/cursor/
var cursor = chart.set("cursor", am5xy.XYCursor.new(root, {
xAxis: xAxis
}));
cursor.lineY.set("visible", false); // no horizontal cursor line
// Update data every second
setInterval(function () {
addData();
}, 1000)
// add a point a second after the last one, and move the pulsing dot to it
function addData() {
var lastDataItem = series.dataItems[series.dataItems.length - 1];
var lastValue = lastDataItem.get("valueY");
// each new value starts from the last one, so the line wanders on
var newValue = lastValue + ((Math.random() < 0.5 ? 1 : -1) * Math.random() * 5);
var lastDate = new Date(lastDataItem.get("valueX"));
var time = am5.time.add(new Date(lastDate), "second", 1).getTime();
// drop the oldest point as the new one comes in, so the line keeps its length
series.data.removeIndex(0);
series.data.push({
date: time,
value: newValue,
bullet: true
})
var newDataItem = series.dataItems[series.dataItems.length - 1];
// the new point grows from the last value to its own, instead of jumping there
newDataItem.animate({
key: "valueYWorking",
to: newValue,
from: lastValue,
duration: 600,
easing: easing
});
// use the bullet of last data item so that a new sprite is not created
// (if the chart has not drawn it yet, the "bullet" flag in the data makes it on the new item instead)
if (lastDataItem.bullets && lastDataItem.bullets.length) {
newDataItem.bullets = [lastDataItem.bullets[0]];
newDataItem.bullets[0].get("sprite").dataItem = newDataItem;
}
// reset bullets
lastDataItem.dataContext.bullet = false;
lastDataItem.bullets = [];
// it also slides in from the last point's place (half a step back) to the middle of its own second
var animation = newDataItem.animate({
key: "locationX",
to: 0.5,
from: -0.5,
duration: 600
});
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",
"pinchZoomX": true,
"paddingLeft": 0,
"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"
]
},
"refs": [
{
"data-0": [
{
"date": 1791458468000,
"value": 96.36009222913943
},
{
"date": 1791458469000,
"value": 91.77951178951987
},
{
"date": 1791458470000,
"value": 96.1502135097219
},
{
"date": 1791458471000,
"value": 92.61870417170954
},
{
"date": 1791458472000,
"value": 94.32723529040055
},
{
"date": 1791458473000,
"value": 102.48390224539568
},
{
"date": 1791458474000,
"value": 99.00349818055633
},
{
"date": 1791458475000,
"value": 96.17419736935193
},
{
"date": 1791458476000,
"value": 93.31241439990661
},
{
"date": 1791458477000,
"value": 99.49391680451987
},
{
"date": 1791458478000,
"value": 107.55296412372046
},
{
"date": 1791458479000,
"value": 109.86113097466917
},
{
"date": 1791458480000,
"value": 115.97399034345797
},
{
"date": 1791458481000,
"value": 111.89484837777864
},
{
"date": 1791458482000,
"value": 109.2912094589028
},
{
"date": 1791458483000,
"value": 103.97744024348091
},
{
"date": 1791458484000,
"value": 113.89766569503257
},
{
"date": 1791458485000,
"value": 118.93440539125676
},
{
"date": 1791458486000,
"value": 117.17591230680809
},
{
"date": 1791458487000,
"value": 113.44414829963314
},
{
"date": 1791458488000,
"value": 107.6302353646967
},
{
"date": 1791458489000,
"value": 108.15692659051345
},
{
"date": 1791458490000,
"value": 107.70244773545163
},
{
"date": 1791458491000,
"value": 111.96233974052697
},
{
"date": 1791458492000,
"value": 112.47369441153315
},
{
"date": 1791458493000,
"value": 108.546959359821
},
{
"date": 1791458494000,
"value": 110.16514736927041
},
{
"date": 1791458495000,
"value": 100.1980808223363
},
{
"date": 1791458496000,
"value": 97.04293480634891
},
{
"date": 1791458497000,
"value": 99.8003699701163
},
{
"date": 1791458498000,
"value": 94.9052879654516
},
{
"date": 1791458499000,
"value": 89.23528825443573
},
{
"date": 1791458500000,
"value": 93.04806877642815
},
{
"date": 1791458501000,
"value": 93.77437517766755
},
{
"date": 1791458502000,
"value": 96.21923324265714
},
{
"date": 1791458503000,
"value": 91.52368646533284
},
{
"date": 1791458504000,
"value": 93.74276921554033
},
{
"date": 1791458505000,
"value": 100.74479262647348
},
{
"date": 1791458506000,
"value": 109.47646758422675
},
{
"date": 1791458507000,
"value": 114.96948518942455
},
{
"date": 1791458508000,
"value": 122.68584650664215
},
{
"date": 1791458509000,
"value": 122.80750661888959
},
{
"date": 1791458510000,
"value": 117.10078727901262
},
{
"date": 1791458511000,
"value": 122.17688786611649
},
{
"date": 1791458512000,
"value": 116.07168242884394,
"bullet": false
},
{
"date": 1791458513000,
"value": 115.39152350493937,
"bullet": false
},
{
"date": 1791458514000,
"value": 113.6648911792728,
"bullet": false
},
{
"date": 1791458515000,
"value": 117.10386412582844,
"bullet": false
},
{
"date": 1791458516000,
"value": 114.23181119202683,
"bullet": false
},
{
"date": 1791458517000,
"value": 112.15252697273759,
"bullet": true
}
]
},
{
"axis-0": {
"type": "DateAxis",
"settings": {
"maxDeviation": 0.5,
"groupData": false,
"extraMax": 0.1,
"extraMin": -0.1,
"baseInterval": {
"timeUnit": "second",
"count": 1
},
"renderer": {
"type": "AxisRendererX",
"settings": {
"minorGridEnabled": true,
"minGridDistance": 50
}
},
"tooltip": {
"type": "Tooltip"
}
}
}
},
{
"axis-1": {
"type": "ValueAxis",
"settings": {
"zoomOut": false,
"renderer": {
"type": "AxisRendererY"
}
}
}
},
{
"series-0": {
"type": "LineSeries",
"settings": {
"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": {
"visible": true,
"fillOpacity": 0
}
}
}
}
}
}
}
]
}