Introduction
Labels in bar charts often face readability issues when the chart is resized, displayed on different screen resolutions, or zoomed in/out using amCharts zoom controls. By default, labels in amCharts 4 have a fixed width, which can lead to overlapping, truncation, or unreadable text on small screens.
To make labels truly responsive, we need to:
- Dynamically calculate the maximum width of labels based on the chart size and column width.
- Truncate long labels if necessary to prevent overflow.
- Use event hooks to ensure labels adjust properly when:
- The chart resizes.
- Zooming/panning occurs.
- Data updates dynamically.
In this article, we’ll explore how to adjust label width dynamically and truncate labels when needed using amCharts 4 events.
Implementation code with the issue:
am4core.ready(function () {
// Create the chart instance
let chart = am4core.create("chartdiv", am4charts.XYChart);
chart.hiddenState.properties.opacity = 0; // Initial fade-in
chart.logo.disabled = true;
chart.responsive.enabled = true; // Enable responsive behavior
// Chart data
chart.data = [
{ company: "Apple Inc.", visits: 23725 },
{ company: "Microsoft Corporation", visits: 1882 },
{ company: "Alphabet Inc. (Google)", visits: 1809 },
{ company: "Amazon.com, Inc.", visits: 1322 },
{ company: "Tesla, Inc.", visits: 1122 },
{ company: "Meta Platforms, Inc. (Facebook)", visits: 1114 },
{ company: "Berkshire Hathaway Inc.", visits: 711 },
{ company: "PepsiCo, Inc.", visits: 443 },
{ company: "Johnson & Johnson Services, Inc.", visits: 350 },
{ company: "McDonald's Corporation", visits: 298 },
{ company: "NVIDIA Corporation", visits: 150 },
{ company: "Netflix, Inc.", visits: 100 }
];
// Create Category Axis (X-axis)
let categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
categoryAxis.dataFields.category = "company";
categoryAxis.renderer.grid.template.location = 0;
categoryAxis.renderer.minGridDistance = 40;
categoryAxis.fontSize = 11;
categoryAxis.renderer.labels.template.wrap = true;
categoryAxis.renderer.labels.template.maxWidth = 80;
categoryAxis.renderer.labels.template.padding(10, 0, 0, 0); // Set all sides to 0
// Create Value Axis (Y-axis)
let valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
valueAxis.min = 0;
valueAxis.max = 24000;
valueAxis.strictMinMax = true;
valueAxis.renderer.minGridDistance = 30;
// Create Column Series
let series = chart.series.push(new am4charts.ColumnSeries());
series.dataFields.categoryX = "company";
series.dataFields.valueY = "visits";
series.columns.template.tooltipText = "{valueY.value}";
series.columns.template.tooltipY = 0;
series.columns.template.strokeOpacity = 0;
// Apply different colors to each column
series.columns.template.adapter.add("fill", function (fill, target) {
return chart.colors.getIndex(target.dataItem.index);
});
// Add Scrollbar
chart.scrollbarX = new am4core.Scrollbar();
chart.scrollbarX.marginTop = 30;
chart.scrollbarX.parent = chart.bottomAxesContainer;
// Add Axis Break for large values
let axisBreak = valueAxis.axisBreaks.create();
axisBreak.startValue = 2100;
axisBreak.endValue = 22900;
// Adjust Axis Break Size
let d = (axisBreak.endValue - axisBreak.startValue) / (valueAxis.max - valueAxis.min);
axisBreak.breakSize = 0.05 * (1 - d) / d; // Increased break size for better visibility
// Adjust hover effect for better interaction
let hoverState = axisBreak.states.create("hover");
hoverState.properties.breakSize = 1; // Slightly larger break when hovered
hoverState.properties.opacity = 0.1;
hoverState.transitionDuration = 1500;
axisBreak.defaultState.transitionDuration = 1000;
}); // end am4core.ready()
Setting a Responsive Max Width for Labels
In amCharts 4, labels for the xAxis in a column chart have a fixed maxWidth. However, since this width does not adjust dynamically, we must calculate it based on the column width and chart size.
To achieve this, we hook into the rangechangeended and sizechanged event, check the width of the columns, and set the maxWidth accordingly.
Implementation
// Function to update label max width based on the widest column
const updateLabelMaxWidth = () => {
let maxColumnWidth = 0;
// Loop through each column and find the maximum width
series.columns.each(function (column) {
if (column && column.pixelWidth > maxColumnWidth) {
maxColumnWidth = column.pixelWidth;
}
});
// Apply max width to labels
categoryAxis.renderer.labels.template.maxWidth = maxColumnWidth;
console.log("Updated Max Label Width: " + maxColumnWidth);
// Refresh the chart to apply updated label width
chart.invalidateRawData();
}
// Trigger label width update on zoom/pan
categoryAxis.events.on("rangechangeended", function () {
updateLabelMaxWidth();
});
// Trigger label width update on screen resize
chart.events.on("sizechanged", function () {
updateLabelMaxWidth();
});
Explanation
The function updateLabelMaxWidth dynamically adjusts x-axis label width based on the widest column in the chart. It loops through all columns, finds the maximum width, and applies it to the labels to prevent overlapping.
Event Hooks:
- rangechangeended → Updates label width when zooming/panning.
- sizechanged → Adjusts labels when the chart is resized.
This ensures that labels resize dynamically based on the available space.
Truncating Long Labels for Better Readability
Even with a dynamic max width, some labels may still be too long and can cause clutter. To handle this, we truncate labels when they exceed a certain length.
Implementation
categoryAxis.renderer.labels.template.adapter.add("textOutput", function(text, target) {
let maxLabelLength = 10; // Set the maximum number of characters before truncation
if (text.length > maxLabelLength) {
return text.substring(0, maxLabelLength) + "..."; // Truncate and add ellipsis
}
return text; // Return original text if it's within the limit
});
Explanation
- This function checks the length of each label.
- If the label is too long, it truncates the text to first word and adds ".." at the end.
- Labels within the max character limit remain unchanged.
This ensures that long labels don’t overlap while still providing enough information and staying responsive when resized or zoomed in/out.
Full Implementation code after applying the solution
am4core.ready(function () {
// Create the chart instance
let chart = am4core.create("chartdiv", am4charts.XYChart);
chart.hiddenState.properties.opacity = 0; // Initial fade-in
chart.logo.disabled = true;
chart.responsive.enabled = true; // Enable responsive behavior
// Chart data
chart.data = [
{ company: "Apple Inc.", visits: 23725 },
{ company: "Microsoft Corporation", visits: 1882 },
{ company: "Alphabet Inc. (Google)", visits: 1809 },
{ company: "Amazon.com, Inc.", visits: 1322 },
{ company: "Tesla, Inc.", visits: 1122 },
{ company: "Meta Platforms, Inc. (Facebook)", visits: 1114 },
{ company: "Berkshire Hathaway Inc.", visits: 711 },
{ company: "PepsiCo, Inc.", visits: 443 },
{ company: "Johnson & Johnson Services, Inc.", visits: 350 },
{ company: "McDonald's Corporation", visits: 298 },
{ company: "NVIDIA Corporation", visits: 150 },
{ company: "Netflix, Inc.", visits: 100 }
];
// Create Category Axis (X-axis)
let categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
categoryAxis.dataFields.category = "company";
categoryAxis.renderer.grid.template.location = 0;
categoryAxis.renderer.minGridDistance = 40;
categoryAxis.fontSize = 11;
categoryAxis.renderer.labels.template.wrap = true;
categoryAxis.renderer.labels.template.maxWidth = 80;
categoryAxis.renderer.labels.template.padding(10, 0, 0, 0); // Set all sides to 0
// Create Value Axis (Y-axis)
let valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
valueAxis.min = 0;
valueAxis.max = 24000;
valueAxis.strictMinMax = true;
valueAxis.renderer.minGridDistance = 30;
// Function to update label max width based on the widest column
const updateLabelMaxWidth = () => {
let maxColumnWidth = 0;
// Loop through each column and find the maximum width
series.columns.each(function (column) {
if (column && column.pixelWidth > maxColumnWidth) {
maxColumnWidth = column.pixelWidth;
}
});
// Apply max width to labels
categoryAxis.renderer.labels.template.maxWidth = maxColumnWidth;
console.log("Updated Max Label Width: " + maxColumnWidth);
// Refresh the chart to apply updated label width
chart.invalidateRawData();
}
// Trigger label width update on zoom/pan
categoryAxis.events.on("rangechangeended", function () {
updateLabelMaxWidth();
});
// Trigger label width update on screen resize
chart.events.on("sizechanged", function () {
updateLabelMaxWidth();
});
// Truncate labels at the end of the first word when necessary
categoryAxis.renderer.labels.template.adapter.add("textOutput", function (text, target) {
let labelMaxWidth = target.maxWidth;
if (labelMaxWidth < 60 && text) { // Check if label width is less than 60 pixels
let words = text.split(" "); // Split text into words
return words.length > 1 ? words[0] + ".." : text; // Keep only the first word with ".."
}
return text;
});
// Create Column Series
let series = chart.series.push(new am4charts.ColumnSeries());
series.dataFields.categoryX = "company";
series.dataFields.valueY = "visits";
series.columns.template.tooltipText = "{valueY.value}";
series.columns.template.tooltipY = 0;
series.columns.template.strokeOpacity = 0;
// Apply different colors to each column
series.columns.template.adapter.add("fill", function (fill, target) {
return chart.colors.getIndex(target.dataItem.index);
});
// Add Scrollbar
chart.scrollbarX = new am4core.Scrollbar();
chart.scrollbarX.marginTop = 30;
chart.scrollbarX.parent = chart.bottomAxesContainer;
// Add Axis Break for large values
let axisBreak = valueAxis.axisBreaks.create();
axisBreak.startValue = 2100;
axisBreak.endValue = 22900;
// Adjust Axis Break Size
let d = (axisBreak.endValue - axisBreak.startValue) / (valueAxis.max - valueAxis.min);
axisBreak.breakSize = 0.05 * (1 - d) / d; // Increased break size for better visibility
// Adjust hover effect for better interaction
let hoverState = axisBreak.states.create("hover");
hoverState.properties.breakSize = 1; // Slightly larger break when hovered
hoverState.properties.opacity = 0.1;
hoverState.transitionDuration = 1500;
axisBreak.defaultState.transitionDuration = 1000;
}); // end am4core.ready()
With these tweaks, our chart now adapts perfectly—labels stay readable, no more ugly overlaps, and everything resizes smoothly. By dynamically adjusting widths, truncating long names, and hooking into events, we’ve made sure the chart looks great on any screen. Now, go ahead and try it out!