JavaScript

Apache ECharts 棒グラフ入門|縦棒・横棒の実装からボタン切り替えまで

みなと

この記事は、Apache EChartsを使った棒グラフの実装解説です。ECharts自体の概要やセットアップ方法は、前回の折れ線グラフ記事でまとめています。EChartsが初めての方は、先にそちらをお読みください。

あわせて読みたい
Apache ECharts 折れ線グラフ入門|最小サンプルから実務レベルに仕上げる手順
Apache ECharts 折れ線グラフ入門|最小サンプルから実務レベルに仕上げる手順

Apache ECharts 公式サイト

この記事では、棒グラフ(縦棒・横棒)の実装を中心に、デザイン調整・レスポンシブ対応・ボタン切り替えまで一通り解説します。

完成デモを触ってみよう

まずは完成デモを見て、最終的にどんな形を目指すか確認してみましょう。ボタンを押すと、ページ別の流入経路アクセス数グラフ(※数値はサンプル)を表示・切り替えできます。

ボタンを押すと
グラフが表示されます

PCとスマホで見比べると、凡例の位置やラベルの表示が自動で切り替わり、どちらの画面でも見やすいレイアウトになっています。

コード全体(HTML / CSS / JavaScript)

ここからは実際にコードを見ていきます。HTML・CSSはコードとポイントをあわせて解説します。JavaScriptは全体を掲載してから、次のセクションで詳しく解説します。

HTML

<!-- 切り替えボタンのエリア -->
<div class="chart-switcher">
  <button id="serviceChartBtn" class="chart-tab" type="button">サービス紹介ページ</button>
  <button id="blogChartBtn" class="chart-tab" type="button">ブログ記事ページ</button>
</div>

<!-- グラフ表示エリア -->
<div id="echartsContainer" class="echarts-container">
  <!-- グラフが描画される前に表示するプレースホルダー -->
  <div class="chart-placeholder">
    <p class="chart-placeholder-text">ボタンを押すと<br>グラフが表示されます</p>
  </div>
</div>

HTMLはシンプルな2つのブロックで構成されています。

切り替えボタンのエリアには、id を付けた2つの button を並べています。この id をJavaScript側で取得して、クリックイベントに紐づけます。

グラフ表示エリアは id="echartsContainer"div で、EChartsはこの要素をコンテナとしてグラフを描画します。初期状態ではプレースホルダー(「ボタンを押すとグラフが表示されます」)を表示しておき、ボタンを押したタイミングでグラフに置き換わります。

スワン
スワン

EChartsのグラフ描画に必須なのは、コンテナとなる divid="echartsContainer")だけです。ボタンやプレースホルダーは今回のデモ用の実装なので、ただグラフを表示したいだけであれば、コンテナのみで問題ありません。

CSS

/* ===============================
   切り替えボタンエリア
   =============================== */
.chart-switcher {
  display: flex;
  justify-content: center;
}

@media (min-width: 768px) {
  .chart-switcher {
    gap: 30px;
  }
}

@media (max-width: 767px) {
  .chart-switcher {
    gap: 15px;
  }
}

/* ボタンの基本スタイル */
.chart-tab {
  display: block;
  position: relative;
  border: none;
  border-radius: 5px;
  padding: 15px 0;
  background: #c11a51;
  color: #fff;
  font: inherit;
  font-weight: 700;
  line-height: 1.4;
  text-align: center;
  cursor: pointer;
  appearance: none;
  transition: background-color 300ms, color 300ms;
}

@media (min-width: 768px) {
  .chart-tab {
    width: 200px;
    font-size: 16px;
  }
}

@media (max-width: 767px) {
  .chart-tab {
    flex: 1;
    width: 100%;
    font-size: 15px;
  }
}

/* 選択中ボタンの下に表示する三角形 */
.chart-tab::before {
  content: "";
  display: block;
  position: absolute;
  left: 50%;
  bottom: -12px;
  width: 15px;
  height: 12px;
  background: #f9e8ed;
  clip-path: polygon(0% 0%, 100% 0%, 50% 100%);
  transform: translateX(-50%);
  opacity: 0;
  transition: opacity 300ms;
}

/* ホバー時(PCのみ) */
@media (min-width: 768px) {
  .chart-tab:hover {
    background: #890631;
  }
}

/* 選択中のボタン */
.chart-tab.is-active {
  background: #f9e8ed;
  color: #c11a51;
  pointer-events: none;
}

/* アクティブ時だけ三角を表示 */
.chart-tab.is-active::before {
  opacity: 1;
}

/* ===============================
   グラフエリア
   =============================== */
@media (min-width: 768px) {
  .echarts-container {
    height: 400px; /* PCのチャート高さ */
    margin-top: 40px;
  }
}

@media (max-width: 767px) {
  .echarts-container {
    height: 300px; /* SPのチャート高さ */
    margin-top: 30px;
  }
}

/* プレースホルダー(グラフ描画前の表示) */
.chart-placeholder {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  border-radius: 5px;
  background-color: rgba(68, 102, 206, 0.09);
  text-align: center;
}

.chart-placeholder-text {
  color: #31345e;
  font-weight: 700;
  line-height: 1.6;
  opacity: 0.5;
}

@media (min-width: 768px) {
  .chart-placeholder-text {
    font-size: 26px;
  }
}

@media (max-width: 767px) {
  .chart-placeholder-text {
    font-size: 20px;
  }
}

CSSも大きく2つのブロックに分かれています。

ボタンエリアは display: flex で横並びにしています。is-active クラスが付いたボタンは背景色と文字色が反転し、::before 擬似要素で下向きの三角形を表示します。pointer-events: none を合わせて指定することで、選択中のボタンを押せない状態にしています。

グラフエリアは ECharts の仕様上、高さをCSSで指定する必要があります。高さを指定しないとグラフが表示されないため、必ず設定しておきましょう。

スワン
スワン

HTMLと同様に、EChartsのグラフ描画に必須なCSSはコンテナの高さ指定だけです。ボタンやプレースホルダーのスタイルは今回のデモ用の実装です。

JavaScript

document.addEventListener('DOMContentLoaded', () => {
  const btnService = document.getElementById('serviceChartBtn');
  const btnBlog = document.getElementById('blogChartBtn');
  const chartDom = document.getElementById('echartsContainer');

  // コンテナが存在しない場合は処理を止める(エラー防止)
  if (!chartDom) return;

  let myChart = null;

  // 2つのグラフで共通する設定をまとめたベースoption
  const baseOption = {
    // グラフ全体の文字スタイル
    textStyle: {
      color: '#333',
      fontFamily: '"Noto Sans JP", sans-serif',
      fontSize: 12,
      fontWeight: 300
    },
    // 凡例
    legend: {
      itemGap: 20,
      itemWidth: 16,
      itemHeight: 12,
      selectedMode: false
    },
    // グラフエリアの余白(containLabel: trueで軸ラベルがはみ出さないようにする)
    grid: {
      right: 0,
      bottom: 0,
      left: 0,
      containLabel: true
    },
    // ツールチップ(ホバー時の情報表示)
    tooltip: {
      trigger: 'axis',
      valueFormatter: (value) => `${value.toLocaleString()} PV`,
      textStyle: {
        color: '#333',
        fontFamily: '"Noto Sans JP", sans-serif',
        fontSize: 12,
        fontWeight: 300
      },
      padding: 15
    },
    // 横軸:流入経路のラベルを並べる(dataはmedia側で幅ごとに指定)
    xAxis: {
      type: 'category',
    },
    // 縦軸:数値の目盛りを自動生成
    yAxis: {
      type: 'value'
    },
    // チャートの幅に応じてレイアウトを切り替え
    media: [
      {
        // PC幅
        query: { minWidth: 740 },
        option: {
          title: {
            textStyle: { fontSize: 18 }
          },
          legend: { top: 5, right: 0 },
          grid: { top: 55 },
          xAxis: {
            axisLabel: { rotate: 0 },
            data: ['オーガニック検索', 'SNS', '参照元', '直接', 'その他']
          }
        }
      },
      {
        // スマホ幅:ラベルを斜め表示 & 長いラベルを短縮
        query: { maxWidth: 739 },
        option: {
          title: {
            textStyle: { fontSize: 16 }
          },
          legend: { top: 40, left: 0 },
          grid: { top: 85 },
          xAxis: {
            axisLabel: { rotate: 45 },
            data: ['オーガニック', 'SNS', '参照元', '直接', 'その他']
          }
        }
      }
    ]
  };

  // サービス紹介ページのグラフoption(baseOptionに差分を追加)
  const optionService = {
    ...baseOption,
    title: {
      text: 'サービス紹介ページ 流入経路別アクセス数',
      top: 0,
      left: 0
    },
    series: [
      {
        name: '今月',
        type: 'bar',
        data: [1320, 580, 430, 310, 180],
        itemStyle: { color: '#c11a51' },
        emphasis: { focus: 'series' } // ホバー時にこの系列を強調
      },
      {
        name: '先月',
        type: 'bar',
        data: [1080, 470, 460, 280, 150],
        itemStyle: { color: '#4466ce' },
        emphasis: { focus: 'series' }
      }
    ],
  };

  // ブログ記事ページのグラフoption(baseOptionに差分を追加)
  const optionBlog = {
    ...baseOption,
    title: {
      text: 'ブログ記事ページ 流入経路別アクセス数',
      top: 0,
      left: 0
    },
    series: [
      {
        name: '今月',
        type: 'bar',
        data: [2140, 980, 310, 220, 130],
        itemStyle: { color: '#c11a51' },
        emphasis: { focus: 'series' }
      },
      {
        name: '先月',
        type: 'bar',
        data: [1860, 750, 340, 200, 110],
        itemStyle: { color: '#4466ce' },
        emphasis: { focus: 'series' }
      }
    ]
  };

  // グラフを描画し、押したボタンをアクティブにする関数
  function showChart(optionToSet, activeBtn) {
    // まだ初期化していない場合のみecharts.init()を呼ぶ(二重初期化防止)
    if (!myChart) {
      myChart = echarts.init(chartDom);
    }
    // 第2引数のtrueで前の設定を残さず新しいoptionに切り替える
    myChart.setOption(optionToSet, true);

    // ボタンのアクティブ状態を切り替える
    [btnService, btnBlog].forEach(btn => btn.classList.remove('is-active'));
    activeBtn.classList.add('is-active');
  }

  btnService.addEventListener('click', () => showChart(optionService, btnService));
  btnBlog.addEventListener('click', () => showChart(optionBlog, btnBlog));

  // 画面サイズ変更時にグラフを再描画(描画済みの場合のみ)
  window.addEventListener('resize', () => {
    if (myChart) myChart.resize();
  });
});

JavaScriptのポイント解説

ここではJavaScriptのコードを解説します。まず全体の構造を把握してから、各ブロックの中身を順番に見ていきます。

JavaScriptの全体構造

このコードは、大きく6つのブロックで構成されています。

ブロック役割
baseOption2つのグラフで共通する設定をまとめたベース
optionServiceサービス紹介ページ用のグラフ設定(baseOptionにタイトルとデータを追加)
optionBlogブログ記事ページ用のグラフ設定(baseOptionにタイトルとデータを追加)
showChart()グラフを描画し、ボタンのアクティブ状態を切り替える関数
addEventListenerボタンのクリックイベントをshowChart()に紐づける
resize処理画面サイズ変更時にグラフを再描画する

baseOption を土台にして、ページごとの差分(タイトルとデータ)だけを乗せた optionService / optionBlog を用意し、ボタンを押したときに showChart() で切り替える——これがコード全体の基本的な流れです。

1)baseOption:共通設定をまとめる

const baseOption = {
  textStyle: { ... },
  legend: { ... },
  grid: { ... },
  tooltip: { ... },
  xAxis: { ... },
  yAxis: { ... },
  media: [ ... ]
};

2つのグラフで共通する設定——文字スタイル・凡例・余白・ツールチップ・軸・レスポンシブ——をすべて baseOption にまとめています。データ(series)はページごとに異なるため、baseOption には含めていません。グラフの数が増えても、baseOption を変えれば全体に反映されるので管理が楽になります。

baseOption の中身を項目ごとに見ていきます。

textStyle

textStyle: {
  color: '#333',
  fontFamily: '"Noto Sans JP", sans-serif',
  fontSize: 12,
  fontWeight: 300
}

グラフ内の文字(タイトル・軸ラベル・凡例など)に共通で効く文字スタイルです。色・フォント・サイズ・ウェイトをサイトのデザインに合わせて指定します。

legend

legend: {
  itemGap: 20,
  itemWidth: 16,
  itemHeight: 12,
  selectedMode: false
}

凡例(「今月」「先月」の色と名前の対応を示す目印)の設定です。itemGap は凡例どうしの間隔、itemWidth / itemHeight は凡例アイコンのサイズです。

selectedMode: false は、凡例クリックによる系列の表示・非表示の切り替えを無効にする設定です。デフォルトでは凡例をクリックすると特定の系列を隠せますが、今回は常に2本表示したいので無効にしています。

凡例の表示位置(top / right など)はPC・スマホで変えたいため、media 側で指定しています。

grid

grid: {
  right: 0,
  bottom: 0,
  left: 0,
  containLabel: true
}

グラフ本体(軸と棒が描かれるエリア)の余白を指定します。right / bottom / left はそれぞれの辺からの距離です。top はPC・スマホで変えたいため media 側で指定しています。

containLabel: true を指定すると、縦軸の数値ラベルや横軸のラベルがグラフエリアの外にはみ出さなくなります。これを入れ忘れるとラベルがはみ出してしまうことがあるため、余白を0にするときはセットで指定しておくのがおすすめです。

tooltip

tooltip: {
  trigger: 'axis',
  valueFormatter: (value) => `${value.toLocaleString()} PV`,
  textStyle: { ... },
  padding: 15
}

ホバー時の情報表示です。

trigger: 'axis' は、棒グラフや折れ線グラフのようにカテゴリ軸を持つグラフで主に使われる設定です。カーソルを合わせた軸上の値——今回であれば今月・先月の両方——がまとめて表示されます。

valueFormatter は表示する数値の形式を整える関数です。value.toLocaleString() で数値を3桁区切りのカンマ付き(例:1,320)に変換し、さらに PV を付けて 1,320 PV のように表示しています。なお、textStyle はグラフ全体の設定が反映されないケースがあるため、ツールチップ側にも同様に指定しています。

xAxis / yAxis

xAxis: {
  type: 'category'
},
yAxis: {
  type: 'value'
}

横軸(xAxis)は type: 'category' で流入経路のラベルを並べる軸、縦軸(yAxis)は type: 'value' で数値の目盛りを自動生成する軸です。

横軸の data(ラベル配列)はPC・スマホでラベルを変えたいため、media 側で指定しています。

media

media: [
  {
    query: { minWidth: 740 },
    option: {
      title: { textStyle: { fontSize: 18 } },
      legend: { top: 5, right: 0 },
      grid: { top: 55 },
      xAxis: {
        axisLabel: { rotate: 0 },
        data: ['オーガニック検索', 'SNS', '参照元', '直接', 'その他']
      }
    }
  },
  {
    query: { maxWidth: 739 },
    option: {
      title: { textStyle: { fontSize: 16 } },
      legend: { top: 40, left: 0 },
      grid: { top: 85 },
      xAxis: {
        axisLabel: { rotate: 45 },
        data: ['オーガニック', 'SNS', '参照元', '直接', 'その他']
      }
    }
  }
]

media は、チャートの幅に応じてレイアウトを切り替えるための仕組みです。CSSのメディアクエリに近い感覚で使えますが、判定するのは「画面幅」ではなく「チャート(コンテナ)の幅」です。

今回はPC幅・スマホ幅それぞれで以下の調整を加えています。

PC幅では、凡例をグラフ右上に配置(legend: { top: 5, right: 0 })し、タイトルと凡例の高さぶんの余白を上部に確保(grid: { top: 55 })しています。

スマホ幅では、横幅が狭いためタイトルと凡例を横並びにできません。そのため凡例をタイトルの下に左寄せで配置(legend: { top: 40, left: 0 })し、凡例のぶんだけ上部余白を広めに確保(grid: { top: 85 })しています。

また、スマホ幅では axisLabel: { rotate: 45 } でラベルを45度傾けて表示し、あわせて data で「オーガニック検索」を「オーガニック」に短縮しています。

PC幅では axisLabel: { rotate: 0 } を明示しています。スマホ幅で rotate: 45 を設定した後、PC幅に戻ったときに傾きがリセットされないケースがあるため、0 を明示することで確実に元に戻るようにしています。

2)optionService / optionBlog:差分だけを上書きする

const optionService = {
  ...baseOption,
  title: {
    text: 'サービス紹介ページ 流入経路別アクセス数',
    top: 0,
    left: 0
  },
  series: [
    {
      name: '今月',
      type: 'bar',
      data: [1320, 580, 430, 310, 180],
      itemStyle: { color: '#c11a51' },
      emphasis: { focus: 'series' }
    },
    {
      name: '先月',
      type: 'bar',
      data: [1080, 470, 460, 280, 150],
      itemStyle: { color: '#4466ce' },
      emphasis: { focus: 'series' }
    }
  ]
};

optionServiceoptionBlog は、baseOption を土台に、ページごとの差分だけを追加したものです。中身を項目ごとに見ていきます。

スプレッド構文(…baseOption)

const optionService = {
  ...baseOption,
  // 差分だけを追加
};

...baseOption は、baseOption の設定をすべてそのまま引き継ぐための書き方です。2つのグラフで共通する設定はすべて baseOption にまとめてあるので、ここでは差分(タイトルとデータ)だけを追加すればよくなります。

title

title: {
  text: 'サービス紹介ページ 流入経路別アクセス数',
  top: 0,
  left: 0
}

グラフのタイトルです。text に表示する文字列を指定し、top / left で表示位置を左上に固定しています。2つのグラフでタイトルの内容だけが変わります。

series

series: [
  {
    name: '今月',
    type: 'bar',
    data: [1320, 580, 430, 310, 180],
    itemStyle: { color: '#c11a51' },
    emphasis: { focus: 'series' }
  },
  { ... } // 先月も同様
]

グラフとして描くデータのまとまりです。項目ごとに見ていきます。

name は凡例やツールチップで表示される系列名です。type: 'bar' で棒グラフとして描画します。data は横軸のラベル順に対応する数値の配列です。title.text とともに、2つのグラフで変わる部分です。

itemStyle: { color: '#c11a51' } は棒の色を指定します。系列ごとに色を変えることで「今月/先月」の違いが一目で分かるようになります。

emphasis: { focus: 'series' } はホバー時の強調表示の設定です。デフォルト('none')ではホバーしても他のデータは変化しませんが、focus: 'series' にすると、カーソルを乗せた系列以外がフェードアウトするため、どちらの系列を見ているか分かりやすくなります。

3)showChart() と addEventListener:ボタン操作でグラフを表示・切り替える

showChart() はボタンを押したときの処理をまとめた関数で、addEventListener でクリックイベントに紐づけています。中身を順番に見ていきます。

echarts.init():チャートの初期化

if (!myChart) {
  myChart = echarts.init(chartDom);
}

echarts.init() でチャートのインスタンスを生成し、描画できる状態にします。if (!myChart) のガードを入れているのは、ボタンを複数回押したときに二重初期化が起きないようにするためです。

setOption():グラフを描画・切り替える

myChart.setOption(optionToSet, true);

setOption()option を渡すことで、グラフの内容を描画・更新します。第2引数に true を渡すと、前の設定を完全にリセットしてから新しい option を反映するため、グラフを切り替えたときに古い設定が残りません。

classList:ボタンのアクティブ状態を切り替える

[btnService, btnBlog].forEach(btn => btn.classList.remove('is-active'));
activeBtn.classList.add('is-active');

グラフを切り替えるのと同時に、押したボタンだけ is-active クラスを付けて見た目を変えます。「今どのグラフを見ているか」がひと目で分かるようになります。

addEventListener:クリックイベントに紐づける

btnService.addEventListener('click', () => showChart(optionService, btnService));
btnBlog.addEventListener('click',    () => showChart(optionBlog,    btnBlog));

各ボタンのクリックイベントに showChart() を紐づけています。どのボタンが押されたかに応じて渡す option とアクティブにするボタンを切り替えるだけなので、ボタンが増えても同じパターンで追加できます。

4)resize処理:画面サイズに追従する

window.addEventListener('resize', () => {
  if (myChart) myChart.resize();
});

myChart.resize() は、コンテナのサイズに合わせてグラフを再描画するメソッドです。画面サイズが変わったときに呼ぶことで、グラフが崩れずにリサイズされます。if (myChart) のガードを入れているのは、まだグラフが描画されていない状態(ボタンを押す前)で resize() を呼ぶ必要がないためです。

横棒グラフにするには

縦棒グラフを横棒に変えるには、JavaScriptをいくつか修正する必要があります。HTMLとCSSはそのまま変更不要です。

完成デモを触ってみよう

ボタンを押すと
グラフが表示されます

変更箇所の解説

① xAxis と yAxis を入れ替える

// 縦棒
xAxis: { type: 'category' },
yAxis: { type: 'value' }

// 横棒
xAxis: { type: 'value' },
yAxis: { type: 'category' }

縦棒では xAxiscategory(ラベル)・yAxisvalue(数値)でしたが、横棒ではこれを入れ替えます。

スワン
スワン

EChartsには「横棒にする」という専用の設定はありません。series.type: 'bar' はそのままで、軸の役割を入れ替えるだけで、EChartsが自動的に横向きの棒グラフとして描画してくれます。

② media 内のラベル設定を yAxis に変える

// 縦棒
// PC幅
xAxis: { axisLabel: { rotate: 0 }, data: ['オーガニック検索', 'SNS', '参照元', '直接', 'その他'] }
// スマホ幅
xAxis: { axisLabel: { rotate: 45 }, data: ['オーガニック', 'SNS', '参照元', '直接', 'その他'] }

// 横棒
// PC幅
yAxis: { data: ['その他', '直接', '参照元', 'SNS', 'オーガニック検索'] }
// スマホ幅
yAxis: { data: ['その他', '直接', '参照元', 'SNS', 'オーガニック'] }

縦棒では media の中で xAxis.data にラベルを指定していましたが、横棒では yAxis.data に変えます。

EChartsの横棒グラフは yAxis.data下から上に向かって表示されるため、「オーガニック検索」を一番上に表示したい場合は data を逆順(その他→直接→…→オーガニック検索)にする必要があります。

また、縦棒のスマホ幅では axisLabel: { rotate: 45 } でラベルを斜め表示にしていましたが、横棒ではラベルが縦軸(yAxis)に並ぶため斜め表示は不要です。スマホ幅の media から rotate の指定を外します。

③ series の data を逆順にする

// 縦棒
series: [
  { name: '今月', data: [1320, 580, 430, 310, 180] },
  { name: '先月', data: [1080, 470, 460, 280, 150] }
]

// 横棒
series: [
  { name: '今月', data: [180, 310, 430, 580, 1320] }, // 逆順
  { name: '先月', data: [150, 280, 460, 470, 1080] }  // 逆順
]

series の数値配列も②と同じ順序に合わせて逆順にします。

④ grid.right に余白を追加する

// 縦棒
grid: { right: 0, ... }

// 横棒
grid: { right: 20, ... }

横棒グラフでは横軸(数値軸)の最大値付近のラベルが右端に表示されます。grid.right: 0 のままだと数値が切れてしまうことがあるため、right: 20 程度の余白を追加します。

まとめ

この記事では、Apache EChartsを使って棒グラフを実装し、デザイン調整・レスポンシブ対応・ボタン切り替え・横棒への変換まで一通り解説しました。

基本の形を押さえれば、あとはデータや色を差し替えるだけで実務向けのグラフに仕上げられます。ぜひ今回のコードをベースに、自分の案件に合わせてカスタマイズしてみてください。

押していただけると励みになります!

ABOUT ME
みなと
みなと
フロントエンドエンジニア
東京のWeb制作会社で15年以上働いている現役フロントエンドエンジニアです。これまで、いろんなプロジェクトに関わりながら、フロントエンド開発やWebデザインに取り組んできました。このブログでは、今までの経験を活かして、Web制作に役立つ情報やノウハウをシェアしていきたいと思います。初心者の方から、現場で働く方まで、誰でも参考にできる内容をお届けしますので、ぜひ覗いてみてください。
記事URLをコピーしました