はじめに

バーグラフのラベルは、チャートがリサイズされたり、異なる画面解像度で表示されたり、amChartsのズームコントロールを使ってズームイン/アウトされたりすると、可読性の問題に直面することがよくあります。デフォルトでは、amCharts 4のラベルは固定幅を持っており、小さな画面では重なり、切り取り、または読みにくいテキストを引き起こす可能性があります。

ラベルを本当にレスポンシブにするためには、次のことを行う必要があります:

  • チャートのサイズとカラム幅に基づいて、ラベルの最大幅を動的に計算します。
  • 必要に応じて長いラベルを切り取ります
  • イベントフックを使用して、ラベルが適切に調整されるようにします。
    • チャートがリサイズされる。
    • ズームまたはパンが発生する。
    • データが動的に更新される。

この記事では、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; // より良い可視性のためにブレークサイズを増加

    // より良いインタラクションのためにホバー効果を調整
    let hoverState = axisBreak.states.create("hover");
    hoverState.properties.breakSize = 1; // ホバー時に少し大きなブレーク
    hoverState.properties.opacity = 0.1;
    hoverState.transitionDuration = 1500;

    axisBreak.defaultState.transitionDuration = 1000;

}); // end am4core.ready()

ラベルのレスポンシブ最大幅を設定

amCharts 4では、カラムチャートxAxisのラベルには固定のmaxWidthがあります。しかし、この幅は動的に調整されないため、カラム幅とチャートサイズに基づいて計算する必要があります。

これを実現するために、rangechangeendedと sizechanged イベントにフックし、カラムの幅をチェックして、maxWidthを設定します。

実装

// 最も広いカラムに基づいてラベルの最大幅を更新する関数
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("更新された最大ラベル幅: " + maxColumnWidth);

  // ラベルを再処理するよう強制
  categoryAxis.invalidateLabels();
}

// ズーム/パン時にラベル幅を更新するトリガー
categoryAxis.events.on("rangechangeended", function () {
  updateLabelMaxWidth();
});

// 画面リサイズ時にラベル幅を更新するトリガー
chart.events.on("sizechanged", function () {
  updateLabelMaxWidth();
});

説明

関数updateLabelMaxWidthは、チャート内の最も広いカラムに基づいてx軸ラベルの幅を動的に調整します。すべてのカラムをループし、最大幅を見つけて、ラベルに適用し、重なりを防ぎます。

イベントフック:

  • 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("更新された最大ラベル幅: " + 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; // より良い可視性のためにブレークサイズを増加

  // より良いインタラクションのためにホバー効果を調整
  let hoverState = axisBreak.states.create("hover");
  hoverState.properties.breakSize = 1; // ホバー時に少し大きなブレーク
  hoverState.properties.opacity = 0.1;
  hoverState.transitionDuration = 1500;

  axisBreak.defaultState.transitionDuration = 1000;

}); // end am4core.ready()

これらの調整により、私たちのチャートは完璧に適応します—ラベルは読みやすく、重なりもなく、すべてがスムーズにリサイズされます。幅を動的に調整し、長い名前を切り取り、イベントにフックすることで、どの画面でもチャートが素晴らしく見えるようにしました。さあ、試してみてください!