I am trying to create a bar chart using AmCharts 5 where the segments for each column are sorted from smallest to largest, so that the largest segment is always placed at the top of the column.
My chart code so far looks like this:
// Get chart div by id
var chartDiv = document.getElementById(selector);
const data = JSON.parse(chartDiv.getAttribute('data-chart'));
const fontSize = 12;
const markerSize = 12;
var root = am5.Root.new(selector);
var chart = root.container.children.push(
am5xy.XYChart.new(root, {
panY: false,
layout: root.verticalLayout,
dy: 10,
})
);
// Create X-axis
let xAxis = chart.xAxes.push(
am5xy.CategoryAxis.new(root, {
categoryField: "week_ending_date",
renderer: am5xy.AxisRendererX.new(root, {})
})
);
let xRenderer = xAxis.get("renderer");
xRenderer.labels.template.setAll({
fontSize: fontSize
});
xAxis.data.setAll(data);
// Create Y-Axis
var yAxis = chart.yAxes.push(
am5xy.ValueAxis.new(root, {
renderer: am5xy.AxisRendererY.new(root, {}),
})
);
let yRenderer = yAxis.get("renderer");
yRenderer.labels.template.setAll({
fontSize: fontSize
});
// Add series
// https://www.amcharts.com/docs/v5/charts/xy-chart/series/
function makeSeries(name, fieldName, colorHex) {
var series = chart.series.push(am5xy.ColumnSeries.new(root, {
name: name,
stacked: true,
xAxis: xAxis,
yAxis: yAxis,
baseAxis: xAxis,
valueYField: fieldName,
categoryXField: "week_ending_date",
fill: colorHex,
}));
series.columns.template.setAll({
tooltipText: "{name} - {valueY}",
tooltipY: am5.percent(90)
});
series.data.setAll(data);
series.bullets.push(function() {
return am5.Bullet.new(root, {
sprite: am5.Label.new(root, {
text: "{valueX}",
fill: root.interfaceColors.get("alternativeText"),
centerY: am5.p50,
centerX: am5.p50,
populateText: true,
fontSize: 12,
})
});
});
legend.data.push(series);
}
// Add series for each site in the data
// Get all site names
let sites = Object.keys(data[0])
// Remove first value (week_ending_date)
sites.shift()
sites.forEach((site, index) => {
makeSeries(site, site, COLOR_SCHEME[index]);
})
And my data looks like this:
[
{
"week_ending_date": "2022-10-02",
"site A": 100,
"site B": 150,
"site C": 200,
},
{
"week_ending_date": "2022-10-09",
"site A": 400,
"site B": 150,
"site C": 50,
},
...
So in my first column, I would want site C to appear at the top of it, and in the second column I want site A to be at the top.
Anyone know if this is possible and how to achieve it?
Thanks in advance.
I've tried searching through the documentation but I cant find any properties that would allow me to reorder segments.
A possible solution is to preprocess the data, sort the entries in a data structure and make the columns floating rather then stacked.
This means setting a starting y value and an ending y value for each bar (that is for each data point); this allows us to place sorted columns in order: the end of the shortest will be the start of the second shortest and the end of the second shortest will be the start of the longest bar.
Here's a snippet based on your code that implements this method:
//var chartDiv = document.getElementById(selector);
//const data = JSON.parse(chartDiv.getAttribute('data-chart'));
const data = [
{
"week_ending_date": "2022-10-02",
"site A": 100,
"site B": 150,
"site C": 200,
},
{
"week_ending_date": "2022-10-09",
"site A": 400,
"site B": 150,
"site C": 50,
},
{
"week_ending_date": "2022-10-16",
"site A": 200,
"site B": 150,
"site C": 200,
},
{
"week_ending_date": "2022-10-23",
"site A": 120,
"site B": 150,
"site C": 200,
}];
const fontSize = 12;
//const markerSize = 12;
var root = am5.Root.new(selector);
var chart = root.container.children.push(
am5xy.XYChart.new(root, {
panY: false,
layout: root.verticalLayout,
dy: 10,
})
);
// Create X-axis
let xAxis = chart.xAxes.push(
am5xy.CategoryAxis.new(root, {
categoryField: "week_ending_date",
renderer: am5xy.AxisRendererX.new(root, {})
})
);
let xRenderer = xAxis.get("renderer");
xRenderer.labels.template.setAll({
fontSize: fontSize
});
xAxis.data.setAll(data);
// Create Y-Axis
var yAxis = chart.yAxes.push(
am5xy.ValueAxis.new(root, {
renderer: am5xy.AxisRendererY.new(root, {}),
})
);
let yRenderer = yAxis.get("renderer");
yRenderer.labels.template.setAll({
fontSize: fontSize
});
// Add series
// https://www.amcharts.com/docs/v5/charts/xy-chart/series/
function makeSeries(name, fieldName, colorHex) {
var series = chart.series.push(am5xy.ColumnSeries.new(root, {
name: name,
//stacked: false, // default
clustered: false,
xAxis: xAxis,
yAxis: yAxis,
baseAxis: xAxis,
trueValue: data,
valueYField: fieldName+'_end',
openValueYField: fieldName+'_start',
categoryXField: "week_ending_date",
fill: colorHex,
}));
series.columns.template.setAll({
tooltipText: "{name} - {"+fieldName+"}",
tooltipY: am5.percent(90),
});
series.data.setAll(data);
series.bullets.push(function() {
return am5.Bullet.new(root, {
sprite: am5.Label.new(root, {
text: "{valueX}",
fill: root.interfaceColors.get("alternativeText"),
centerY: am5.p50,
centerX: am5.p50,
populateText: true,
fontSize: 12,
})
});
});
const legend = chart.children.push(am5.Legend.new(root, {}));
legend.data.push(series);
}
// Get all site names
let sites = Object.keys(data[0])
// Remove first value (week_ending_date)
sites.shift()
// preprocess data
const dataEntriesSorted = data.map(
o => Object.entries(o).filter(([_, v]) => typeof v === "number").
sort(([_key1, v1], [_key2, v2]) => v1 - v2)
);
dataEntriesSorted.forEach(function(entries, i){
let sum = 0;
entries.forEach(function([key, val]){
data[i][key+'_start'] = sum; // the start of the bar
data[i][key+'_end'] = val + sum; // the end of the bar
sum += data[i][key];
})
});
// Add series for each site in the data
sites.forEach((site, index) => {
makeSeries(site, site, chart.get("colors").next());
});
<div id="selector" style="width: 400px;height: 400px"></div>
<script src="https://cdn.amcharts.com/lib/5/index.js"></script>
<script src="https://cdn.amcharts.com/lib/5/xy.js"></script>