ヒストグラムライブラリ比較

4つのライブラリで同じ生データ(API の応答時間 1,000 件)の分布を描き、ビン幅を変えたときの形の変化と、集計の仕方の違いを比較

このページでは、ApexCharts・Plotly.js・Recharts・Chart.js を使ったヒストグラム(Histogram)の実装パターンを、API の応答時間 1,000 件という同じ生データのインタラクティブなデモとコードで確認できます。ApexCharts(chart.type: 'histogram')と Plotly.js(type: 'histogram')は生データを渡すだけでライブラリが集計し、Recharts と Chart.js は d3-array の bin() で集計してから棒グラフとして描きます。どのデモもスライダーでビン幅を変えられ、ビン幅によって2つ目の山(キャッシュが効かなかったリクエスト)が見えたり埋もれたりします。

棒グラフがカテゴリごとの集計値を比べる図なのに対し、ヒストグラムは1つの数値の生データを区間(ビン)に分けて数え、分布の形を見せる図です。横軸がカテゴリではなく連続した数値なので、棒どうしをすき間なく並べます。棒グラフ(Bar Chart)の実装比較 →

分布は、四分位数で要約する箱ひげ図や、密度の曲線で描くバイオリンプロットでも表せます(いくつものグループを横に並べて比べやすい)。ボックスプロット(Box Plot)の実装比較 →バイオリンプロット(Violin Plot)の実装比較 →

1. ApexCharts (react-apexcharts)

ApexCharts 6.9 で加わったチャート種別。chart.type: 'histogram' で、集計していない生データ(数値の配列)を series にそのまま渡すと、ApexCharts がビンに分けて数える。ビン幅は plotOptions.histogram.binWidth、ビンを切る範囲は range で指定する。

✓ 生データをそのまま渡す✓ ビン幅を規則で自動決定✓ 割合・密度・累積

チャートを読み込み中...

操作:スライダーでビン幅を変更/棒に hover でビンの範囲と件数を表示

特徴: binWidth を省くと bins: 'auto'(Freedman–Diaconis と Sturges の、幅の狭いほう)で幅が決まり、このデータでは約 17ms 幅・42 本になる(range が 0〜700 のとき。'sturges' なら 11 本)。規則は 'fd'・'sturges'・'scott'・'rice'・'sqrt' から選べ、本数を数値で渡すこともできる。binWidth は範囲を割り切れる幅に丸められる(0〜700 で 150 を指定すると 140 になる)ため、このページのスライダーは割り切れる値だけにしている。range を省くとビンはデータの最小値(ここでは 52ms)から切られるので、境目を切りのいい値にしたいときは range を渡す。normalize で件数を割合('relative')や密度('density')に、cumulative で累積に切り替えられ、系列を複数渡すと同じビンで重ねて描く。既定のツールチップの見出しは「100 to 125」と英語で出るため、tooltip.x.formatter で作り直している。棒の境目は 1px のすき間と白い線(stroke)の両方で作られ、棒が細いと白っぽく見えるので、狭い画面では responsive で線を消している。集計の機能は既定のバンドルに入っているので、sunburst のような追加の import は要らない。react-apexcharts の型の type にはまだ histogram が無いので、値は渡したうえで型だけ外している(コード例を参照)。 CSS変数(--apx-*)を読む仕組みはありますが、再読み込みの呼び出しが別途必要なため、サイトのダークモードには追従しません(ライト表示で固定しています)。
tsx
'use client';
// ApexHistogram.tsx(apexcharts 8.7 / react-apexcharts 2.2。ページ側では next/dynamic の ssr: false で読み込む)
import { useState } from 'react';
import Chart from 'react-apexcharts';
import type { ApexOptions } from 'apexcharts';

// サンプルデータ:API の応答時間(ms)1,000 件。乱数の種を固定して、毎回同じ値にする
let seed = 42;
const rand = () => (seed = (seed * 16807) % 2147483647) / 2147483647;
const normal = () => Math.sqrt(-2 * Math.log(rand())) * Math.cos(2 * Math.PI * rand());
const responseTimes = Array.from({ length: 1000 }, (_, i) =>
  Math.round(i % 5 === 0 ? 420 * Math.exp(0.18 * normal()) : 120 * Math.exp(0.3 * normal())),
);

// ビン幅の候補(ms)。どれも範囲(0〜700)を割り切れる値にする
const WIDTHS = [10, 20, 25, 35, 50, 70, 100, 140];

export default function ApexHistogram() {
  const [binWidth, setBinWidth] = useState(25);

  const options: ApexOptions = {
    chart: { type: 'histogram', toolbar: { show: false } },
    // 生データを渡すと、ApexCharts がビンに分けて数える。binWidth を省くと bins: 'auto' で幅が決まる
    plotOptions: { histogram: { binWidth, range: [0, 700] } },
    colors: ['#3b82f6'],
    xaxis: { type: 'numeric', min: 0, max: 700, tickAmount: 7, title: { text: '応答時間(ms)' } },
    yaxis: { title: { text: 'リクエスト数' } },
    // 狭い画面(breakpoint は画面の幅)では、目盛りを減らし、棒の境目の白い線を消す(細い棒が白っぽく見えるため)
    responsive: [{ breakpoint: 640, options: { xaxis: { tickAmount: 2 }, stroke: { width: 0 } } }],
    tooltip: {
      // x はビンの中央の値。既定の見出しは「100 to 125」と英語なので、ビン幅から範囲を作り直す
      x: { formatter: (v: number) => `${v - binWidth / 2}〜${v + binWidth / 2} ms` },
      y: { formatter: (v: number) => `${v} 件` },
    },
  };

  return (
    <div>
      <label style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
        ビン幅
        <input type="range" min={0} max={WIDTHS.length - 1} value={WIDTHS.indexOf(binWidth)}
          onChange={(e) => setBinWidth(WIDTHS[Number(e.target.value)])} />
        {binWidth} ms
      </label>
      {/* react-apexcharts の型の type に histogram が無いので、値は渡したうえで型だけ外す */}
      <Chart
        type={'histogram' as never}
        series={[{ name: 'リクエスト数', data: responseTimes }]}
        options={options}
        height={320}
      />
    </div>
  );
}

AIプロンプトテンプレート

React + TypeScript で、ApexCharts(apexcharts 8.7・react-apexcharts 2.2)を使ったヒストグラムを実装してください。

- 読み込み: react-apexcharts の Chart を使うコンポーネントを next/dynamic の ssr: false で読み込むこと
- 型: react-apexcharts の type に histogram が無いため、type={'histogram' as never} として値だけ渡すこと
- データ: API の応答時間(ms)1,000 件の生データを、集計せずに series: [{ name, data }] の data に数値の配列で渡すこと(ApexCharts がビンに分けて数える)
- ビン: plotOptions.histogram.binWidth でビン幅を、range: [0, 700] でビンを切る範囲を指定すること。ビン幅は範囲を割り切れる値(10・20・25・35・50・70・100・140 ms)から選ぶスライダーで変えられるようにすること
- 軸: xaxis.type: 'numeric' と min・max・tickAmount で、目盛りを ms の値で打つこと。狭い画面では responsive で tickAmount を減らし、棒の境目の白い線(stroke.width)を 0 にすること(棒が細いと白っぽく見えるため)
- ツールチップ: tooltip.x.formatter でビンの中央の値から「100〜125 ms」のような範囲を作り、tooltip.y.formatter で「◯ 件」と表示すること

このプロンプトはあくまでたたき台です。AIの回答はモデルやバージョン、会話の文脈によって毎回異なります。意図通りに動かない場合は、条件を追記・修正してお使いください。

2. Plotly.js (react-plotly.js)

type: 'histogram' のトレースの x に生データを渡すと、Plotly がビンに分けて数える。ビンは xbins の start・end・size で指定し、省くと Plotly が自動で決める(nbinsx で本数の上限を渡せる)。

✓ 生データをそのまま渡す✓ 正規化(histnorm)✓ 集計関数(histfunc)

チャートを読み込み中...

操作:スライダーでビン幅を変更/棒に hover でビンの範囲と件数を表示

特徴: histnorm で件数を割合('percent')・確率('probability')・密度('density'・'probability density')に変えられ、histfunc で件数の代わりに y の合計・平均・最大なども集計できる(y にも値を渡したとき)。cumulative.enabled で累積分布にもなる。hover の %{x} にはビンの範囲(100 - 124 のように)が入るので、範囲を自分で作り直さなくてよい。react-plotly.js の既定の入口は plotly.js(フル版)を読み込むため、このページでは react-plotly.js/factory に plotly.js-dist-min を渡して Plot を作っている(コード例を参照)。 暗色テンプレートがJS版に同梱されていないため、サイトのダークモードには追従しません(ライト表示で固定しています)。
tsx
'use client';
// PlotlyHistogram.tsx(plotly.js-dist-min 4.2 / react-plotly.js 4.1。ページ側では next/dynamic の ssr: false で読み込む)
import { useState } from 'react';
import createPlotlyComponent from 'react-plotly.js/factory';
import Plotly from 'plotly.js-dist-min';

// react-plotly.js の既定の入口は plotly.js(フル版)を読み込むので、plotly.js-dist-min を渡して作る
const Plot = createPlotlyComponent(Plotly as never);

// サンプルデータ:API の応答時間(ms)1,000 件。乱数の種を固定して、毎回同じ値にする
let seed = 42;
const rand = () => (seed = (seed * 16807) % 2147483647) / 2147483647;
const normal = () => Math.sqrt(-2 * Math.log(rand())) * Math.cos(2 * Math.PI * rand());
const responseTimes = Array.from({ length: 1000 }, (_, i) =>
  Math.round(i % 5 === 0 ? 420 * Math.exp(0.18 * normal()) : 120 * Math.exp(0.3 * normal())),
);

// ビン幅の候補(ms)。どれも範囲(0〜700)を割り切れる値にする
const WIDTHS = [10, 20, 25, 35, 50, 70, 100, 140];

export default function PlotlyHistogram() {
  const [binWidth, setBinWidth] = useState(25);

  return (
    <div>
      <label style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
        ビン幅
        <input type="range" min={0} max={WIDTHS.length - 1} value={WIDTHS.indexOf(binWidth)}
          onChange={(e) => setBinWidth(WIDTHS[Number(e.target.value)])} />
        {binWidth} ms
      </label>
      <Plot
        data={[
          {
            type: 'histogram',
            x: responseTimes,
            // ビンの始まり・終わり・幅。xbins を省くと Plotly が自動で決める(nbinsx で本数の上限を渡せる)
            xbins: { start: 0, end: 700, size: binWidth },
            // histnorm: 'percent' や 'probability density' にすると、件数ではなく割合・密度で描く
            marker: { color: '#3b82f6', line: { color: '#ffffff', width: 1 } },
            // %{x} はビンの範囲(100 - 124 のように出る)
            hovertemplate: '%{x} ms:%{y} 件<extra></extra>',
          },
        ]}
        layout={{
          bargap: 0, // 棒どうしをくっつける
          xaxis: { title: { text: '応答時間(ms)' }, range: [0, 700] },
          yaxis: { title: { text: 'リクエスト数' } },
          margin: { t: 10, r: 10, b: 50, l: 60 },
        }}
        config={{ displayModeBar: false, responsive: true }}
        useResizeHandler
        style={{ width: '100%', height: 320 }}
      />
    </div>
  );
}

AIプロンプトテンプレート

React + TypeScript で、Plotly.js(plotly.js-dist-min 4.2・react-plotly.js 4.1)を使ったヒストグラムを実装してください。

- 読み込み: react-plotly.js の既定の入口は plotly.js(フル版)を読み込むため、react-plotly.js/factory の createPlotlyComponent に plotly.js-dist-min を渡して Plot を作ること。コンポーネントは next/dynamic の ssr: false で読み込むこと
- データ: API の応答時間(ms)1,000 件の生データを、type: 'histogram' のトレースの x に数値の配列で渡すこと(Plotly がビンに分けて数える)
- ビン: xbins の start・end・size でビンの範囲と幅を指定し、スライダーで size を変えられるようにすること(10・20・25・35・50・70・100・140 ms)
- 見た目: layout.bargap: 0 で棒をくっつけ、marker.line で棒の境目に細い線を引くこと
- ツールチップ: hovertemplate で「%{x} ms:%{y} 件」と表示し、<extra></extra> でトレース名を消すこと
- その他: config.displayModeBar: false でツールバーを消し、useResizeHandler で幅に追従させること

このプロンプトはあくまでたたき台です。AIの回答はモデルやバージョン、会話の文脈によって毎回異なります。意図通りに動かない場合は、条件を追記・修正してお使いください。

3. Recharts + d3-array

Recharts にはヒストグラムの種別が無く、集計もしないので、d3-array の bin() で生データをビンに分けて数え、BarChart で描く。barCategoryGap={0} で棒どうしをすき間なく並べ、XAxis を type="number" にして目盛りを ms の値で打つ。

✓ SVG描画✓ 集計は d3-array✓ ダーク自動追従

チャートを読み込み中...

操作:スライダーでビン幅を変更/棒に hover でビンの範囲と件数を表示

特徴: bin() は thresholds に境目の配列を渡すと、その境目で区切る。省くと Sturges の式で本数を決めてから切りのいい境目に揃え(このデータでは 50ms 幅)、thresholdFreedmanDiaconis・thresholdScott に替えることもできる。XAxis を既定のカテゴリの軸のままにすると、目盛りが棒の中央に付いて「100」が 100〜125ms の棒を指すように見えるため、数値の軸にして棒をビンの中央の値に置いている。割合や累積にしたいときは、数えた件数を自分で割る・足す。集計と描画が分かれているぶん、境目の決め方も集計の中身も自由に変えられる。 軸の文字(currentColor)・グリッド・ツールチップ・棒の境目の線を CSS で解決できる値で渡しているので、サイトのダークモードに追従する。
tsx
'use client';
// RechartsHistogram.tsx(recharts 3.10 / d3-array 3.2)
import { useMemo, useState } from 'react';
import { bin, range } from 'd3-array';
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';

// サンプルデータ:API の応答時間(ms)1,000 件。乱数の種を固定して、毎回同じ値にする
let seed = 42;
const rand = () => (seed = (seed * 16807) % 2147483647) / 2147483647;
const normal = () => Math.sqrt(-2 * Math.log(rand())) * Math.cos(2 * Math.PI * rand());
const responseTimes = Array.from({ length: 1000 }, (_, i) =>
  Math.round(i % 5 === 0 ? 420 * Math.exp(0.18 * normal()) : 120 * Math.exp(0.3 * normal())),
);

// ビン幅の候補(ms)。どれも範囲(0〜700)を割り切れる値にする
const WIDTHS = [10, 20, 25, 35, 50, 70, 100, 140];

export default function RechartsHistogram() {
  const [binWidth, setBinWidth] = useState(25);

  // Recharts は数えないので、d3-array の bin() でビンに分けてから棒で描く
  const bins = useMemo(() => {
    const binner = bin().domain([0, 700]).thresholds(range(0, 700, binWidth));
    return binner(responseTimes).map((b) => ({
      x: ((b.x0 ?? 0) + (b.x1 ?? 0)) / 2, // 棒はビンの中央に置く
      x0: b.x0 ?? 0, x1: b.x1 ?? 0,
      count: b.length,
    }));
  }, [binWidth]);

  return (
    <div>
      <label style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
        ビン幅
        <input type="range" min={0} max={WIDTHS.length - 1} value={WIDTHS.indexOf(binWidth)}
          onChange={(e) => setBinWidth(WIDTHS[Number(e.target.value)])} />
        {binWidth} ms
      </label>
      <ResponsiveContainer width="100%" height={320}>
        {/* barCategoryGap={0} で、棒どうしをすき間なく並べる */}
        <BarChart data={bins} barCategoryGap={0} margin={{ top: 10, right: 20, bottom: 10, left: 0 }}>
          <CartesianGrid strokeDasharray="3 3" vertical={false} />
          {/* x を数値の軸にして、目盛りを ms の値で打つ(カテゴリの軸だと、目盛りが棒の中央に付く)。狭い幅では等間隔に間引く */}
          <XAxis type="number" dataKey="x" domain={[0, 700]} ticks={[0, 100, 200, 300, 400, 500, 600, 700]} interval="equidistantPreserveStart" />
          <YAxis />
          <Tooltip
            labelFormatter={(_, payload) => {
              const p = payload?.[0]?.payload;
              return p ? `${p.x0}〜${p.x1} ms` : '';
            }}
            formatter={(v) => [`${v} 件`, 'リクエスト数']}
          />
          <Bar dataKey="count" fill="#3b82f6" stroke="#ffffff" />
        </BarChart>
      </ResponsiveContainer>
    </div>
  );
}

AIプロンプトテンプレート

React + TypeScript で、Recharts(3.10)と d3-array(3.2)を使ったヒストグラムを実装してください。

- 集計: Recharts は集計しないため、d3-array の bin().domain([0, 700]).thresholds(range(0, 700, binWidth)) で生データ(API の応答時間 1,000 件)をビンに分け、ビンの中央の値 x・範囲 x0 / x1・件数 count の配列にすること。binWidth が変わったときだけ useMemo で数え直すこと
- ビン幅: 10・20・25・35・50・70・100・140 ms から選ぶスライダーで変えられるようにすること
- 描画: BarChart に barCategoryGap={0} を渡して、棒どうしをすき間なく並べること
- 軸: XAxis を type="number"・dataKey="x"・domain={[0, 700]} にし、ticks で 100ms ごとの目盛りを打つこと(カテゴリの軸だと目盛りが棒の中央に付くため)。狭い幅では interval="equidistantPreserveStart" で目盛りを等間隔に間引くこと
- ツールチップ: labelFormatter で payload の x0・x1 から「100〜125 ms」を作り、formatter で「◯ 件」と表示すること
- ダークモード: 軸の文字は fill: currentColor、ツールチップの背景・枠は CSS 変数、棒の境目の線は背景色の CSS 変数にして、サイトのダークモードに追従させること

このプロンプトはあくまでたたき台です。AIの回答はモデルやバージョン、会話の文脈によって毎回異なります。意図通りに動かない場合は、条件を追記・修正してお使いください。

4. Chart.js (react-chartjs-2) + d3-array

Chart.js にもヒストグラムの種別は無いので、d3-array の bin() で数えた結果を、数値の x 軸(type: 'linear')を持つ棒グラフで描く。barPercentage・categoryPercentage を 1 にして、棒を間隔いっぱいに広げる。

✓ Canvas描画✓ 集計は d3-array✓ 軽量

チャートを読み込み中...

操作:スライダーでビン幅を変更/棒に hover でビンの範囲と件数を表示

特徴: 数値の x 軸では、Chart.js は棒の幅を「データの間隔」と「目盛りの間隔」の狭いほうに合わせる。目盛りをビン幅より細かく打つと棒が細くなってすき間が空くので、このデモでは ticks.stepSize をビン幅の倍数(100ms 以上)にしている。棒グラフの既定(offset: true)では軸の両端に半本分の余白が入るため、offset: false で棒を目盛りの位置どおりに置く。ツールチップの raw には data に渡した点がそのまま入るので、点に x0・x1 を持たせておけば範囲を出せる。 色は文字列で指定する仕様でCSSを参照できないため、サイトのダークモードには追従しません(ライト表示で固定しています)。
tsx
'use client';
// ChartjsHistogram.tsx(chart.js 4.6 / react-chartjs-2 5.3 / d3-array 3.2)
import { useMemo, useState } from 'react';
import { bin, range } from 'd3-array';
import { Bar } from 'react-chartjs-2';
import { Chart as ChartJS, LinearScale, BarElement, Tooltip } from 'chart.js';

ChartJS.register(LinearScale, BarElement, Tooltip);

// サンプルデータ:API の応答時間(ms)1,000 件。乱数の種を固定して、毎回同じ値にする
let seed = 42;
const rand = () => (seed = (seed * 16807) % 2147483647) / 2147483647;
const normal = () => Math.sqrt(-2 * Math.log(rand())) * Math.cos(2 * Math.PI * rand());
const responseTimes = Array.from({ length: 1000 }, (_, i) =>
  Math.round(i % 5 === 0 ? 420 * Math.exp(0.18 * normal()) : 120 * Math.exp(0.3 * normal())),
);

// ビン幅の候補(ms)。どれも範囲(0〜700)を割り切れる値にする
const WIDTHS = [10, 20, 25, 35, 50, 70, 100, 140];

export default function ChartjsHistogram() {
  const [binWidth, setBinWidth] = useState(25);

  // Chart.js も数えないので、d3-array の bin() で数える。棒はビンの中央に置き、範囲はツールチップ用に持たせる
  const points = useMemo(() => {
    const binner = bin().domain([0, 700]).thresholds(range(0, 700, binWidth));
    return binner(responseTimes).map((b) => ({
      x: ((b.x0 ?? 0) + (b.x1 ?? 0)) / 2, y: b.length,
      x0: b.x0 ?? 0, x1: b.x1 ?? 0,
    }));
  }, [binWidth]);

  return (
    <div>
      <label style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
        ビン幅
        <input type="range" min={0} max={WIDTHS.length - 1} value={WIDTHS.indexOf(binWidth)}
          onChange={(e) => setBinWidth(WIDTHS[Number(e.target.value)])} />
        {binWidth} ms
      </label>
      {/* maintainAspectRatio: false のときは、高さを持つ親要素に入れる */}
      <div style={{ height: 320 }}>
        <Bar
          data={{
            datasets: [{
              label: 'リクエスト数',
              data: points,
              backgroundColor: '#3b82f6',
              borderColor: '#ffffff', borderWidth: 1,
              // 棒の幅を間隔いっぱいにして、隣のビンとすき間なく並べる
              barPercentage: 1, categoryPercentage: 1,
            }],
          }}
          options={{
            maintainAspectRatio: false,
            plugins: {
              tooltip: {
                callbacks: {
                  // raw は data に渡した点(x0・x1 を持たせてある)
                  title: (items) => { const p = items[0].raw as { x0: number; x1: number }; return `${p.x0}〜${p.x1} ms`; },
                  label: (item) => `${item.parsed.y} 件`,
                },
              },
            },
            scales: {
              // x を数値の軸にする。offset: false で、棒を目盛りの位置どおりに置く
              x: {
                type: 'linear', min: 0, max: 700, offset: false, grid: { offset: false },
                // Chart.js は目盛りの間隔でも棒の幅を決めるので、目盛りはビン幅の倍数にする(狭いと棒が細くなる)。
                // maxRotation: 0 で、狭い画面ではラベルを斜めにせず間引く
                ticks: { stepSize: binWidth * Math.ceil(100 / binWidth), maxRotation: 0, autoSkipPadding: 12 },
              },
              y: { beginAtZero: true },
            },
          }}
        />
      </div>
    </div>
  );
}

AIプロンプトテンプレート

React + TypeScript で、Chart.js(chart.js 4.6・react-chartjs-2 5.3)と d3-array(3.2)を使ったヒストグラムを実装してください。

- 登録: LinearScale・BarElement・Tooltip を ChartJS.register で登録すること
- 集計: Chart.js は集計しないため、d3-array の bin() で生データ(API の応答時間 1,000 件)をビンに分け、{ x: ビンの中央, y: 件数, x0, x1 } の点の配列にすること
- ビン幅: 10・20・25・35・50・70・100・140 ms から選ぶスライダーで変えられるようにすること
- 描画: Bar の dataset に barPercentage: 1・categoryPercentage: 1 を渡して、棒をすき間なく並べること
- 軸: scales.x を type: 'linear'・min: 0・max: 700・offset: false にし、ticks.stepSize をビン幅の倍数(100ms 以上)にすること(Chart.js は目盛りの間隔でも棒の幅を決めるため、目盛りが細かいと棒が細くなる)。狭い画面では ticks.maxRotation: 0・autoSkipPadding でラベルを斜めにせず間引くこと
- ツールチップ: callbacks.title で raw の x0・x1 から「100〜125 ms」を作り、callbacks.label で「◯ 件」と表示すること
- 高さ: maintainAspectRatio: false にして、高さを持つ親要素に入れること

このプロンプトはあくまでたたき台です。AIの回答はモデルやバージョン、会話の文脈によって毎回異なります。意図通りに動かない場合は、条件を追記・修正してお使いください。

ライブラリ比較

ライブラリ集計(ビン分け)ビン幅の決め方割合・累積ダークモード
ApexCharts◎ ライブラリ(生データを渡す)binWidth か、bins の規則(auto・fd・sturges など)で自動◎ normalize・cumulativeライト固定
Plotly.js◎ ライブラリ(生データを渡す)xbins.size か、省いて自動(nbinsx で本数の上限)◎ histnorm・cumulativeライト固定
Recharts◯ d3-array の bin() で数えるthresholds に境目を渡す(省くと Sturges)件数を自分で割る・足す✓ 追従
Chart.js◯ d3-array の bin() で数えるthresholds に境目を渡す(省くと Sturges)件数を自分で割る・足すライト固定

選択のポイント

  • •ApexCharts: 生データを渡すだけで、ビン幅の自動決定(規則を選べる)・割合・密度・累積・複数の分布の重ね描きまで設定で済む。範囲を割り切れないビン幅は丸められるので、境目に意味がある場合は割り切れる幅を選ぶ。
  • •Plotly.js: 生データを渡すだけで集計でき、histnorm・histfunc・cumulative と集計の選択肢がいちばん多い。箱ひげ図・バイオリンプロットも同じ書き方で描けるので、分析画面に向く。バンドルが大きいため、遅延読み込みが前提。
  • •Recharts + d3-array: 集計は自分で書くが、bin() の数行で済み、境目の決め方も集計の中身も自由に変えられる。SVG で色を CSS から渡せるので、サイトのダークモードに合わせたい場合に向く。
  • •Chart.js + d3-array: 集計を d3-array に任せ、描画だけ軽い Canvas で行う。すでに Chart.js を使っているプロジェクトに足す場合に向く。棒の幅が目盛りの間隔にも縛られる点に注意。

典型的な使用例

  • ⏱️応答時間・処理時間の分布:遅いリクエストがどのくらいの割合で、どのあたりに固まっているかを見る(平均では見えない2つ目の山や、長く伸びた裾を見つける)
  • 📝テストの点数・評価の分布:得点がどの区間に集まっているか、二極化していないかを見る
  • 🛒購入金額・注文数の分布:顧客の購入金額がどの価格帯に集中しているかを見て、価格帯や施策を決める
  • 🏭品質管理(寸法・重さのばらつき):製品の測定値が規格の範囲に収まっているか、偏りがないかを見る

⚠️ 使用時の注意点

  • •ビン幅で形が大きく変わる。広すぎると2つの山が1つに見え、狭すぎると件数の少ないビンがギザギザになって形が読めない。自動の規則(Sturges・Freedman–Diaconis など)を目安にし、データに意味のある区切り(10ms・1点など)があればそれに合わせる
  • •棒どうしはすき間なく並べる。すき間を空けると、カテゴリを比べる棒グラフに見えてしまう
  • •ビンは「左端を含み、右端を含まない」区間が一般的で、100ms ちょうどの値は 100〜125ms のビンに入る(d3-array と ApexCharts は、最後のビンだけ右端も含む)。ライブラリをまたいで件数を比べるときは、境目を揃える
  • •件数のままだと、データの総数が違う分布どうしは比べにくい。重ねて比べるときは割合か密度にする

ヒストグラム(Histogram)は、1つの数値データを一定の幅の区間(ビン)に分けて、区間ごとの件数を棒の高さで表すグラフ。棒をすき間なく並べ、分布の形(山の位置・山の数・裾の長さ・偏り)を見せる。

応答時間・テストの点数・購入金額・製品の測定値など、平均や中央値だけでは分からない値のばらつきや、性質の違う集団が混ざっていること(2つ以上の山)を見つけたい場面で使われる。

主なバリエーション
  • •基本型(件数)
  • •相対度数型(割合・密度)
  • •累積型(累積度数)
  • •重ね合わせ型(複数の分布を同じビンで重ねる)
  • •密度曲線つき型(カーネル密度推定の線を重ねる)

📊 使用しているサンプルデータ(クリックで表示)

ある API の 1,000 リクエスト分の応答時間(ms)です。8割はキャッシュが効いて 120ms 前後、残りの2割(5件に1件)はキャッシュが効かずに 420ms 前後になる、2つの山を持つデータにしています(中央値 134ms・95パーセンタイル 465ms)。中央値だけでは2つ目の山は見えませんが、ヒストグラムならビン幅しだいで見えます。乱数の種を固定しているので、4つのデモとコード例は同じ値になります。

関連する逆引きリファレンス