[C3.js][TypeScript] Draw line charts 1
Tags: typescript, c3js
Published: 2021-11-14 17:46:03

## Intro
This time, I will try drawing line charts with specific design.

I have tried drawing line charts with Chart.js.

* [【TypeScript】Try Chart.js](https://dev.to/masanori_msl/typescript-try-chart-js-23kj)
* [[TypeScript][Chart.js] Play my own voice 3](https://dev.to/masanori_msl/typescript-chart-js-play-my-own-voice-3-2l6c)

But because I couldn't find how to draw additional lines with specific format like dashed lines.
So I will try C3.js in this time.

* [C3.js | D3-based reusable chart library](https://c3js.org/)

### Environments

* Node.js ver.17.0.1
* TypeScript ver.4.4.4
* C3 ver.0.7.20
* PostCSS ver.8.3.11
* Webpack ver.5.61.0
* @types/c3 ver.0.7.6
* postcss-cli ver.9.0.2
* postcss-import ver.14.0.2
* ts-loader ver.9.2.6
* ts-node ver.10.4.0
* webpack-cli ver.4.9.1

## Draw line charts
First, I will try drawing line charts with sample data.

#### index.html
```html
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>chart sample</title>
        <meta charset="utf-8">
        <link href="css/chart.page.css" rel="stylesheet" />
    </head>
    <body>
        <div id="chart_root"></div>
        <script src="./js/main.page.js"></script>
        <script>Page.init();</script>
    </body>
</html>
```

#### chart.page.css
```css
@import url("../../node_modules/c3/c3.min.css");
```

I must load the CSS file of C3.
Because I don't want to copy it by myself, I use "postcss-import" to import it.

#### chart.type.ts
```ts
export enum LineType {
    default = 0,
    dashedLine,
};
export type ChartValues = {
    type: LineType,
    values: readonly SampleValue[],
};
export type SampleValue = {
    x: number,
    y: number,
};
```

#### main.page.ts
```ts
import { LineType } from "./charts/chart.type";
import { MainPageView } from "./main.page.view";

let view: MainPageView;
export function init(): void {
    view = new MainPageView();
    view.updateValues([{
        type: LineType.default,
        values: [{ x: 0.1, y: 0 },
            { x: 1, y: 1.2 },
            { x: 3.2, y: 2.5 },
            { x: 5.6, y: 6.7 },
            { x: 7, y: 7.8 },
            { x: 8.0 , y: 9 }]
    }]);
}
```

#### main.page.view.ts
```ts
import { ChartValues } from "./charts/chart.type";
import { ChartViewer } from "./charts/chartViewer";

export class MainPageView {
    private chartRoot: HTMLElement;
    private charts: ChartViewer[] = [];
    public constructor() {
        this.chartRoot = document.getElementById("chart_root") as HTMLElement;
    }
    public updateValues(values: readonly ChartValues[]): void {
        const sampleChart = new ChartViewer(this.chartRoot);
        sampleChart.draw(values[0]!);
        this.charts.push(sampleChart);        
    }
}
```

#### chartViewer.ts
```ts
import c3 from "c3";
import { ChartValues } from "./chart.type";

export class ChartViewer {
    private chartElement: HTMLElement;
    public constructor(root: HTMLElement) {
        this.chartElement = document.createElement("div");
        root.appendChild(this.chartElement);
    }
    public draw(value: ChartValues): void {
        const chart = c3.generate({
            bindto: this.chartElement,
            data: {
                columns: [
                    ["data1", ...value.values.map(v => v.y)]
                ]},
        });
    }
}
```

#### Result
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kdarzt3w6yylpbuj824o.png)

## Change ticks for x direction
The x ticks of the sample are index numbers.

So I want to change them as x values of "SampleValue".

#### chartViewer.ts
```ts
...
export class ChartViewer {
...
    public draw(value: ChartValues): void {

        const chart = c3.generate({
            bindto: this.chartElement,
            data: {
                x: "x",
                columns: [
                    ["data1", ...value.values.map(v => v.y)],
                    ["x", ...value.values.map(v => v.x)],
            ]},
            axis: {
                x: {
                    tick: {
                        values: [...this.getTicksX(0, 10)],
                    }
                }
            }
        });
    }
    private getTicksX(from: number, to: number): readonly number[] {
        const results: number[] = [];
        for(let i = from; i <= to; i++) {
            results.push(i);
        }
        return results;
    }
}
```

#### Result
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1hc09hs78f3iqqfda7tj.png)

#### Min, Max

The ticks are drawn only up to the values.
To draw in the specified range, I add min and max.

#### chartViewer.ts
```ts
...
    public draw(value: ChartValues): void {

        const chart = c3.generate({
            bindto: this.chartElement,
            data: {
                x: "x",
                columns: [
                    ["data1", ...value.values.map(v => v.y)],
                    ["x", ...value.values.map(v => v.x)],
            ]},
            axis: {
                x: {
                    min: 0,
                    max: 10,
                    tick: {                        
                        values: [...this.getTicksX(0, 10)],
                    }
                }
            }
        });
    }
...
```

#### Result
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m9taz8ibja64q0mcg9wr.png)
## Grid

I can show grid like below.

#### chartViewer.ts
```ts
...
    public draw(value: ChartValues): void {
        const chart = c3.generate({
...
            grid: {
                x: {
                    show: true,
                },
            },
        });
    }
...
```

I want to add auxiliary lines and set specific designs.

#### chart.page.css
```css
@import url("../../node_modules/c3/c3.min.css");

.solid_line line {
    stroke: #000000;
    stroke-dasharray: 1 0;
    stroke-linecap: round;
}
.dashed_line line {
    stroke: #9f9f9f;
    stroke-dasharray: 2 5;
    stroke-linecap: round;
}
```

#### chartViewer.ts
```ts
...
export class ChartViewer {
...
    public draw(value: ChartValues): void {
        const valueXList = this.getValueX(0, 10);
        const ticksX = this.getTicksX(valueXList);
        const gridLines = valueXList.map(t => this.generateGridLine(t));

        const chart = c3.generate({
            bindto: this.chartElement,
            data: {
                x: "x",
                columns: [
                    ["x", ...value.values.map(v => v.x)],
                    ["data1", ...value.values.map(v => v.y)],
                ],
                types: {
                    data1: "line"
                },
            },
            axis: {
                x: {
                    min: 0,
                    max: 10,
                    tick: {
                        values: [...ticksX],
                        outer: true,
                    }
                }
            },
            grid: {
                x: {
                    show: false,
                    lines: [...gridLines]
                },
                y: {
                    show: true,
                }
            },
            interaction: {
                enabled: false,
            }
        });
        console.log(`${chart}`);
    }
    private getValueX(from: number, to: number): readonly number[] {
        const results: number[] = [];
        for(let i = from; i <= to; i++) {
            if(i < to) {
                for(let j = 0.0; j < 1.0; j += 0.1) {
                    results.push(i + j);
                }
            }
        }
        return results;
    }
    private getTicksX(values: readonly number[]): readonly string[] {
        const results: string[] = [];
        for(const v of values) {
            if(v === (Math.trunc(v))) {
                results.push(v.toString());
            } else {
                results.push("");
            }    
        }
        return results;
    }
    private generateGridLine(value: number): { value: string, class: string } {
        let lineClass = "";
        if(value === (Math.trunc(value))) {
            lineClass = "solid_line";
        } else {
            lineClass = "dashed_line";
        }
        return {
            value: value.toString(),
            class: lineClass,
        };
    }
}
```

#### Result
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u9rx2a7roi2vn6nj6b88.png)