[ C 3 . js ][ typescript ]線グラフ3を描画します



イントロ
今回は、複数のラインデータ、グリッド線などを追加してみます.
グラフを作成した後、私はイメージとして保存します.





  • スケールスパンとして実数を使用する
    私はグリッドのスケールの値を作成しようとしました.
    しかし、実数を使用すると、予期しない値になるでしょう.
        private getValueList(range: { min: number, max: number},
                scaleSpan: { main: number, auxiliary: number }): readonly number[] {
            const results: number[] = [];
            // min: -2, max: 10, auxiliary: 0.1
            for(let i = range.min; i <= range.max; i += scaleSpan.auxiliary) {
                console.log(`value: ${i}`);
                results.push(i);
            }
            return results;
        }
    

    結果

    だから最初に調整します.
        private getValueList(range: ChartRange,
                scaleSpan: ScaleSpan): readonly number[] {
            const results: number[] = [];
            const adjustedAuxiliary = scaleSpan.auxiliary * scaleSpan.adjuster;
            // min: -2, max: 10, auxiliary: 0.1, adjuster: 10
            for(let i = range.min * scaleSpan.adjuster; i <= range.max * scaleSpan.adjuster; i += adjustedAuxiliary) {
                console.log(`value: ${(i / scaleSpan.adjuster)}`);
                results.push(i / scaleSpan.adjuster);
            }
            return results;
        }
    

    結果


    サンプル

    チャート.種類TS
    export enum LineType {
        default = 0,
        dashedLine,
    }
    export type ImageSize = {
        width: number,
        height: number,
    };
    export type ChartRange = {
        min: number,
        max: number
    };
    export type ScaleSpan = {
        main: number,
        auxiliary: number,
        adjuster: number
    };
    export type ChartValue = {
        type: LineType,
        color: string,
        values: readonly Point[],
    }
    export type Point = {
        x: number,
        y: number,
    };
    export type ChartValues = {
        size: ImageSize,
        fullSize: ImageSize,
        dpi: number,
        xRange: ChartRange,
        yRange: ChartRange,
        xScaleSpan: ScaleSpan,
        yScaleSpan: ScaleSpan,
        charts: readonly ChartValue[],
    };
    

    メイン.ページ.TS
    import { ChartValues, LineType } from "./charts/chart.type";
    import { ChartViewer } from "./charts/chartViewer";
    
    export function generate(): void {
        const chartRoot = document.getElementById("chart_root") as HTMLElement;
        const view = new ChartViewer(chartRoot);
        view.draw(generateSampleData());
        setTimeout(() => {
            view.saveImage();
        }, 100);
    }
    function generateSampleData(): ChartValues {
        const charts = [
            {
                type: LineType.default,
                color: "#ff0000",
                values: [{ x: 0.2, 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 }],
            },
            {
                type: LineType.dashedLine,
                color: "#00ff00",
                values: [{ x: 1.2, y: 10 },
                    { x: 4.1, y: 5.2 },
                    { x: 5.3, y: 6.5 },
                    { x: 6.6, y: -1.7 },
                    { x: 7.5, y: 1.8 },
                    { x: 7.8 , y: 4 }]
            }];
        return {
            size: { width: 1200, height: 800 },
            fullSize: { width: 1400, height: 800 },
            dpi: 600,
            xRange: { min: -2, max: 10 },
            yRange: { min: 0, max: 10 },
            xScaleSpan: { main: 1, auxiliary: 0.2, adjuster: 10 },
            yScaleSpan: { main: 1, auxiliary: 0.2, adjuster: 10 },
            charts,
        };
    }
    

    複数行データの追加

    chartviewerTS
    import c3, { ChartType } from "c3";
    import { ChartRange, ChartValues, ScaleSpan } from "./chart.type";
    
    export class ChartViewer {
        private chartElement: HTMLElement;
    
        public constructor(root: HTMLElement) {
            this.chartElement = document.createElement("div");
            root.appendChild(this.chartElement);
        }
        public draw(values: ChartValues): void {
            const valueXList = this.getValueList(values.xRange, values.xScaleSpan);
            const valueYList = this.getValueList(values.yRange, values.yScaleSpan);
            const data = this.generateChartData(values);
            if(data == null) {
                return;
            }        
            c3.generate({
                bindto: this.chartElement,
                data,
                axis: {
                    x: {
                        min: values.xRange.min,
                        max: values.xRange.max,
                        tick: {
                            values: this.getTicks(valueXList, values.xRange, values.xScaleSpan),
                            outer: false,
                        },
                    },
                    y: {
                        show: true,
                        min: values.yRange.min,
                        max: values.yRange.max,
                        tick: {
                            values: this.getTicks(valueYList, values.yRange, values.yScaleSpan),
                            outer: false,
                        },
                    },
                },
                grid: {
                    x: {
                        show: false,
                        lines: this.generateGridLines(valueXList, values.xRange, values.xScaleSpan),
                    },
                    y: {
                        show: false,
                        lines: this.generateGridLines(valueYList, values.yRange, values.yScaleSpan),
                    }
                },
                interaction: {
                    enabled: true,
                },
                size: values.size,
            });
        }
        public saveImage(): void {
    ...
        }
        /** generate data for "c3.generate"  */
        private generateChartData(values: ChartValues): c3.Data|null {
            if(values.charts.length <= 0) {
                return null;
            }
            /* if all x values of every chart data are same, you can use only one "x" value */
            const xs: { [key: string]: string } = {};
            const columns: [string, ...c3.Primitive[]][] = [];
            const types: { [key: string]: ChartType } = {};
            for(let i = 0; i < values.charts.length; i++) {
                const chartValues = values.charts[i]?.values;
                if(chartValues == null ||
                    chartValues.length <= 0) {
                    continue;
                }
                xs[`data${i + 1}`] = `x${i + 1}`;
                columns.push([`data${i + 1}`, ...chartValues.map(v => v.y)]);
                columns.push([`x${i + 1}`, ...chartValues.map(v => v.x)]);
                types[`data${i + 1}`] = "line";
            }
            return {
                xs,
                columns,
                types,
            };
        }
        private getValueList(range: ChartRange,
                scaleSpan: ScaleSpan): number[] {
            const results: number[] = [];
            const adjustedAuxiliary = scaleSpan.auxiliary * scaleSpan.adjuster;
            for(let i = range.min * scaleSpan.adjuster; i <= range.max * scaleSpan.adjuster; i += adjustedAuxiliary) {
                results.push(i / scaleSpan.adjuster);
            }
            return results;
        }
        private getTicks(values: readonly number[],
            range: ChartRange,
            scaleSpan: ScaleSpan): string[] {
            const results: string[] = [];
            let count = range.min;
            for(const v of values) {
                if(v === (scaleSpan.main * count)) {
                    results.push(v.toString());            
                    count += 1;
                }
            }
            return results;
        }
        private generateGridLines(values: readonly number[],
            range: ChartRange,
            scaleSpan: ScaleSpan): { value: string, class: string }[] {
                const results = new Array<{ value: string, class: string }>();
                let count = range.min;
            for(const v of values) {
                if(v === (scaleSpan.main * count)) {
                    results.push({
                        value: v.toString(),
                        class: "solid_line",
                    });        
                    count += 1;
                } else {
                    results.push({
                        value: v.toString(),
                        class: "dashed_line",
                    });
                }
            }
            return results;
        }
    ...
    }
    
  • Multiple XY Line Chart - C3.js | D3-based reusable chart library

  • 変更線スタイル
    色、幅、線のスタイルを変えたいです.
    “C 3 . data”から色を設定できます.

    chartviewerTS
    ...
        /** generate data for "c3.generate"  */
        private generateChartData(values: ChartValues): c3.Data|null {
            if(values.charts.length <= 0) {
                return null;
            }
            const xs: { [key: string]: string } = {};
            const columns: [string, ...c3.Primitive[]][] = [];
            const types: { [key: string]: ChartType } = {};
            const colors: { [key: string]: string } = {};
            for(let i = 0; i < values.charts.length; i++) {
                const chartValues = values.charts[i]?.values;
                if(chartValues == null ||
                    chartValues.length <= 0) {
                    continue;
                }
                xs[`data${i + 1}`] = `x${i + 1}`;
                columns.push([`data${i + 1}`, ...chartValues.map(v => v.y)]);
                columns.push([`x${i + 1}`, ...chartValues.map(v => v.x)]);
                types[`data${i + 1}`] = "line";
                colors[`data${i + 1}`] = values.charts[i]?.color ?? "#000000";
            }
            return {
                xs,
                columns,
                types,
                colors,
            };
        }
    ...
    
  • Data Color - C3.js | D3-based reusable chart library
  • しかし、私は設定幅とスタイルを見つけることができません.
    それで、私は一人でセットしました.

    chartviewerTS
    ...
        public draw(values: ChartValues): void {
    ...     
            c3.generate({
    ...
            });
            this.setLineStyles(values);
        }
    ...
        private setLineStyles(values: ChartValues): void {
            if(values.charts.length <= 0) {
                return;
            }
            for(let i = 0; i < values.charts.length; i++) {
                // the line elements have "c3-line-{data name}" class. 
                const solidLines = this.chartElement.getElementsByClassName(`c3-line-data${i + 1}`);
                const chartValue = values.charts[i];
                if(chartValue == null) {
                    continue;
                }
                for(let i = 0; i < solidLines.length; i++) {
                    const path = solidLines[i];
                    if(this.hasStyle(path)){
                        path.style.fill = "none";
                        path.style.strokeWidth = "3px";
                        path.style.strokeLinecap = "round";
                        switch(chartValue.type) {
                            case LineType.dashedLine:
                                path.style.strokeDasharray = "2 5";
                                break;
                            default:
                                path.style.strokeDasharray = "1 0";
                                break;
                        }                    
                    }
                }
            }
        }
    

    グリッドラインスタイル

    Z指数
    デフォルトでは、グリッド線は行の前に描画されます.

    注文を変更できます.
    ...
        public draw(values: ChartValues): void {
    ...  
            c3.generate({
                bindto: this.chartElement,
                data,
                axis: {
    ...
                },
                grid: {
                    x: {
                        show: false,
                        lines: this.generateGridLines(valueXList, values.xRange, values.xScaleSpan),
                    },
                    y: {
                        show: false,
                        lines: this.generateGridLines(valueYList, values.yRange, values.yScaleSpan),
                    },
                    lines: {
                        front: false,
                    },
                },
                point: {
                    show: false,
                },
                legend: {
                    show: false,
                },
                interaction: {
                    enabled: true,
                },
                size: values.size,
            });
            this.setLineStyles(values);
        }
    ...
    

    結果


    結果