DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 966,155 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Masui Masanori
Masui Masanori

Posted on

[C3.js][TypeScript] Draw line charts 1

Intro

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

I have tried drawing line charts with Chart.js.

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.

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

<!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>
Enter fullscreen mode Exit fullscreen mode

chart.page.css

@import url("../../node_modules/c3/c3.min.css");
Enter fullscreen mode Exit fullscreen mode

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

export enum LineType {
    default = 0,
    dashedLine,
};
export type ChartValues = {
    type: LineType,
    values: readonly SampleValue[],
};
export type SampleValue = {
    x: number,
    y: number,
};
Enter fullscreen mode Exit fullscreen mode

main.page.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 }]
    }]);
}
Enter fullscreen mode Exit fullscreen mode

main.page.view.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);        
    }
}
Enter fullscreen mode Exit fullscreen mode

chartViewer.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)]
                ]},
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

Result

Image description

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

...
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;
    }
}
Enter fullscreen mode Exit fullscreen mode

Result

Image description

Min, Max

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

chartViewer.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)],
                    }
                }
            }
        });
    }
...
Enter fullscreen mode Exit fullscreen mode

Result

Image description

Grid

I can show grid like below.

chartViewer.ts

...
    public draw(value: ChartValues): void {
        const chart = c3.generate({
...
            grid: {
                x: {
                    show: true,
                },
            },
        });
    }
...
Enter fullscreen mode Exit fullscreen mode

I want to add auxiliary lines and set specific designs.

chart.page.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;
}
Enter fullscreen mode Exit fullscreen mode

chartViewer.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,
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

Result

Image description

Top comments (0)

🌚 Life is too short to browse without dark mode