บทนำ

ป้ายชื่อในกราฟแท่งมักประสบปัญหาการอ่านเมื่อกราฟถูกปรับขนาด, แสดงบนความละเอียดหน้าจอที่แตกต่างกัน, หรือซูมเข้า/ออกโดยใช้การควบคุมการซูมของ amCharts โดยค่าเริ่มต้น, ป้ายชื่อใน amCharts 4 จะมีความกว้างคงที่, ซึ่งอาจทำให้เกิด การทับซ้อน, การตัดทอน, หรือข้อความที่อ่านไม่ออก บนหน้าจอขนาดเล็ก.

เพื่อทำให้ป้ายชื่อ ตอบสนอง จริงๆ, เราจำเป็นต้อง:

  • คำนวณ ความกว้างสูงสุด ของป้ายชื่อแบบไดนามิกตามขนาดกราฟและความกว้างของคอลัมน์.
  • ตัดทอนป้ายชื่อที่ยาว หากจำเป็นเพื่อป้องกันการล้น.
  • ใช้ event hooks เพื่อให้แน่ใจว่าป้ายชื่อปรับตัวได้อย่างเหมาะสมเมื่อ:
    • กราฟปรับขนาด.
    • เกิดการซูม/เลื่อน.
    • ข้อมูลอัปเดตแบบไดนามิก.

ในบทความนี้, เราจะสำรวจวิธีการ ปรับความกว้างของป้ายชื่อแบบไดนามิก และ ตัดทอนป้ายชื่อเมื่อจำเป็น โดยใช้เหตุการณ์ของ amCharts 4.

ตัวอย่างของป้ายชื่อที่ทับซ้อนและขาดความตอบสนองในกราฟแท่ง

โค้ดการใช้งานที่มีปัญหา:


am4core.ready(function () {
    // สร้างอินสแตนซ์กราฟ
    let chart = am4core.create("chartdiv", am4charts.XYChart);
    chart.hiddenState.properties.opacity = 0; // การเฟดอินเริ่มต้น
    chart.logo.disabled = true;
    chart.responsive.enabled = true; // เปิดใช้งานพฤติกรรมที่ตอบสนอง

    // ข้อมูลกราฟ
    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 }
    ];

    // สร้างแกนหมวดหมู่ (แกน 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); // ตั้งค่าทุกด้านเป็น 0

    // สร้างแกนค่า (แกน Y)
    let valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
    valueAxis.min = 0;
    valueAxis.max = 24000;
    valueAxis.strictMinMax = true;
    valueAxis.renderer.minGridDistance = 30;

    // สร้างชุดคอลัมน์
    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;

    // ใช้สีที่แตกต่างกันสำหรับแต่ละคอลัมน์
    series.columns.template.adapter.add("fill", function (fill, target) {
      return chart.colors.getIndex(target.dataItem.index);
    });

    // เพิ่มแถบเลื่อน
    chart.scrollbarX = new am4core.Scrollbar();
    chart.scrollbarX.marginTop = 30;
    chart.scrollbarX.parent = chart.bottomAxesContainer;

    // เพิ่มการแบ่งแกนสำหรับค่าที่ใหญ่
    let axisBreak = valueAxis.axisBreaks.create();
    axisBreak.startValue = 2100;
    axisBreak.endValue = 22900;

    // ปรับขนาดการแบ่งแกน
    let d = (axisBreak.endValue - axisBreak.startValue) / (valueAxis.max - valueAxis.min);
    axisBreak.breakSize = 0.05 * (1 - d) / d; // ขนาดการแบ่งที่เพิ่มขึ้นเพื่อการมองเห็นที่ดีขึ้น

    // ปรับเอฟเฟกต์ hover เพื่อการโต้ตอบที่ดีขึ้น
    let hoverState = axisBreak.states.create("hover");
    hoverState.properties.breakSize = 1; // ขนาดการแบ่งที่ใหญ่ขึ้นเมื่อเลื่อนเมาส์
    hoverState.properties.opacity = 0.1;
    hoverState.transitionDuration = 1500;

    axisBreak.defaultState.transitionDuration = 1000;

}); // สิ้นสุด am4core.ready()

การตั้งค่าความกว้างสูงสุดที่ตอบสนองสำหรับป้ายชื่อ

ใน amCharts 4, ป้ายชื่อสำหรับ xAxis ใน กราฟแท่ง จะมีความกว้างสูงสุดคงที่ อย่างไรก็ตาม, เนื่องจากความกว้างนี้ไม่ปรับเปลี่ยนแบบไดนามิก, เราจึงต้องคำนวณมันตาม ความกว้างของคอลัมน์และขนาดกราฟ.

เพื่อให้บรรลุสิ่งนี้, เราจะใช้ rangechangeended และ sizechanged เหตุการณ์, ตรวจสอบความกว้างของคอลัมน์, และตั้งค่าความกว้างสูงสุดตามนั้น.

การใช้งาน

// ฟังก์ชันเพื่ออัปเดตความกว้างสูงสุดของป้ายชื่อตามคอลัมน์ที่กว้างที่สุด
const updateLabelMaxWidth = () => {
  let maxColumnWidth = 0;

  // วนลูปผ่านแต่ละคอลัมน์และหาความกว้างสูงสุด
  series.columns.each(function (column) {
    if (column && column.pixelWidth > maxColumnWidth) {
      maxColumnWidth = column.pixelWidth;
    }
  });

  // ใช้ความกว้างสูงสุดกับป้ายชื่อ
  categoryAxis.renderer.labels.template.maxWidth = maxColumnWidth;
  console.log("Updated Max Label Width: " + maxColumnWidth);

  // บังคับให้ป้ายชื่อถูกประมวลผลใหม่
  categoryAxis.invalidateLabels();
}

// กระตุ้นการอัปเดตความกว้างของป้ายชื่อเมื่อซูม/เลื่อน
categoryAxis.events.on("rangechangeended", function () {
  updateLabelMaxWidth();
});

// กระตุ้นการอัปเดตความกว้างของป้ายชื่อเมื่อขนาดหน้าจอเปลี่ยน
chart.events.on("sizechanged", function () {
  updateLabelMaxWidth();
});

คำอธิบาย

ฟังก์ชัน updateLabelMaxWidth ปรับความกว้างของป้ายชื่อแกน x แบบไดนามิกตามคอลัมน์ที่กว้างที่สุดในกราฟ มันวนลูปผ่านทุกคอลัมน์, หาความกว้าง สูงสุด, และนำไปใช้กับป้ายชื่อเพื่อป้องกัน การทับซ้อน.

Event Hooks:

  • rangechangeended → อัปเดตความกว้างของป้ายชื่อเมื่อซูม/เลื่อน.
  • sizechanged → ปรับป้ายชื่อเมื่อกราฟถูกปรับขนาด.

สิ่งนี้ทำให้แน่ใจว่าป้ายชื่อ ปรับขนาดแบบไดนามิก ตามพื้นที่ที่มีอยู่.

การตัดทอนป้ายชื่อที่ยาวเพื่อการอ่านที่ดีขึ้น

แม้จะมี ความกว้างสูงสุดแบบไดนามิก, ป้ายชื่อบางอันอาจยังคง ยาวเกินไป และอาจทำให้เกิดความยุ่งเหยิง เพื่อจัดการกับสิ่งนี้, เราจึง ตัดทอนป้ายชื่อ เมื่อมันเกินความยาวที่กำหนด.

การใช้งาน

categoryAxis.renderer.labels.template.adapter.add("textOutput", function(text, target) {
    let maxLabelLength = 10; // ตั้งค่าจำนวนตัวอักษรสูงสุดก่อนการตัดทอน

    if (text.length > maxLabelLength) {
        return text.substring(0, maxLabelLength) + "..."; // ตัดทอนและเพิ่มจุดไข่ปลา
    }
    return text; // คืนค่าข้อความเดิมหากอยู่ภายในขีดจำกัด
});

คำอธิบาย

  • ฟังก์ชันนี้ ตรวจสอบความยาว ของแต่ละป้ายชื่อ.
  • หากป้ายชื่อ ยาวเกินไป, มันจะตัดทอนข้อความไปยังคำแรกและ เพิ่ม ".." ที่ท้าย.
  • ป้ายชื่อที่อยู่ภายใน ขีดจำกัดจำนวนตัวอักษรสูงสุด จะไม่เปลี่ยนแปลง.

สิ่งนี้ทำให้แน่ใจว่าป้ายชื่อที่ยาวจะไม่ทับซ้อนในขณะที่ยังคงให้ข้อมูลเพียงพอและยังคงตอบสนองเมื่อปรับขนาดหรือซูมเข้า/ออก.

ผลลัพธ์สุดท้ายหลังจากการปรับแต่งป้ายชื่อแบบไดนามิก

โค้ดการใช้งานทั้งหมดหลังจากการใช้วิธีแก้ปัญหา


am4core.ready(function () {
  // สร้างอินสแตนซ์กราฟ
  let chart = am4core.create("chartdiv", am4charts.XYChart);
  chart.hiddenState.properties.opacity = 0; // การเฟดอินเริ่มต้น
  chart.logo.disabled = true;
  chart.responsive.enabled = true; // เปิดใช้งานพฤติกรรมที่ตอบสนอง

  // ข้อมูลกราฟ
  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 }
  ];

  // สร้างแกนหมวดหมู่ (แกน 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); // ตั้งค่าทุกด้านเป็น 0

  // สร้างแกนค่า (แกน Y)
  let valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
  valueAxis.min = 0;
  valueAxis.max = 24000;
  valueAxis.strictMinMax = true;
  valueAxis.renderer.minGridDistance = 30;

  // ฟังก์ชันเพื่ออัปเดตความกว้างสูงสุดของป้ายชื่อตามคอลัมน์ที่กว้างที่สุด
  const updateLabelMaxWidth = () => {
    let maxColumnWidth = 0;

    // วนลูปผ่านแต่ละคอลัมน์และหาความกว้างสูงสุด
    series.columns.each(function (column) {
      if (column && column.pixelWidth > maxColumnWidth) {
        maxColumnWidth = column.pixelWidth;
      }
    });

    // ใช้ความกว้างสูงสุดกับป้ายชื่อ
    categoryAxis.renderer.labels.template.maxWidth = maxColumnWidth;
    console.log("Updated Max Label Width: " + maxColumnWidth);

    // บังคับให้ป้ายชื่อถูกประมวลผลใหม่
    categoryAxis.invalidateLabels();
  }

  // กระตุ้นการอัปเดตความกว้างของป้ายชื่อเมื่อซูม/เลื่อน
  categoryAxis.events.on("rangechangeended", function () {
    updateLabelMaxWidth();
  });

  // กระตุ้นการอัปเดตความกว้างของป้ายชื่อเมื่อขนาดหน้าจอเปลี่ยน
  chart.events.on("sizechanged", function () {
    updateLabelMaxWidth();
  });

  // ตัดทอนป้ายชื่อที่ปลายคำแรกเมื่อจำเป็น
  categoryAxis.renderer.labels.template.adapter.add("textOutput", function (text, target) {
    let labelMaxWidth = target.maxWidth;

    if (labelMaxWidth < 60 && text) { // ตรวจสอบว่าความกว้างของป้ายชื่อมีน้อยกว่า 60 พิกเซล
      let words = text.split(" "); // แยกข้อความเป็นคำ
      return words.length > 1 ? words[0] + ".." : text; // เก็บเฉพาะคำแรกพร้อมกับ ".."
    }

    return text;
  });

  // สร้างชุดคอลัมน์
  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;

  // ใช้สีที่แตกต่างกันสำหรับแต่ละคอลัมน์
  series.columns.template.adapter.add("fill", function (fill, target) {
    return chart.colors.getIndex(target.dataItem.index);
  });

  // เพิ่มแถบเลื่อน
  chart.scrollbarX = new am4core.Scrollbar();
  chart.scrollbarX.marginTop = 30;
  chart.scrollbarX.parent = chart.bottomAxesContainer;

  // เพิ่มการแบ่งแกนสำหรับค่าที่ใหญ่
  let axisBreak = valueAxis.axisBreaks.create();
  axisBreak.startValue = 2100;
  axisBreak.endValue = 22900;

  // ปรับขนาดการแบ่งแกน
  let d = (axisBreak.endValue - axisBreak.startValue) / (valueAxis.max - valueAxis.min);
  axisBreak.breakSize = 0.05 * (1 - d) / d; // ขนาดการแบ่งที่เพิ่มขึ้นเพื่อการมองเห็นที่ดีขึ้น

  // ปรับเอฟเฟกต์ hover เพื่อการโต้ตอบที่ดีขึ้น
  let hoverState = axisBreak.states.create("hover");
  hoverState.properties.breakSize = 1; // ขนาดการแบ่งที่ใหญ่ขึ้นเมื่อเลื่อนเมาส์
  hoverState.properties.opacity = 0.1;
  hoverState.transitionDuration = 1500;

  axisBreak.defaultState.transitionDuration = 1000;

}); // สิ้นสุด am4core.ready()

ด้วยการปรับแต่งเหล่านี้, กราฟของเราตอนนี้ ปรับตัวได้อย่างสมบูรณ์—ป้ายชื่อยังคงอ่านได้, ไม่มีการทับซ้อนที่น่าเกลียด, และทุกอย่างปรับขนาดได้อย่างราบรื่น โดยการปรับความกว้างแบบไดนามิก, การตัดทอนชื่อที่ยาว, และการเชื่อมต่อกับเหตุการณ์, เราได้ทำให้แน่ใจว่ากราฟดูดีบนหน้าจอใดๆ ตอนนี้, ลองไปทำดูนะ!