d3.jsの基本

D3.jsを基本からまとめてみます。
今回は棒チャートを例にして、基本的な描画方法とデータの紐づけについて説明します。

## Observableを使う
手っ取り早くD3.jsを使いたい方はObservableというオンラインツールを使いましょう。右上の追加ボタンからNew Notebookを選択して、Simple D3を新規追加します。コードを書くときは`{}`の中に記述しましょう。

![スクリーンショット 2024-10-21 21.33.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2729078/504f14e5-b19a-39c3-9366-302203aff074.png)

https://observablehq.com/

## 書き方の順番
1. 目的の整理
2. グラフの全体構成を考える
3. グラフの描画領域(SVG, Scalable Vector Graphics)を定義
4. データの準備
5. スケールの設定 
6. データの紐つけ 
7. 要素の描画

## 1. 目的の整理
まず、どんなデータをどのように可視化するのかを明確にします。たとえば、「時間経過に伴う売上の推移を折れ線グラフで表示したい」という具体的な要件を書き出します。

## 2. グラフの全体構成を考える
次に、グラフの基本構成を考えます。以下のようなポイントを確認します。

- 軸は何か？ 横軸 (x軸) と縦軸 (y軸) に何を表示するか
- データの範囲はどれくらいか？ 横軸は日付か時間か、縦軸はどの範囲の数値か
- グラフの種類: 折れ線グラフや棒グラフ、円グラフなどどのタイプを使うか

この時点で大まかなイメージを作り、次に具体的なコーディングに進みます。

## 3. グラフの描画領域(SVG, Scalable Vector Graphics)を定義
次に、グラフの描画領域（SVG）を定義します。マージン、幅、高さを決めます。この段階では、最終的なグラフのサイズ感や、軸が邪魔にならないようにマージンの調整を行います。また、チャートを描画するためのSVGコンテナを作成します。これは、グラフを表示するための「キャンバス」になります。
```javascript
const width = 500;
const height = 300;
const margin = { top: 20, right: 30, bottom: 40, left: 40 };

// SVGを作成
const svg = d3.create("svg")  // d3.createを使用
    .attr("width", width)
    .attr("height", height);
```
- `const svg`: ここで変数 svg は、作成されたSVG要素を指しており、以降のコードでこの要素に対して操作を加えるために使います。この変数名は任意の名前であり、例えば const chart や const canvas など、他の名前にしても動作に影響はありません

## 4. データの準備
今回は簡単な配列をデータとして用意します。
```javascript
const data = [30, 86, 168, 281, 303, 365];
```

## 5. スケールの設定 
D3.jsのスケールを使って、データを描画するための座標に変換します。ここでは、棒の高さと幅を設定するためにスケールを定義します。
```javascript
const x = d3.scaleBand()
    .domain(d3.range(data.length))  // データ数に応じたバンド幅
    .range([margin.left, width - margin.right])
    .padding(0.1);  // 棒同士の間隔を設定

const y = d3.scaleLinear()
    .domain([0, d3.max(data)])  // データの最大値に基づくスケール
    .range([height - margin.bottom, margin.top]);  // Y軸を上下反転して設定
```
- `d3.scaleBand()`: バンドスケールを作成しています。バンドスケールは、カテゴリごとに均等に幅を割り当てて、カテゴリごとに離散的な位置を提供します。棒グラフで、各棒の位置と幅を決めるために使われます。
- `.domain(d3.range(data.length))`: スケールの 入力範囲（domain） を設定します。この場合、d3.range(data.length) は data.lengthの数（データの要素数）に応じた連続した数値配列を生成します（例: [0, 1, 2, ..., data.length - 1]）。この配列が domain になり、それぞれの値が1つの棒に対応します。
- `.range([margin.left, width - margin.right])`: 出力範囲（range） を設定します。xスケールの出力範囲は、棒グラフを描画する領域の左端から右端までの位置を決定します。この場合、margin.leftはグラフの左端の位置、width - margin.rightはグラフの右端の位置を示します。これにより、棒グラフが描画される領域が確定します。
- `.padding(0.1)`: 棒と棒の間隔を設定します。0.1は各棒の間に10%の余白を持たせることを意味します。棒が互いに少し隙間を空けて表示されます。
- `d3.scaleLinear()`: リニアスケール（線形スケール）を作成しています。リニアスケールは、入力範囲（domain）と出力範囲（range）が線形に対応するスケールです。すなわち、入力範囲の値が直線的に出力範囲の値に変換されます。
- `.domain([0, d3.max(data)])`: **入力範囲（domain）**を設定します。この場合、[0, d3.max(data)]は、データの最小値を0、最大値をd3.max(data)（データ配列内の最大値）として、縦軸の値の範囲を指定しています。これにより、yスケールは0から最大値までをカバーします。
- `.range([height - margin.bottom, margin.top])`: **出力範囲（range）**を設定します。出力範囲はグラフの描画エリアに対応します。height - margin.bottomはグラフの描画エリアの下端を示し、margin.topは上端を示します。この範囲内にデータをマッピングします。
- 上下反転について: HTMLでは座標の(0, 0)が左上にあるため、y軸のスケールは通常反転させて使います。つまり、0（最小値）はグラフの下側に、最大値は上側に配置されます。このため、rangeの指定が[height - margin.bottom, margin.top]という形で、下から上に変換されています。 

## 6. データの紐つけ 
次に、データをSVGのrect要素に紐づけます。`d3.selectAll()`と`.data()`を使ってデータと要素を結びつけます。
```javascript
svg.selectAll("rect")  // SVG内のすべてのrect要素を選択。棒グラフの棒を描画するために使用される四角形の要素
    .data(data)  // D3のデータ結合機能を使い、配列 data のデータを選択したrect要素に紐づける
    .enter()  // データに対して新しい要素を作成
    .append("rect")  // rect（棒）を追加
    .attr("x", (d, i) => x(i))  // 各棒のX座標をスケールに基づいて設定
    .attr("y", d => y(d))  // 各棒のY座標を設定（データ値に基づいて高さを調整）
    .attr("height", d => y(0) - y(d))  // 棒の高さをデータ値で設定
    .attr("width", x.bandwidth())  // 棒の幅をバンド幅で設定
    .attr("fill", "steelblue");  // 色を設定
```
- `svg.selectAll("rect")`: SVG内の全ての rect 要素を選択（最初は空）。
- `.data(data)`: data を選択した rect 要素に紐づける。
- `.enter()`: データに対応する新しい rect 要素を作成。
- `.append("rect")`: 新しい rect 要素をSVGに追加。
- `.attr("x", (d, i) => x(i))`: 各棒のX座標を設定。
- `.attr("y", d => y(d))`: 各棒のY座標を設定。
- `.attr("height", d => y(0) - y(d))`: 各棒の高さを設定。
- `.attr("width", x.bandwidth())`: 各棒の幅を設定。
- `.attr("fill", "steelblue")`: 各棒の色を設定。
## 7. 要素の描画
次に、X軸とY軸をSVGに追加して、データをわかりやすくします。
```javascript
// X軸を追加
svg.append("g")
    .attr("transform", `translate(0,${height - margin.bottom})`)  // X軸をグラフの下に配置
    .call(d3.axisBottom(x));

// Y軸を追加
svg.append("g")
    .attr("transform", `translate(${margin.left},0)`)  // Y軸を左側に配置
    .call(d3.axisLeft(y));
```
## 全体のコード
```javascript
// グラフの設定
{
  const width = 500;
  const height = 300;
  const margin = { top: 20, right: 30, bottom: 40, left: 40 };
  const data = [30, 86, 168, 281, 303, 365];

  const x = d3.scaleBand()
      .domain(d3.range(data.length))
      .range([margin.left, width - margin.right])
      .padding(0.1);

  const y = d3.scaleLinear()
      .domain([0, d3.max(data)])
      .range([height - margin.bottom, margin.top]);

  const svg = d3.create("svg")  // d3.createを使用
      .attr("width", width)
      .attr("height", height);

  // 棒を追加
  svg.selectAll("rect")
      .data(data)
      .enter()
      .append("rect")
      .attr("x", (d, i) => x(i))
      .attr("y", d => y(d))
      .attr("height", d => y(0) - y(d))
      .attr("width", x.bandwidth())
      .attr("fill", "steelblue");

  // X軸を追加
  svg.append("g")
      .attr("transform", `translate(0,${height - margin.bottom})`)
      .call(d3.axisBottom(x));

  // Y軸を追加
  svg.append("g")
      .attr("transform", `translate(${margin.left},0)`)
      .call(d3.axisLeft(y));

  return svg.node();  // SVG要素を返す
}
```
### おまけ
D3.jsの公式チュートリアル

https://d3js.org/getting-started