[C3.js][TypeScript] Draw line charts 3
Masui Masanori
Posted on February 12, 2022
Intro
In this time, I will try adding multiple line data, format grid lines, and etc.
After creating charts, I will save them as images.
- [WPF][WebView2] Save line charts as images
- [C3.js][TypeScript] Draw line charts 1
- [C3.js][TypeScript] Draw line charts 2
- [C3.js][TypeScript] Save C3.js charts as images
Use real numbers as scale span
I tried to create the value of the scale of the grid.
But when I used real numbers, some values would become unexpected.
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;
}
Result
So I adjust them first.
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;
}
Result
Samples
chart.type.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[],
};
main.page.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,
};
}
Add multiple line data
chartViewer.ts
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;
}
...
}
Changing lines styles
I want to change colors, widths, styles of lines.
I can set colors from "c3.Data".
chartViewer.ts
...
/** 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,
};
}
...
But I can't find setting widths and styles.
So I set by myself.
chartViewer.ts
...
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;
}
}
}
}
}
Grid lines styles
Z-Index
By default, the grid lines are drawn in front of lines.
I can change the orders.
...
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);
}
...
Result
Result
💖 💪 🙅 🚩
Masui Masanori
Posted on February 12, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.