D3 v7 グラフ - d3-scale、d3-axis、d3-shape

【関連記事】
[React 再入門 (React hook API) - Qiita](https://qiita.com/sand/items/7a6ef787d11c3ebabf08)
[D3 v7 入門 - Enter / Update / Exit - Qiita](https://qiita.com/sand/items/8b5ca42d1bf511e61ccd)
[D3 v7 応用 - Enter / Update / Exit - Qiita](https://qiita.com/sand/items/999715cebe52ade66c0b)
[D3 v7 グラフ - d3-scale、d3-axis、d3-shape - Qiita](https://qiita.com/sand/items/721db51592e1d161d69d)
[React+D3 アプリ作成入門 - Qiita](https://qiita.com/sand/items/a5156fe4b29aab993186)
[D3 v7 棒グラフのいろいろ - Qiita](https://qiita.com/sand/items/1658dd67b320c0810c77)
[D3 v7 都道府県別人口の treemap - Qiita](https://qiita.com/sand/items/691ba4be18825b23268d)


D3 の基本概念である Data Join - Enter / Update / Exit の説明は上記の記事で述べましたが、今回は実際に D3 でグラフを描く際に必要となる d3-scale、d3-axis、d3-shape　について述べたいと思います。この３つのモジュールは大きなものですが、今回は必要最小限な部分についてまとめました。




# １．d3-scale
[d3-scale - 公式ドキュメント](https://d3js.org/d3-scale)
* Scale は抽象的データをビジュアル表現にマップするものです。ほとんどの場合、データを平面に描くために使われます。
* 例えば scale は、ビジュアルエンコーディング（カラー、線幅、またはシンボル文字など）表現を用い、時間データを水平座標に、気温データを垂直座標にマップし、散布図を描きます。
* またカテゴリデータや適切な距離を必要とする離散データを表現するためにも使われます。

::: note info
**d3-scale**

* **Linear scales** - for quantitative data
* **Time scales** - for time-series data
* **Pow scales** - for quantitative data (that has a wide range)
* **Log scales** - for quantitative data (that has a wide range)
* **Symlog scales** - for quantitative data (that has a wide range)
* **Ordinal scales** - for categorical or ordinal data
* **Band scales** - for categorical or ordinal data as a position encoding
* **Point scales** - for categorical or ordinal data as a position encoding
* **Sequential scales** - for quantitative data as a sequential color encoding
* **Diverging scales** - for quantitative data as a diverging color encoding
* **Quantile scales** - for quantitative data as a discrete encoding
* **Quantize scales** - for quantitative data as a discrete encoding
* **Threshold scales** - for quantitative data as a discrete encoding
:::

scale エンコーディングのビジュアル化については、d3-axis や scale.ticks, scale.tickFormat の項を参照してください。


## １－１．Linear scales

* Linear scales は連続した定量的な**定義域(domain)** を、連続的な**値域(range)** へ、線形変換を使ってマップするものです。
* つまり y = f(x) で言えば、x は定量的で連続な値、y も連続した値。
* Linear scales は連続した定量的なデータに対しての、デフォルトの選択です。何故なら比例差を保持するからです。y = ax + b ですから、x が増加したら比例して y も増加します。


::: note warn
**定量的**は「物事を数値化できるさま」、**定性的**は「物事で数値化できないさま」を指します。
:::

### １－１－１．scaleLinear(domain, range)
::: note info
**scaleLinear(domain, range)**
引数の domain と range で構成される linear scale を作成します。
（デフォルトの interpolator と clamping は無効。）
引数が１つの場合は range が指定されたと解釈します。domain または range が省略されたときは、デフォルトの **[0, 1]** が指定されたと解釈します。
:::

以下の例、domain は[0, 100]で 0 から 100 までの実数です。連続的で定量的な値ですね。range も ["red", "blue"] で連続した rgb 数値になります。

```JavaScript:
    let func = d3.scaleLinear([0, 100], ["red", "blue"])
    console.log(func(0));
    console.log(func(100));
    console.log(func(10));
    console.log(func(50));
    console.log(func(50.5));
```

```
rgb(255, 0, 0)
rgb(0, 0, 255)
rgb(230, 0, 26)
rgb(128, 0, 128)
rgb(126, 0, 129)
```

domain が省略された場合。

```JavaScript:
d3.scaleLinear(["red", "blue"]) // default domain of [0, 1]
```


### １－１－２．linear.domain(domain)
::: note info
**linear.domain(domain)**
Linear scales の domain を引数で指定されたものに設定します。引数の domain 配列は 2 つ以上の要素を持ちます。この要素が数値でない場合は数値変換されます。 新 domain 設定後の scale が返されます。
:::

```JavaScript:
const x = d3.scaleLinear().domain([10, 130]);
```

**連続 scale** は通常、配列の中に2つの値を持ちます。2つ以上の値を持つ場合は **区分的 scale** が作成されます。内部的には、区分的 scale はバイナリサーチを使って補正を行います。ですから domain は昇順か降順になっている必要があります。

::: note warn
**連続 scale**の連続と、以下の定義の連続は違った意味で使われていることに注意。
「Linear scales は連続した定量的な定義域(domain) を、連続的な値域(range) へ、線形変換を使ってマップするものです。」
:::

例えば、以下のような color scale の場合は、負の数で "red" と "white"　の間の色を補正し、正の数で "white" と "green" の間の色を補正します。


```JavaScript:
    const color = d3.scaleLinear(["red", "white", "green"]).domain([-1, 0, 1]);

    console.log(color(-1));
    console.log(color(-0.5));
    console.log(color(0));
    console.log(color(0.5));
    console.log(color(1));
```


```
rgb(255, 0, 0)       // "red"
rgb(255, 128, 128)
rgb(255, 255, 255)   // "white"
rgb(128, 192, 128)
rgb(0, 128, 0)       // "green"
```

もし引数の domain が省略されたら、現在の domain をそのまま返します。

```JavaScript:
color.domain() // [-1, 0, 1]
```

### １－１－３．linear.range(range)

::: note info
**linear.range(range)**
Linear scales の range を引数で指定されたものに設定します。引数の range 配列は2つ以上の要素を持ちます。domain と違って range の要素は数値以外でも、補正がサポートされている範囲で、任意の値で構いません。新 range 設定後の scale が返されます。
:::

```JavaScript:
const x = d3.scaleLinear().range([0, 960]);
```

もし引数の range が省略されたら、現在の range をそのまま返します。

```JavaScript:
x.range() // [0, 960]
```


# ２．d3-axis
[d3-axis - 公式ドキュメント](https://d3js.org/d3-axis)
axis component はscale の位置の目安となる見易いマークを描いてくれます。それは、linear, log, band, や time など、ほとんどの scale タイプで使えます。

axis component が、SVG の **g 要素** の selection オブジェクト上で call されることで、axis(軸)が描かれます。axis は原点に描かれますが、その位置を変更したいときは、selection オブジェクトの **transform** 属性を指定します。

```JavaScript:
const gx = svg.append("g")
    .attr("transform", `translate(0,${height - marginBottom})`)
    .call(d3.axisBottom(x));
```


## ２－１．axisTop(scale)
::: note info
**axisTop(scale)**
上辺の水平軸(domain path)を描きます。デフォルトで、その上側にサイズ 6 でパディング 3 の目盛りが描かれます。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/524bc547-1349-2d81-7e70-443bc4fa74e0.png)

## ２－２．axisRight(scale)

::: note info
**axisRight(scale)**
右辺の垂直軸(domain path)を描きます。デフォルトで、その右側にサイズ 6 でパディング 3 の目盛りが描かれます。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/1a4ffe81-1827-35f4-b188-c87182d2577e.png)

## ２－３．axisBottom(scale)

::: note info
**axisBottom(scale)**
底辺の水平軸(domain path)を描きます。デフォルトで、その下側にサイズ 6 でパディング 3 の目盛りが描かれます。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/43b75b82-7476-4f16-e317-faa6cd3eadc3.png)

## ２－４．axisLeft(scale)

::: note info
**axisLeft(scale)**
左辺の垂直軸(domain path)を描きます。デフォルトで、その左側にサイズ 6 でパディング 3 の目盛りが描かれます。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/e24ee598-8d37-8ac4-578f-a464f2c61fd2.png)


## ２－５．axisBottom(scale) のサンプルコード



```JavaScript:
<!DOCTYPE html>
<html lang="en">
  <head></head>
    <meta charset="utf-8">
    <title>test</title>
  </head>
  <body>
    <svg width="800" height="800"></svg>
  </body>
  <script type="module">
    import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";

    // 1. 軸スケールの設定
    const width = 500,
          height = 200,
          margin = 50,
          x = d3.scaleLinear()
              .domain([0, 10])
              .range([margin, width - margin]);

    // 2. 軸の表示
    const gx = d3.select("svg").append("g")
      .attr("transform", `translate(0,${height - margin})`)
      .call(d3.axisBottom(x));
      
  </script>
</html>
```



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/dba3d7b7-a200-7b0e-c2ab-b33320469cf3.png)



# ３．d3-shape
[d3-shape - 公式ドキュメント](https://d3js.org/d3-shape)
ビジュアル化（可視化）は、シンボルや円弧、ライン、エリアなどの離散的なグラフィカル・マークで実現されます。
バーチャートの長方形はシンプルですが、他の形状（shape）、例えば 環状の扇形や Catmull–Rom スプライン曲線などは複雑です。しかし、**d3-shape** モジュールを使えばこれらの様々な形状を簡単に作成できるようになります。



::: note info
**D3 path 要素**
**path 要素** とは、SVG で任意の図形を描画するための要素で、**d 属性** でその形状を指定します。
[「パス - SVG: スケーラブルベクターグラフィック | MDN」](https://developer.mozilla.org/ja/docs/Web/SVG/Tutorial/Paths)
:::


D3 の他の側面と同じように、これらの shape（形状）はデータドリブンです。つまり各 shape ジェネレータはアクセサを利用して、入力データをビジュアル表現にマップする方法をコントロールできます。

例えば、時系列の line ジェネレータを、データをグラフの scale 軸に適合させるように、定義することができます。ここでアクセサの x と y にはそれぞれ、x 軸と y 軸の scale が使われます。


```JavaScript:
const line = d3.line()
    .x((d) => x(d.date))
    .y((d) => y(d.value));
```


この line ジェネレータは SVG path 要素の **d 属性** を計算するのに使われます。

```JavaScript:
path.datum(data).attr("d", line);
```


::: note info
**d3-shape モジュール**
* **Arcs** - circular or annular sectors, as in a pie or donut chart.
* **Areas** - an area defined by a bounding topline and baseline, as in an area chart.
* **Curves** - interpolate between points to produce a continuous shape.
* **Lines** - a spline or polyline, as in a line chart.
* **Links** - a smooth cubic Bézier curve from a source to a target.
* **Pies** - compute angles for a pie or donut chart.
* **Stacks** - stack adjacent shapes, as in a stacked bar chart.
* **Symbols** - a categorical shape encoding, as in a scatterplot.
* **Radial areas** - like area, but in polar coordinates.
* **Radial lines** - like line, but in polar coordinates.
* **Radial links** - like link, but in polar coordinates.
:::



## ３－１．Lines
line ジェネレータで、line チャートの中に、スプラインや折れ線を描くことができます。

::: note warn
**スプライン**
数学やコンピュータグラフィックスで、滑らかな曲線や曲面を表現するための数学的な手法です。
:::


 ### ３－１－１．line(x, y)

::: note info
**line(x, y)**
与えられたアクセサ x と y を使って、line ジェネレータを作り出します。
:::


::: note info
**line.x(x)**
引数の x が指定された場合、x アクセサを引数で指定された関数や数値とし、ジェネレータを返します。
```JavaScript:
const line = d3.line().x((d) => x(d.Date));
```
line が生成される時、x アクセサは入力データ配列の各々の要素に対して適用されます。x アクセサにはデータ要素 d と、配列の index i と　データ配列の 3 引数が渡されます。


引数が指定されていない場合、現在の x アクセサを返します。
```JavaScript:
line.x() // (d) => x(d.Date)
```

デフォルトで以下のように定義されています。
```JavaScript:
function x(d) {
  return d[0];
}
```
:::



::: note info
**line.y(y)**
引数の y が指定された場合、y アクセサを引数で指定された関数や数値とし、ジェネレータを返します。
```JavaScript:
const line = d3.line().y((d) => y(d.Close));
```
line が生成される時、y アクセサは入力データ配列の各々の要素に対して適用されます。y アクセサにはデータ要素 d と、配列の index i と　データ配列の 3 引数が渡されます。



引数が指定されていない場合、現在の y アクセサを返します。
```JavaScript:
line.y() // (d) => y(d.Close)
```

デフォルトで以下のように定義されています。
```JavaScript:
function y(d) {
  return d[1];
}
```
:::


 ### ３－１－２．line(x, y) のサンプルコード
```JavaScript:
<!DOCTYPE html>
<html lang="en">
  <head></head>
    <meta charset="utf-8">
    <title>test</title>
  </head>
  <body>
    <svg> </svg>
  </body>
  <script type="module">
    import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";

    const data =[
        [{x: 0, y: 6},{x: 1, y: 9},{x: 2, y: 6},
        {x: 3, y: 5},{x: 4, y: 2},{x: 6, y: 4},
        {x: 7, y: 2},{x: 8, y: 5},{x: 9, y: 2}]
    ];

    // 1. x,yスケールの設定
    const   width = 600,
            height = 400,
            margin = 50,
            x = d3.scaleLinear()
                .domain([0, 10])
                .range([margin, width - margin]),
            y = d3.scaleLinear()
                .domain([0, 10])
                .range([height - margin, margin]);

    // 2. line ジェネレータの作成
    const line =  d3.line()
                    .x((d) => x(d.x))   // x accessor.
                    .y((d) => y(d.y));  // y accessor.

    // 3. SVGの設定
    const svg = d3.select("svg");
    svg.attr("height", height)
       .attr("width", width);

    // 4. line チャートの描画
    svg.selectAll("path")
        .data(data)
        .enter()
            .append("path")             // path 要素
            .attr("d", (d) => line(d))  // d 属性を line ジェネレータで設定
            .attr('stroke', '#333')     // 線の色
            .attr('fill', 'none');      // 塗りつぶし色
 
    </script>
</html> 
```

もう少し、詳細に見たいと思います。

```JavaScript:
    let selection = svg.selectAll("path")
    console.log(selection)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/3a945346-29ce-eaf1-d968-47a6912b5527.png)

::: note warn
**\_groups : [ [] ]**
:::


```JavaScript:
    let selection = svg.selectAll("path")
        .data(data)
    console.log(selection)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/9c112ad5-6b62-8742-6842-e04936df5bd5.png)


::: note warn
**\_groups : [ [] ]**
**\_enter : [ [m] ]**
※data = [ [p1,p2,...,p9] ] と表すと m の\__data__ = [p1,p2,...,p9]
:::


```JavaScript:
    let selection = svg.selectAll("path")
        .data(data)
        .enter();

    console.log(selection);
```


選択要素 (\_groups) に割り当てられたのは1個の配列（サイズ9）であることに注意してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/034a0864-5c1f-9c96-9265-63e782c117b0.png)

以下のコードで、line(d) の引数 d として \__data__ で示される、1個の配列がそのまま渡されます。
```JavaScript:
.attr("d", (d) => line(d)) 
```



**path 要素** は以下の通りになります。**d 属性** の値が line ジェネレータで生成されたものです。

```
<svg height="400" width="600">
<path d="M50,170L100,80L150,170L200,200L250,290L350,230L400,290L450,200L500,290" 
stroke="#333" fill="none">
</path>
</svg>
```

line グラフ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/fba0bcdd-fd06-6689-6800-0f4acdaa14bd.png)





# ４．d3-scale、d3-axis、d3-shape のサンプルコード


開発環境は テキストエディタまたは Visual Studio でコードを書き、以下の Python 標準の Web サーバを使って動作確認しています。

```
python -m http.server

http://localhost:8000/line.html
```


これまで見てきた d3-scale、d3-axis、d3-shape を全て利用したサンプルコードです。

```JavaScript
<!DOCTYPE html>
<html lang="en">
  <head></head>
    <meta charset="utf-8">
    <title>test</title>
  </head>
  <body>
    <svg> </svg>
  </body>
  <script type="module">
    import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";

    const data =[
        [{x: 0, y: 6},{x: 1, y: 9},{x: 2, y: 6},
        {x: 3, y: 5},{x: 4, y: 2},{x: 6, y: 4},
        {x: 7, y: 2},{x: 8, y: 5},{x: 9, y: 2}]
    ];

    // 1. x,yスケールの設定
    const   width = 600,
            height = 400,
            margin = 30,
            x = d3.scaleLinear()
                .domain([0, 10])
                .range([margin, width - margin]),
            y = d3.scaleLinear()
                .domain([0, 10])
                .range([height - margin, margin]);
                
    // 2. line ジェネレータの作成
    const line =  d3.line()
                    .x((d) => x(d.x))
                    .y((d) => y(d.y));

     // 3. SVGの設定
    const svg = d3.select("svg").attr("height", height).attr("width", width);

    // 4. line チャートの描画
    svg.selectAll("path")
        .data(data)
        .enter()
            .append("path")
            .attr("d", (d) => line(d))
            .attr('stroke', '#333') // 線の色
            .attr('fill', 'none');  // 塗りつぶし色

    renderAxes(svg);

    // 5. Axes の描画
    function renderAxes(svg){
        let xAxis = d3.axisBottom(x.range([0, width-2*margin])); // width - 左右の margin
        let yAxis = d3.axisLeft(y.range([height-2*margin, 0]));  // height - 上下の margin

        const xStart = margin        
        const yStart = height - margin
        const yEnd = margin

        svg.append("g")        
            .attr("class", "axis")
            .attr("transform", () => {
                return "translate(" + xStart
                    + "," + yStart + ")";
                })
            .call(xAxis);

        svg.append("g")        
            .attr("class", "axis")
            .attr("transform", () => {
                return "translate(" + xStart
                    + "," + yEnd + ")";
                })
            .call(yAxis);
}
    </script>
</html> 
 
```



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/afb28d27-92ea-640f-0da5-a16928d8ec24.png)


今回は以上です。
 ///