DEV Community

Masui Masanori
Masui Masanori

Posted on

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

Intro

This time, I will try saving the line charts as images.

Offset

The chart what I created last time, the positions of 0 weren't at start point.
Image description

To set offset values as 0, add "padding" into "axis".

chartViewer.ts

import c3 from "c3";
import { ChartValues } from "./chart.type";

export class ChartViewer {
    private chartElement: HTMLElement;
    private chart: c3.ChartAPI|null = null;
...
    public draw(value: ChartValues): void {
        const valueXList = this.getValueX(0, 10);
        const ticksX = this.getTicks(valueXList);
        const valueYList = value.values.map(v => v.y);
        const gridLineY = valueYList.map(t => this.generateGridLine(t));
        const gridLines = valueXList.map(t => this.generateGridLine(t));

        this.chart = c3.generate({
            bindto: this.chartElement,
            data: {
                x: "x",
                columns: [
                    ["data1", ...value.values.map(v => v.y)],
                    ["x", ...value.values.map(v => v.x)],
                ],
                types: {
                    data1: "line"
                },
            },
            axis: {
                x: {
                    min: 0,
                    max: 10,
                    tick: {
                        values: [...ticksX],
                        outer: false,
                    },
                    padding: { left: 0, }
                },
                y: {
                    min: 0,
                    padding: { bottom: 0, }
                }
            },
            grid: {
                x: {
                    show: false,
                    lines: [...gridLines],
                },
                y: {
                    show: false,
                    lines: [...gridLineY],
                }
            },
            interaction: {
                enabled: false,
            },
        });
    }
...
}
Enter fullscreen mode Exit fullscreen mode

Result

Image description

Save as an image

I can save the chart as an image.

chartViewer.ts

...
    public saveImage(): void {
        const svg = this.getSvgRoot();
        if(svg == null) {
            console.error("svg was null");            
            return;
        }
        const serializedImage = new XMLSerializer().serializeToString(svg);
        const image = new Image();
        image.onload = () => {
            const canvas = document.createElement("canvas");
            canvas.width = this.chartElement.clientWidth;
            canvas.height = this.chartElement.clientHeight;
            const ctx = canvas.getContext("2d");
            if(ctx == null) {
                console.error("ctx was null");
                return;
            }
            ctx.drawImage(image, 0, 0);
            document.body.appendChild(canvas);
        };
        image.src = "data:image/svg+xml;charset=utf-8;base64," + window.btoa(serializedImage);
    }
...
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);
        // To save images, I must wait for drawing the chart.
        setTimeout(() => {
            sampleChart.saveImage();
        }, 100);    
    }
}
Enter fullscreen mode Exit fullscreen mode

Result

Image description

Set CSS values

One problem is that the saved image doesn't reflect the CSS.
So the line what had been drawn in the image was filled and the grid lines weren't drawn.

To draw them, I have to set styles by TypeScript(JavaScript).

chartViewer.ts

...
    public saveImage(): void {
        const svg = this.getSvgRoot();
        if(svg == null) {
            console.error("svg was null");            
            return;
        }
        const chartPaths = this.getElementsBySelector(svg, ".c3-chart path");
        const axisPaths = this.getElementsBySelector(svg, ".c3-axis path");
        const lines = this.getElementsBySelector(svg, ".c3 line");

        const gridLines = Array.from(axisPaths).concat(Array.from(lines));
        chartPaths.forEach((element) => {
            this.setCssValue(element);
        });
        gridLines.forEach((element) => {
            this.setCssValue(element, "green");
        });

        const serializedImage = new XMLSerializer().serializeToString(svg);
        const image = new Image();
        image.onload = () => {            
...
        };
        image.src = "data:image/svg+xml;charset=utf-8;base64," + window.btoa(serializedImage);
    }
...
    private getElementsBySelector(element: SVGElement, selector: string): readonly CSSStyleDeclaration[] {
        const results: CSSStyleDeclaration[] = [];
        const nodes = element.querySelectorAll(selector);
        for(let i = 0; i < nodes.length; i++) {
            const node = nodes[i];
            // Force getting "style" attribute.
            if(node != null &&
                    (node as any)["style"] != null) {
                results.push((node as any)["style"]);
            }
        }
        return results;
    }
    private setCssValue(style: CSSStyleDeclaration, stroke?: string): void {
        style.fill = "none";
        if(stroke != null) {
            style.stroke = stroke;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Result

Image description

Because I haven't set CSS values yet, next time I will try do that.

Top comments (0)