TypeScriptを用いたデータ可視化におすすめのライブラリとその使い方

# 概要
データ可視化に使えるJSライブラリはたくさんありますが、TypeScriptでの導入方法に関する情報がまとまったサイトがないので書きました。

# 紹介するライブラリ一覧
| ライブラリ | 公式HP | 概要 |
| --- | --- | --- |
| D3.js | https://d3js.org/ | 言わずと知られた情報可視化ライブラリです。さまざまなチャートを高い自由度で作成できます。まずはギャラリー (https://d3-graph-gallery.com/index.html) をみてみて下さい。|
| Three.js | https://threejs.org/ | 3D表現をできるライブラリです。|
| day.js | https://day.js.org/ | 時刻データを扱うことに特化したライブラリです。JavascriptのDate型よりも遥かに扱いやすく重宝します。|
| chroma.js | https://gka.github.io/chroma.js/ | 色を扱うことに特化したライブラリです。色はデータ可視化において重要な要素ですので必須なライブラリです。|

# D3.js
言わずと知られた情報可視化ライブラリです。さまざまなチャートを高い自由度で作成できます。まずはギャラリー (https://d3-graph-gallery.com/index.html) をみてみて下さい。

## インストール
```sh
npm i -S @types/d3 d3
```
## サンプル
![スクリーンショット 2023-01-19 18.35.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2902914/e8000799-c317-9ab2-0b2f-9456db58849d.png)
データの出展：https://www.mhlw.go.jp/stf/covid-19/open-data.html

### main.ts
```ts
import * as d3 from 'd3';

window.addEventListener('load', () => {
    init();
}, false);

const width: number = 600;
const height: number = 400;
const margin = {
    top : 50, right: 50, bottom: 100, left: 50
};

function init(): void {
    // データの読み込み
    d3.csv('data.csv')
        .then((data) => {
            draw(data);
        })
        .catch((error) => {
            console.log(error)
        });
}


function draw(data: d3.DSVRowArray): void {

    let chartWidth: number = width - margin.left - margin.right;
    let chartHeight: number = height - margin.top - margin.bottom;

    // SVGの設定
    const svg: any = d3.select('body')
        .append('svg')
        .attr('width', width)
        .attr('height', height);

    console.log(svg);

    // x axis
    let xScale: any = d3.scaleTime()
        .domain(<Date[]>d3.extent(data, (d: d3.DSVRowString) => new Date(String(d.Date)) ))
        .range([0, chartWidth]);
    svg.append('g')
        .attr('transform', `translate(${margin.left}, ${Number(margin.top + chartHeight)})`)
        .call(d3.axisBottom(xScale).tickFormat(<any>d3.timeFormat('%Y/%m/%d')) )
        .selectAll('text')
        .attr('transform', 'translate(25, 35)rotate(60)');


    // y axis
    let yScale: any = d3.scaleLinear()
        .domain([ 0, Number(d3.max(data, (d: d3.DSVRowString) => +Number(d.ALL) )) ])
        .range([chartHeight, 0]);
    svg.append('g')
        .attr('transform', `translate(${margin.left}, ${margin.top})`)
        .call(d3.axisLeft(yScale));


    // 折線
    const line: any = d3.line()
        .x((d: any) => xScale(new Date(String(d.Date))))
        .y((d: any) => yScale(Number(d.ALL)));

    // 描画
    svg.append('path')
        .datum(data)
        .attr('transform', `translate(${margin.left}, ${margin.top})`)
        .attr('fill', 'none')
        .attr('stroke', 'steelblue')
        .attr('stroke-width', 1.5)
        .attr('d', line);
}
```
```html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="main.js"></script>
    <title>Line Chart</title>
</head>
<body>
</body>
</html>
```

# three.js
3D表現をできるライブラリです。

## インストール
```sh
npm i -S three @types/three
```
## サンプル
![スクリーンショット 2023-01-20 11.30.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2902914/c7b664cc-83c9-3bbf-c284-0b0958792ad3.png)

### main.ts
```ts
import * as THREE from "three";

window.addEventListener('load', () => {
    init();
}, false);


let cvsWidth: number = 600;
let cvsHeight: number = 600;
let renderer: THREE.WebGLRenderer = new THREE.WebGLRenderer({ alpha: true });
let scene: THREE.Scene = new THREE.Scene();
let camera: THREE.PerspectiveCamera = new THREE.PerspectiveCamera(50, cvsWidth / cvsHeight, 1, 2000);
let cube: THREE.Mesh;

function init() {
    let elem = <HTMLElement>document.querySelector('body');
    elem.append(renderer.domElement);
    camera.position.set(50, 50, 50);
    camera.lookAt(new THREE.Vector3(0, 0, 0));
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(cvsWidth, cvsHeight);
    camera.aspect = cvsWidth / cvsHeight;
    camera.updateProjectionMatrix();

    const light = new THREE.DirectionalLight(0xffffff);
    light.position.set(1, 1, 1);
    scene.add(light);

    const axes = new THREE.AxesHelper(10);
    scene.add(axes);

    draw();
    render();
}

function render() {
    requestAnimationFrame(render);
    cube.rotation.x += 0.01;
    cube.rotation.y += 0.01;
    renderer.render(scene, camera);
}

function draw() {
    const geometry = new THREE.BoxGeometry(10, 10, 10);
    const material = new THREE.MeshLambertMaterial({
        color: new THREE.Color('#4da9ff'),
        transparent: true,
        opacity: 0.5
    });
    cube = new THREE.Mesh(geometry, material);
    scene.add(cube);
}
```
### index.html
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <script src="main.js"></script>
    <style>
        body {
            margin: 0;
            padding: 0;
            background-color: #222;
        }
    </style>
    <title>Document</title>
</head>
<body>
</body>
</html>
```

# day.js
時刻データを扱うことに特化したライブラリです。JavascriptのDate型よりも遥かに扱いやすく重宝します。

## インストール
```shell
npm i -S dayjs
```

tsconfig.jsonに`"esModuleInterop": true,`を追加する
```js
{
    "compilerOptions": {
        // ...
        "esModuleInterop": true,
        // ...
    }    
}
```
## サンプル
![スクリーンショット 2023-01-20 13.38.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2902914/c22d1933-220a-bbc8-14d0-8e2c54be8e04.png)

### main.ts
```ts
import dayjs, { Dayjs } from 'dayjs';
// UTCを使うためのおまじない
import utc from "dayjs/plugin/utc";
dayjs.extend(utc);
// タイムゾーンを使うためのおまじない
import timezone from "dayjs/plugin/timezone";
dayjs.extend(timezone);

window.addEventListener('load', () => {
    init();
}, false);

function init(): void {
    let time = '2020/06/01 12:13:42';

    let utc0 = dayjs.tz(time, 'UTC');
    let cet0 = dayjs.tz(time, 'CET');
    let jst0 = dayjs.tz(time, 'Asia/Tokyo');
    let utc0Millis = utc0.valueOf();
    let cet0Millis = cet0.valueOf();
    let jst0Millis = jst0.valueOf();
    let utc0Str = utc0.format('YYYY/MM/DD HH:MM:ss');
    let cet0Str = cet0.format('YYYY/MM/DD HH:MM:ss');
    let jst0Str = jst0.format('YYYY/MM/DD HH:MM:ss');
   
    let utc1 = dayjs(time).tz('UTC');
    let cet1 = dayjs(time).tz('CET');
    let jst1 = dayjs(time).tz('Asia/Tokyo');
    let utc1Millis = utc1.valueOf();
    let cet1Millis = cet1.valueOf();
    let jst1Millis = jst1.valueOf();
    let utc1Str = utc1.format('YYYY/MM/DD HH:MM:ss');
    let cet1Str = cet1.format('YYYY/MM/DD HH:MM:ss');
    let jst1Str = jst1.format('YYYY/MM/DD HH:MM:ss');

    console.log('dayjsオブジェクトを生成する際1タイムゾーンを指定する場合')
    console.table([
        ['Time Zone', 'UNIX時間(msec)', '日付'],
        ['UTC', utc0Millis, utc0Str],
        ['CET', cet0Millis, cet0Str],
        ['JST', jst0Millis, jst0Str],
    ]);

    console.log('dayjsオブジェクトを生成したのちに、タイムゾーンを指定する場合')
    console.table([
        ['Time Zone', 'UNIX時間(msec)', '日付'],
        ['UTC', utc1Millis, utc1Str],
        ['CET', cet1Millis, cet1Str],
        ['JST', jst1Millis, jst1Str],
    ]);
}
```


# chroma.js

## インストール
```sh
npm i -S chroma-js @types/chroma-js
```
## サンプル
![スクリーンショット 2023-01-20 17.06.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2902914/c3b72b78-b836-783c-92c7-b1bb1f491624.png)

### main.ts
```ts
import chroma from 'chroma-js';

window.addEventListener('load', () => {
    init();
}, false);

function init(): void {
    let elem: HTMLElement = <HTMLElement>document.querySelector('body');

    let colorList: { [key: string]: chroma.Color } = {
        'chroma.rgb(235, 64, 52)': chroma.rgb(235, 64, 52),
        'chroma.rgb(235, 64, 52).alpha(0.5)': chroma.rgb(235, 64, 52).alpha(0.5),
        'chroma.hsv(229, 0.78, 0.92)': chroma.hsv(229, 0.78, 0.92),
        'chroma.hsv(229, 0.78, 0.92).darken()': chroma.hsv(229, 0.78, 0.92).darken(),
        'chroma.hsv(229, 0.78, 0.92).brighten()': chroma.hsv(229, 0.78, 0.92).brighten(),
        'chroma.hsv(229, 0.78, 0.92).saturate()': chroma.hsv(229, 0.78, 0.92).saturate(),
        'chroma.hsv(229, 0.78, 0.92).desaturate()': chroma.hsv(229, 0.78, 0.92).desaturate(),
    };


    Object.keys(colorList).forEach(key => {
        let color: chroma.Color = colorList[key];
        let colorElem = document.createElement('span');
        colorElem.className = 'colorPanel';
        colorElem.style.backgroundColor = color.css();
        colorElem.innerText = color.hex();

        let labelElem = document.createElement('span');
        labelElem.innerText = key;

        let div = document.createElement('div');
        div.append(colorElem);
        div.append(labelElem);
        elem.append(div);
    });
}
```
### index.html
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="main.js"></script>
    <title>Document</title>
    <style>
        .colorPanel {
            display: inline-block;
            width: 80px;
            height: 20px;
            margin: 10px;
            padding: 3px 10px 6px 10px;
            text-align: center;
            border-radius: 5px;
            color: #FFF;
        }
    </style>
</head>
<body>
</body>
</html>
```