Giới thiệu

Các nhãn trong biểu đồ cột thường gặp vấn đề về khả năng đọc khi biểu đồ được thay đổi kích thước, hiển thị trên các độ phân giải màn hình khác nhau, hoặc phóng to/thu nhỏ bằng các điều khiển zoom của amCharts. Theo mặc định, các nhãn trong amCharts 4 có chiều rộng cố định, điều này có thể dẫn đến sự chồng chéo, cắt ngắn, hoặc văn bản không thể đọc được trên các màn hình nhỏ.

Để làm cho các nhãn thực sự phản hồi, chúng ta cần:

  • Tính toán chiều rộng tối đa của các nhãn một cách động dựa trên kích thước biểu đồ và chiều rộng cột.
  • Cắt ngắn các nhãn dài nếu cần thiết để ngăn ngừa tràn.
  • Sử dụng các hook sự kiện để đảm bảo các nhãn điều chỉnh đúng cách khi:
    • Biểu đồ thay đổi kích thước.
    • Xảy ra phóng to/di chuyển.
    • Dữ liệu được cập nhật một cách động.

Trong bài viết này, chúng ta sẽ khám phá cách điều chỉnh chiều rộng nhãn một cách độngcắt ngắn nhãn khi cần thiết bằng cách sử dụng các sự kiện của amCharts 4.

Một ví dụ về các nhãn chồng chéo và thiếu tính phản hồi trong biểu đồ cột

Mã thực hiện với vấn đề:


am4core.ready(function () {
    // Tạo phiên bản biểu đồ
    let chart = am4core.create("chartdiv", am4charts.XYChart);
    chart.hiddenState.properties.opacity = 0; // Hiệu ứng mờ ban đầu
    chart.logo.disabled = true;
    chart.responsive.enabled = true; // Kích hoạt hành vi phản hồi

    // Dữ liệu biểu đồ
    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 }
    ];

    // Tạo trục phân loại (trục X)
    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); // Đặt tất cả các cạnh bằng 0

    // Tạo trục giá trị (trục Y)
    let valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
    valueAxis.min = 0;
    valueAxis.max = 24000;
    valueAxis.strictMinMax = true;
    valueAxis.renderer.minGridDistance = 30;

    // Tạo chuỗi cột
    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;

    // Áp dụng màu sắc khác nhau cho mỗi cột
    series.columns.template.adapter.add("fill", function (fill, target) {
      return chart.colors.getIndex(target.dataItem.index);
    });

    // Thêm thanh cuộn
    chart.scrollbarX = new am4core.Scrollbar();
    chart.scrollbarX.marginTop = 30;
    chart.scrollbarX.parent = chart.bottomAxesContainer;

    // Thêm ngắt trục cho các giá trị lớn
    let axisBreak = valueAxis.axisBreaks.create();
    axisBreak.startValue = 2100;
    axisBreak.endValue = 22900;

    // Điều chỉnh kích thước ngắt trục
    let d = (axisBreak.endValue - axisBreak.startValue) / (valueAxis.max - valueAxis.min);
    axisBreak.breakSize = 0.05 * (1 - d) / d; // Tăng kích thước ngắt để dễ nhìn hơn

    // Điều chỉnh hiệu ứng hover để tương tác tốt hơn
    let hoverState = axisBreak.states.create("hover");
    hoverState.properties.breakSize = 1; // Ngắt lớn hơn một chút khi hover
    hoverState.properties.opacity = 0.1;
    hoverState.transitionDuration = 1500;

    axisBreak.defaultState.transitionDuration = 1000;

}); // kết thúc am4core.ready()

Đặt chiều rộng tối đa phản hồi cho các nhãn

Trong amCharts 4, các nhãn cho xAxis trong một biểu đồ cột có chiều rộng tối đa cố định. Tuy nhiên, vì chiều rộng này không điều chỉnh một cách động, chúng ta phải tính toán nó dựa trên chiều rộng cột và kích thước biểu đồ.

Để đạt được điều này, chúng ta sẽ kết nối với sự kiện rangechangeended và sizechanged , kiểm tra chiều rộng của các cột, và đặt chiều rộng tối đa tương ứng.

Thực hiện

// Hàm để cập nhật chiều rộng tối đa của nhãn dựa trên cột rộng nhất
const updateLabelMaxWidth = () => {
  let maxColumnWidth = 0;

  // Lặp qua từng cột và tìm chiều rộng tối đa
  series.columns.each(function (column) {
    if (column && column.pixelWidth > maxColumnWidth) {
      maxColumnWidth = column.pixelWidth;
    }
  });

  // Áp dụng chiều rộng tối đa cho các nhãn
  categoryAxis.renderer.labels.template.maxWidth = maxColumnWidth;
  console.log("Chiều rộng nhãn tối đa đã cập nhật: " + maxColumnWidth);

  // Buộc nhãn phải được xử lý lại
  categoryAxis.invalidateLabels();
}

// Kích hoạt cập nhật chiều rộng nhãn khi phóng to/di chuyển
categoryAxis.events.on("rangechangeended", function () {
  updateLabelMaxWidth();
});

// Kích hoạt cập nhật chiều rộng nhãn khi thay đổi kích thước màn hình
chart.events.on("sizechanged", function () {
  updateLabelMaxWidth();
});

Giải thích

Hàm updateLabelMaxWidth điều chỉnh động chiều rộng nhãn trục x dựa trên cột rộng nhất trong biểu đồ. Nó lặp qua tất cả các cột, tìm chiều rộng tối đa, và áp dụng nó cho các nhãn để ngăn chặn sự chồng chéo.

Các hook sự kiện:

  • rangechangeended → Cập nhật chiều rộng nhãn khi phóng to/di chuyển.
  • sizechanged → Điều chỉnh các nhãn khi biểu đồ được thay đổi kích thước.

Điều này đảm bảo rằng các nhãn thay đổi kích thước một cách động dựa trên không gian có sẵn.

Cắt ngắn các nhãn dài để cải thiện khả năng đọc

Ngay cả với chiều rộng tối đa động, một số nhãn có thể vẫn quá dài và có thể gây rối. Để xử lý điều này, chúng ta cắt ngắn các nhãn khi chúng vượt quá một độ dài nhất định.

Thực hiện

categoryAxis.renderer.labels.template.adapter.add("textOutput", function(text, target) {
    let maxLabelLength = 10; // Đặt số ký tự tối đa trước khi cắt ngắn

    if (text.length > maxLabelLength) {
        return text.substring(0, maxLabelLength) + "..."; // Cắt ngắn và thêm dấu ba chấm
    }
    return text; // Trả về văn bản gốc nếu nó nằm trong giới hạn
});

Giải thích

  • Hàm này kiểm tra độ dài của mỗi nhãn.
  • Nếu nhãn quá dài, nó cắt ngắn văn bản đến từ đầu tiên và thêm ".." ở cuối.
  • Các nhãn trong giới hạn ký tự tối đa sẽ không thay đổi.

Điều này đảm bảo rằng các nhãn dài không bị chồng chéo trong khi vẫn cung cấp đủ thông tin và giữ tính phản hồi khi thay đổi kích thước hoặc phóng to/thu nhỏ.

Kết quả cuối cùng sau khi áp dụng các điều chỉnh nhãn động

Mã thực hiện đầy đủ sau khi áp dụng giải pháp


am4core.ready(function () {
  // Tạo phiên bản biểu đồ
  let chart = am4core.create("chartdiv", am4charts.XYChart);
  chart.hiddenState.properties.opacity = 0; // Hiệu ứng mờ ban đầu
  chart.logo.disabled = true;
  chart.responsive.enabled = true; // Kích hoạt hành vi phản hồi

  // Dữ liệu biểu đồ
  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 }
  ];

  // Tạo trục phân loại (trục X)
  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); // Đặt tất cả các cạnh bằng 0

  // Tạo trục giá trị (trục Y)
  let valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
  valueAxis.min = 0;
  valueAxis.max = 24000;
  valueAxis.strictMinMax = true;
  valueAxis.renderer.minGridDistance = 30;

  // Hàm để cập nhật chiều rộng tối đa của nhãn dựa trên cột rộng nhất
  const updateLabelMaxWidth = () => {
    let maxColumnWidth = 0;

    // Lặp qua từng cột và tìm chiều rộng tối đa
    series.columns.each(function (column) {
      if (column && column.pixelWidth > maxColumnWidth) {
        maxColumnWidth = column.pixelWidth;
      }
    });

    // Áp dụng chiều rộng tối đa cho các nhãn
    categoryAxis.renderer.labels.template.maxWidth = maxColumnWidth;
    console.log("Chiều rộng nhãn tối đa đã cập nhật: " + maxColumnWidth);

    // Buộc nhãn phải được xử lý lại
    categoryAxis.invalidateLabels();
  }

  // Kích hoạt cập nhật chiều rộng nhãn khi phóng to/di chuyển
  categoryAxis.events.on("rangechangeended", function () {
    updateLabelMaxWidth();
  });

  // Kích hoạt cập nhật chiều rộng nhãn khi thay đổi kích thước màn hình
  chart.events.on("sizechanged", function () {
    updateLabelMaxWidth();
  });

  // Cắt ngắn nhãn ở cuối từ đầu tiên khi cần thiết
  categoryAxis.renderer.labels.template.adapter.add("textOutput", function (text, target) {
    let labelMaxWidth = target.maxWidth;

    if (labelMaxWidth < 60 && text) { // Kiểm tra nếu chiều rộng nhãn nhỏ hơn 60 pixel
      let words = text.split(" "); // Tách văn bản thành các từ
      return words.length > 1 ? words[0] + ".." : text; // Giữ lại chỉ từ đầu tiên với ".."
    }

    return text;
  });

  // Tạo chuỗi cột
  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;

  // Áp dụng màu sắc khác nhau cho mỗi cột
  series.columns.template.adapter.add("fill", function (fill, target) {
    return chart.colors.getIndex(target.dataItem.index);
  });

  // Thêm thanh cuộn
  chart.scrollbarX = new am4core.Scrollbar();
  chart.scrollbarX.marginTop = 30;
  chart.scrollbarX.parent = chart.bottomAxesContainer;

  // Thêm ngắt trục cho các giá trị lớn
  let axisBreak = valueAxis.axisBreaks.create();
  axisBreak.startValue = 2100;
  axisBreak.endValue = 22900;

  // Điều chỉnh kích thước ngắt trục
  let d = (axisBreak.endValue - axisBreak.startValue) / (valueAxis.max - valueAxis.min);
  axisBreak.breakSize = 0.05 * (1 - d) / d; // Tăng kích thước ngắt để dễ nhìn hơn

  // Điều chỉnh hiệu ứng hover để tương tác tốt hơn
  let hoverState = axisBreak.states.create("hover");
  hoverState.properties.breakSize = 1; // Ngắt lớn hơn một chút khi hover
  hoverState.properties.opacity = 0.1;
  hoverState.transitionDuration = 1500;

  axisBreak.defaultState.transitionDuration = 1000;

}); // kết thúc am4core.ready()

Với những điều chỉnh này, biểu đồ của chúng ta giờ đây thích ứng hoàn hảo—các nhãn vẫn dễ đọc, không còn chồng chéo xấu xí, và mọi thứ đều thay đổi kích thước một cách mượt mà. Bằng cách điều chỉnh chiều rộng một cách động, cắt ngắn các tên dài, và kết nối với các sự kiện, chúng ta đã đảm bảo rằng biểu đồ trông tuyệt vời trên bất kỳ màn hình nào. Giờ thì hãy thử nghiệm nhé!