Lines in Angular
Chapter 1: Single line using HTML Canvas
Almost all of the Google/YouTube searches resulted in HTML Canvas and tutorials for drawing in Canvas. Main issue with this approach is that we don’t have any control over the drawn line, as drawing a line just changes the colour of that particular pixel. Since I was not able to find anything else, I decided to start implementing HTML Canvas.
Created a line-service.ts
drawLine() {
if (this.lineCanvas) {
const context = this.lineCanvas.getContext('2d');
if (context == null) {
return;
}
if (!this.startSaved || !this.endSaved) {
alert('Please set start and end point');
return;
}
context.strokeStyle = this.lineColor; // Set line color
context.lineWidth = this.lineWidth; // Set line width
context.setLineDash([15, 3]);
context.beginPath();
context.moveTo(this.startx, this.starty);
context.lineTo(this.endx, this.endy);
context.stroke();
}
}
Chapter 2: Drawing Multiple lines and Line events
Drawing multiple lines was fairly easy with HTML Canvas , just call the drawLine() method again and again with different start and end points. But Adding line events was a pain.
- Delete
- Drag
I found it difficult to delete a particular line. Because my only option was to paint white color in the line, so that it will get blend with background, and won’t be visible for the user. But I had a grid background and it was impossible to erase the line. Also that might be erase the existing lines as well.
Anyways keeping performance god in my mind , I created a global array of lines. And From that array, I was drawing on the canvas. When someone want to delete a line , I can simply , remove that particular line from the array and re-draw everything. Also I during drag I can simply re-draw with new co-ordinates.
But major concern was showing the dragging animation, only solution for this was to use a secondary canvas , and during dragging we keep re-drawing that canvas. And when the dragging is done, just paint it to the primary cnavas. For the users eye, it won’t make any difference.
But but but but I am not pasting any snippets for these solutions , because I chose none of these to move forward, I removed Canvas.
Chapter 3: Not Canvas ?
Thanks to this tutorial in Solidjs Here. (Feel free to check it out , way better than reading my article from here onwards) 😂. I decided to scrap HTML Canvas and use SVG instead.
<svg class="wrapper">
<path
[ngClass]="{'edgeNew': isNew, 'selected': selected, 'edge': !selected}"
[attr.d]=path></path>
</svg>
ngOnInit(): void {
this.edgeObserver.subscribe((value) => {
this.isNew = value.isNew;
this.selected = value.selected;
this.x1 = value.position.x1;
this.y1 = value.position.y1;
this.x2 = value.position.x2;
this.y2 = value.position.y2;
this.path = `M${this.x1} ${this.y1} C ${this.x1 + Math.abs(this.x2 - this.x1)} ${this.y1}, ${this.x2 - Math.abs(this.x2 - this.x1)} ${this.y2}, ${this.x2} ${this.y2}`
this.lineReady = true;
})
if(this.LineOptions){
this.isNew = this.LineOptions.isNew;
this.selected = this.LineOptions.selected;
this.x1 = this.LineOptions.position.x1;
this.y1 = this.LineOptions.position.y1;
this.x2 = this.LineOptions.position.x2;
this.y2 = this.LineOptions.position.y2;
this.path = `M${this.x1} ${this.y1} C ${this.x1 + Math.abs(this.x2 - this.x1)} ${this.y1}, ${this.x2 - Math.abs(this.x2 - this.x1)} ${this.y2}, ${this.x2} ${this.y2}`
this.lineReady = true;
}
}
This allowed me to componentize a line. That is what I love about angular, now everything can be handled in component level. The click , the drag and everything. I did also make it look like a curved smooth line.
Chapter 4: Finishing Touches
Like I did for canvas , I created a global line array that keep tracks of all the lines. The main advantage of SVG component is that it made easy to identify the click made on a line , where as in Canvas it was not possible to say whether the user clicked on a line without complex calculations.
<ng-container *ngIf="lines.length > 0">
<div *ngFor="let line of lines">
<line [LineOptions]="getLineOptions(line)"></line>
</div>
</ng-container>
export interface LineOptions {
selected: boolean;
isNew: boolean;
position: {x1: number, y1: number, x2: number, y2: number};
}
Well this all worked out pretty well, but still the question remained on how to show the drag animation. I can’t keep on re-drawing the global array, so I decided to create a new component for this case , it will only be visible during the drag
<ng-container>
<line *ngIf="newLine" [edgeObserver]="edgeObserver"></line>
</ng-container>
@HostListener('document:mousemove', ['$event'])
onMouseMove(e: any) {
if(this.currentOutput){
this.edgeObserver.next({
isNew: true,
selected: false,
position: {
x1: this.currentOutput.x,
y1: this.currentOutput.y,
x2: e.clientX,
y2: e.clientY
}
})
this.newLine = true;
}
}
During the mouse drag edgeObserver keeps updating the line options of the new line. Which will only re-render that particular component instead of the whole thing and that’s it we are good to go.
PS: This is part of a project , that is closed as of today and will be published as soon as possible, stay tuned 😊
For more update stay posted : Shaheen