Real-Time Data Sorting
A column chart that keeps itself sorted. Every second and a half, eleven countries get new values, and the columns slide into their new order, tallest first, with the numbers counting as they go.
About this chart
When to sort live
When values keep changing, a fixed order hides the story: the reader has to hunt for the leader after every update. Re-sorting keeps the ranking readable, and the slide shows who moved up and who fell back.
Good for
- Live leaderboards: sales teams, votes, games
- Rankings on monitoring screens
- Results coming in on election night
Think twice when
- Values people look up by name: a fixed order is easier
- Updates faster than the slide: the columns never settle
- Trends over time: a line chart keeps the history
For developers
Get the code.
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.
For projects that use npm:
How it reads:
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.How it reads:
"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://docs.amcharts.com/v5/getting-started/#root-element
var root = am5.Root.new("chartdiv");
// Set themes
// https://docs.amcharts.com/v5/concepts/themes/
root.setThemes([
am5themes_Animated.new(root),
am5themes_Responsive.new(root)
]);
// Create chart
// https://docs.amcharts.com/v5/charts/xy-chart/
var chart = root.container.children.push(am5xy.XYChart.new(root, {
panX: true, // drag the plot to pan sideways...
panY: true, // ...and up and down
wheelX: "none", // the wheel doesn't zoom...
wheelY: "none", // ...so the page scrolls past the chart
paddingLeft: 0 // the value labels sit at the chart's left edge
}));
// We don't want zoom-out button to appear while animating, so we hide it
chart.zoomOutButton.set("forceHidden", true);
// Create axes
// https://docs.amcharts.com/v5/charts/xy-chart/axes/
var xRenderer = am5xy.AxisRendererX.new(root, {
minGridDistance: 30, // at least 30px between labels; on narrow screens some are skipped
minorGridEnabled: true
});
xRenderer.labels.template.setAll({
rotation: -90, // labels turned on their side
centerY: am5.p50, // each label centered on its column
centerX: 0,
paddingRight: 15
});
xRenderer.grid.template.set("visible", false); // no vertical grid lines
var xAxis = chart.xAxes.push(am5xy.CategoryAxis.new(root, {
maxDeviation: 0.3, // can be panned 30% past the first or last column
categoryField: "country",
renderer: xRenderer
}));
var yAxis = chart.yAxes.push(am5xy.ValueAxis.new(root, {
maxDeviation: 0.3,
min: 0, // the columns start at zero
renderer: am5xy.AxisRendererY.new(root, {})
}));
// Add series
// https://docs.amcharts.com/v5/charts/xy-chart/series/
var series = chart.series.push(am5xy.ColumnSeries.new(root, {
name: "Series 1",
xAxis: xAxis,
yAxis: yAxis,
valueYField: "value",
categoryXField: "country",
// Each column takes its own color from the series palette
colorByDataItem: true
}));
// Rounded corners for columns
series.columns.template.setAll({
cornerRadiusTL: 5,
cornerRadiusTR: 5,
strokeOpacity: 0, // no outline
// a soft shadow under each column
shadowColor: am5.color(0x000000),
shadowOpacity: 0.3, // 30% opaque
shadowBlur: 6, // blurred over 6px
shadowOffsetX: 2, // moved 2px to the right...
shadowOffsetY: 2 // ...and 2px down
});
// Add Label bullet
series.bullets.push(function () {
return am5.Bullet.new(root, {
// at the top of the column, with the text hanging down inside it (centerY: 0)
locationY: 1,
sprite: am5.Label.new(root, {
// the animated value, so the number counts along as the column grows or shrinks
text: "{valueYWorking.formatNumber('#.')}",
fill: root.interfaceColors.get("alternativeText"), // white by default, for text on colored fills
centerY: 0,
centerX: am5.p50, // centered on the column
populateText: true // fills in the {valueYWorking} placeholder from the column's data
})
});
});
// Set data
var data = [{
"country": "USA",
"value": 2025
}, {
"country": "China",
"value": 1882
}, {
"country": "Japan",
"value": 1809
}, {
"country": "Germany",
"value": 1322
}, {
"country": "UK",
"value": 1122
}, {
"country": "France",
"value": 1114
}, {
"country": "India",
"value": 984
}, {
"country": "Spain",
"value": 711
}, {
"country": "Netherlands",
"value": 665
}, {
"country": "South Korea",
"value": 443
}, {
"country": "Canada",
"value": 441
}];
xAxis.data.setAll(data);
series.data.setAll(data);
// update data with random values each 1.5 sec
setInterval(function () {
updateData();
}, 1500)
// a random change for every column, then sort the axis by the new values
function updateData() {
am5.array.each(series.dataItems, function (dataItem) {
var value = dataItem.get("valueY") + Math.round(Math.random() * 300 - 150);
if (value < 0) { // a negative value becomes 10
value = 10;
}
// both valueY and valueYWorking change; only valueYWorking is animated
dataItem.set("valueY", value);
dataItem.animate({
key: "valueYWorking",
to: value,
duration: 600, // the value animates over 0.6 seconds...
easing: am5.ease.out(am5.ease.cubic) // ...slowing down at the end
});
})
sortCategoryAxis();
}
// Get series item by category
function getSeriesItem(category) {
for (var i = 0; i < series.dataItems.length; i++) {
var dataItem = series.dataItems[i];
if (dataItem.get("categoryX") == category) {
return dataItem;
}
}
}
// Axis sorting
function sortCategoryAxis() {
// Sort by value
series.dataItems.sort(function (x, y) {
return y.get("valueY") - x.get("valueY"); // descending
})
// Go through each axis item
am5.array.each(xAxis.dataItems, function (dataItem) {
// get corresponding series item
var seriesDataItem = getSeriesItem(dataItem.get("category"));
if (seriesDataItem) {
// get index of series data item
var index = series.dataItems.indexOf(seriesDataItem);
// calculate delta position
var deltaPosition = (index - dataItem.get("index", 0)) / series.dataItems.length;
// set index to be the same as series data item index
dataItem.set("index", index);
// set deltaPosition instantly
dataItem.set("deltaPosition", -deltaPosition);
// animate delta position to 0
dataItem.animate({
key: "deltaPosition",
to: 0,
duration: 1000, // the columns slide to their new places in one second...
easing: am5.ease.out(am5.ease.cubic) // ...slowing down at the end
})
}
});
// Sort axis items by index.
// This changes the order instantly, but as deltaPosition is set,
// they keep in the same places and then animate to true positions.
xAxis.dataItems.sort(function (x, y) {
return x.get("index") - y.get("index");
});
}
// Make stuff animate on load
// https://docs.amcharts.com/v5/concepts/animations/
series.appear(1000);
chart.appear(1000, 100);
{
"type": "XYChart",
"settings": {
"panX": true,
"panY": true,
"wheelX": "none",
"wheelY": "none",
"paddingLeft": 0
},
"properties": {
"series": [
"#series-0"
],
"xAxes": [
"#axis-0"
],
"yAxes": [
"#axis-1"
],
"zoomOutButton": {
"settings": {
"forceHidden": true
}
}
},
"refs": [
{
"data-0": [
{
"country": "USA",
"value": 2025
},
{
"country": "China",
"value": 1882
},
{
"country": "Japan",
"value": 1809
},
{
"country": "Germany",
"value": 1322
},
{
"country": "UK",
"value": 1122
},
{
"country": "France",
"value": 1114
},
{
"country": "India",
"value": 984
},
{
"country": "Spain",
"value": 711
},
{
"country": "Netherlands",
"value": 665
},
{
"country": "South Korea",
"value": 443
},
{
"country": "Canada",
"value": 441
}
]
},
{
"axis-0": {
"type": "CategoryAxis",
"settings": {
"maxDeviation": 0.3,
"categoryField": "country",
"renderer": {
"type": "AxisRendererX",
"settings": {
"minGridDistance": 30,
"minorGridEnabled": true
},
"properties": {
"labels": {
"properties": {
"template": {
"settings": {
"rotation": -90,
"centerY": {
"type": "Percent",
"value": 50
},
"centerX": 0,
"paddingRight": 15
}
}
}
},
"grid": {
"properties": {
"template": {
"settings": {
"visible": false
}
}
}
}
}
}
},
"properties": {
"data": "#data-0"
}
}
},
{
"axis-1": {
"type": "ValueAxis",
"settings": {
"maxDeviation": 0.3,
"min": 0,
"renderer": {
"type": "AxisRendererY"
}
}
}
},
{
"series-0": {
"type": "ColumnSeries",
"settings": {
"name": "Series 1",
"xAxis": "#axis-0",
"yAxis": "#axis-1",
"valueYField": "value",
"categoryXField": "country",
"colorByDataItem": true
},
"properties": {
"data": "#data-0",
"columns": {
"properties": {
"template": {
"settings": {
"cornerRadiusTL": 5,
"cornerRadiusTR": 5,
"strokeOpacity": 0,
"shadowColor": {
"type": "Color",
"value": "#000000"
},
"shadowOpacity": 0.3,
"shadowBlur": 6,
"shadowOffsetX": 2,
"shadowOffsetY": 2
}
}
}
},
"bullets": [
{
"type": "Bullet",
"settings": {
"locationY": 1,
"sprite": {
"type": "Label",
"settings": {
"text": "{valueYWorking.formatNumber('#.')}",
"fill": "@root.interfaceColors.get('alternativeText')",
"centerY": 0,
"centerX": {
"type": "Percent",
"value": 50
},
"populateText": true
}
}
}
}
]
}
}
}
]
}



