Custom chart creation with canvas and Angular
Today, I'd like to show you how you can create a reusable chart using canvas HTML element and Angular.
We are going to develop the following component :
As you can see we have two series of data.
The component will be used as follow:
Let's first create the service to set up the canvas 2D context and draw our chart.
We can easily add more functionality to our component, but for demonstration purpose it's enough.
We are going to develop the following component :
As you can see we have two series of data.
The component will be used as follow:
<custom-chart [data]="datasets"></custom-chart>
Let's first create the service to set up the canvas 2D context and draw our chart.
Chart Service
We are going to create a service to set up our environment and draw on the canvas.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {Injectable, ElementRef} from '@angular/core'; | |
export class GroupData { | |
label: string; | |
left: number; | |
right: number; | |
} | |
export class ComparisonData { | |
labels: string[]; | |
datasets: GroupData[]; | |
} | |
@Injectable() | |
export class ChartService { | |
constructor() {} | |
drawBar(ctx: CanvasRenderingContext2D, x: number, y: number, | |
color: string, width: number, reverse: boolean) { | |
let labelX: number; | |
let barX: number; | |
if (reverse) { | |
barX = 100 - width + 40; | |
labelX = barX - 40; | |
} else { | |
barX = 0; | |
labelX = width + 15; | |
} | |
ctx.save(); | |
ctx.translate(x, y); | |
ctx.fillStyle = color; | |
ctx.font = "normal 12px Arial"; | |
ctx.fillText(`${width} %`, labelX, 24); | |
ctx.fillRect(barX, 0, width, 40); | |
ctx.restore(); | |
} | |
drawComparisonChart(canvasRef: ElementRef, data: ComparisonData) { | |
//Serie A Total | |
let leftTotalValue = 0; | |
//Serie B Total | |
let rightTotalValue = 0; | |
//Canvas width | |
let canvasWidth: number = 300; | |
//Canvas height | |
let canvasHeight: number = 0; | |
//bar height | |
let height: number = 40; | |
//margin between bar | |
let margin: number = 25; | |
//Set totals and canvas height | |
data.datasets.forEach((element) => { | |
let left = element.left; | |
leftTotalValue += left; | |
let right = element.right; | |
rightTotalValue += right; | |
canvasHeight += height + margin; | |
}); | |
//add space for legends | |
canvasHeight += 80; | |
//Set canvas environment | |
let canvas: HTMLCanvasElement = canvasRef.nativeElement; | |
canvas.height = canvasHeight; | |
canvas.width = canvasWidth; | |
//get canvas context for drawing | |
let ctx: CanvasRenderingContext2D = canvas.getContext('2d'); | |
//safe tests | |
if (leftTotalValue > 0 && rightTotalValue > 0 && data.labels.length === 2) { | |
let y: number; | |
data.datasets.forEach((element, index) => { | |
//coordinate for translation | |
y = (index * height) + (margin * (index + 1)); | |
//create label | |
ctx.save(); | |
ctx.translate(143, y - 28); | |
ctx.fillStyle = '#90CAF9'; | |
ctx.font = "normal 12px Arial"; | |
ctx.fillText(element.label, 0, 24); | |
ctx.restore(); | |
//left serie | |
let leftWidth = Math.round((element.left / leftTotalValue) * 100); | |
this.drawBar(ctx, 0, y, '#90CAF9', leftWidth, true); | |
//right serie | |
let rightWidth = Math.round((element.right / rightTotalValue) * 100); | |
this.drawBar(ctx, 143, y, '#80DEEA', rightWidth, false); | |
}); | |
//legends | |
ctx.save(); | |
ctx.translate(0, y + 50); | |
ctx.fillStyle = '#90CAF9'; | |
ctx.font = "normal 14px Arial"; | |
let leftLabel: TextMetrics = ctx.measureText(data.labels[0]); | |
let leftLabelX: number = 140 - (leftLabel.width); | |
ctx.fillText(data.labels[0], leftLabelX, 15); | |
ctx.fillStyle = '#80DEEA'; | |
ctx.fillText(data.labels[1], 143, 15); | |
ctx.restore(); | |
} | |
} | |
} |
The data format should be:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
labels: ['Offer', 'Demand'], | |
datasets: [ | |
{ | |
label: 'Asia', | |
left: 678, | |
right: 3277 | |
}, | |
{ | |
label: 'Europe', | |
left: 0, | |
right: 2900 | |
}, | |
{ | |
label: 'Africa', | |
left: 0, | |
right: 1190 | |
}, | |
{ | |
label: 'Oceania', | |
left: 342, | |
right: 6339 | |
}, | |
{ | |
label: 'North America', | |
left: 0, | |
right: 6089 | |
}, | |
{ | |
label: 'South America', | |
left: 456, | |
right: 8084 | |
} | |
] | |
} |
Chart Component
We access the canvas reference using the @ViewChild annotation. We will use @Input annotation for data binding.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {Component, OnInit, ViewChild, Input, ElementRef} from '@angular/core'; | |
import {ChartService, ComparisonData} from '../../core/services/chart.service'; | |
@Component({ | |
selector: 'custom-chart', | |
template: '<canvas #myCanvas></canvas>' | |
}) | |
export class CustomComponent implements OnInit { | |
@ViewChild('myCanvas') canvasRef: ElementRef; | |
@Input() data: ComparisonData; | |
constructor( | |
private chartService: ChartService | |
) {} | |
ngOnInit(): void { | |
this.chartService.drawComparisonChart(this.canvasRef, this.data); | |
} | |
} |
nemulocgu Judy Reuland https://wakelet.com/wake/PcK_xxUVVGJoqtjb__LVX
ReplyDeleteteaupetifens
clarorunpe1984 Shannon Baker SolidWorks
ReplyDeleteAutodesk Maya
McAfee Internet Security
bandmadmipha
Well Written Beautiful Post!
ReplyDeletehere is my post related to html canvas.
you may like.
Convert HTML to Canvas
NranisXclamya Keith Carouthers Click
ReplyDeletesoftware
essoifracal