While the landscape of the frameworks available for structure and building web applications is changing every minute, D3js is still admitting to create icons using Javascript. In this tutorial, we'll add a chart D3 to a corner type applications and make the size of the graph.
We can also create an interface for the data points, in a new file ‘src/app/data/data.model.ts’:
Finally, we can pass the data to our chart component by modifying ‘src/app/app.component.html’:
Let’s replace the content of ‘src/app/bar-char/bar-chart.component.html’ with:
We can add some styling by adding the following to the renamed ‘src/app/bar-chart/bar-chart.component.scss’ file:
Using the component’s width and height
A nice tweak would be to use the width and size of the component, so we can style the component from its parent. We can accomplish this by modifying the ‘src/app/bar-chart/bar-chart.component.scss‘ file like this:
Resizing dynamically
The last tweak that we are going to make is to recreate the chart when the window is resized. This can cause a hit to performance if your chart has a lot of data, but we can manage this by throttling (which won’t be explained in this post series). This should be a relatively unfrequent event anyway.
To resize dynamically, we can modify the file ‘src/app/bar-chart/bar-chart.component.html’ like such:
Integrating D3 with Angular can be extremely powerful, as D3 allows us to create amazing visualizations and Angular provides the framework to create everything else related to a web application in a scalable way. This is an example of the power of both libraries combined, as we find ourselves with a chart that can be reused easily, with the responsibility of displaying data completely isolated while still allowing us to style it and position it from its parent container. We do need to be careful when integrating the librairies since they are both modifying the DOM and can possibly alter the expected behavior. The ViewEncapsulation that is not working properly is a great example of this.
Creating the Angular project
The first step is to create a new Angular project using the CLI, and to add the d3 library to it:ng new angular-d3Next, we will create the component that we will work with:
npm install d3 --save
npm install @types/d3 --save-dev
ng generate component bar-chartFinally, we will replace the content of ‘src/app/app.component.html’ with the following:
<h1>Bar Chart</h1>
<app-bar-chart></app-bar-chart>
Loading and passing data
In this tutorial, we will use this bar chart from Mike Bostock as the D3 visualization. You can find the data in JSON format here, and we will put it in a new asset file at ‘src/assets/data.json’.We can also create an interface for the data points, in a new file ‘src/app/data/data.model.ts’:
export interface DataModel {To load this data, we can modify the ‘src/app/app.component.ts’ file like this:
letter: string;
frequency: number;
}
angular-d3-app.component.tsFor HttpClient to work, we need to add HttpClientModule to our App NgModule imports in ‘src/app/app.module.ts’.
import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { DataModel } from 'src/data/data.model';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
data: Observable<DataModel>;
constructor(private http: HttpClient) {
this.data = this.http.get<DataModel>('data/data.json');
}
}
Finally, we can pass the data to our chart component by modifying ‘src/app/app.component.html’:
<h1>Bar Chart</h1>Integrating the D3 chart
<app-bar-chart [data]=”data | async”></app-bar-chart>
Let’s replace the content of ‘src/app/bar-char/bar-chart.component.html’ with:
<div #chart id="chart"></div>As you can see, our component will be code-driven, with nothing in the template except a div, which will serve as our container. The chart size will be inferred from the size of this element, which will be helpful in making the SVG react like a normal html node. Here is the main part of the code, the ‘src/app/bar-chart/bar-chart.component.ts’ file:
angular-d3-bar-chart.component.ts
import { Component, ElementRef, Input, OnChanges, ViewChild, ViewEncapsulation } from '@angular/core';
import * as d3 from 'd3';
import { DataModel } from 'src/app/data/data.model';
@Component({
selector: 'app-bar-chart',
encapsulation: ViewEncapsulation.None,
templateUrl: './bar-chart.component.html',
styleUrls: ['./bar-chart.component.scss']
})
export class BarChartComponent implements OnChanges {
@ViewChild('chart')
private chartContainer: ElementRef;
@Input()
data: DataModel[];
margin = {top: 20, right: 20, bottom: 30, left: 40};
constructor() { }
ngOnChanges(): void {
if (!this.data) { return; }
this.createChart();
}
private createChart(): void {
d3.select('svg').remove();
const element = this.chartContainer.nativeElement;
const data = this.data;
const svg = d3.select(element).append('svg')
.attr('width', element.offsetWidth)
.attr('height', element.offsetHeight);
const contentWidth = element.offsetWidth - this.margin.left - this.margin.right;
const contentHeight = element.offsetHeight - this.margin.top - this.margin.bottom;
const x = d3This file is mostly a slightly modified version of Mike Bostock’s snippet, but instead of having a static height and width, we use the div element’s height and width. We use the data that is passed through an input property instead of getting it from the file, which separates responsibility. In the component declaration, we changed the ViewEncapsulation, since the dynamic modifications to the DOM by D3 don’t play well with the default Angular styling. Without this modification, the styles aren’t applied.
.scaleBand()
.rangeRound([0, contentWidth])
.padding(0.1)
.domain(data.map(d => d.letter));
const y = d3
.scaleLinear()
.rangeRound([contentHeight, 0])
.domain([0, d3.max(data, d => d.frequency)]);
const g = svg.append('g')
.attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')');
g.append('g')
.attr('class', 'axis axis--x')
.attr('transform', 'translate(0,' + contentHeight + ')')
.call(d3.axisBottom(x));
g.append('g')
.attr('class', 'axis axis--y')
.call(d3.axisLeft(y).ticks(10, '%'))
.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', 6)
.attr('dy', '0.71em')
.attr('text-anchor', 'end')
.text('Frequency');
g.selectAll('.bar')
.data(data)
.enter().append('rect')
.attr('class', 'bar')
.attr('x', d => x(d.letter))
.attr('y', d => y(d.frequency))
.attr('width', x.bandwidth())
.attr('height', d => contentHeight - y(d.frequency));
}
}
We can add some styling by adding the following to the renamed ‘src/app/bar-chart/bar-chart.component.scss’ file:
angular-d3-bar-chart.component.scssNow, when you run ‘ng serve’, you should see the bar chart. You can also notice that it is taking up the whole page. If you resize the page and refresh, you will notice that the chart changes its size accordingly.
app-bar-chart {
.bar {
fill: steelblue;
}
.bar:hover {
fill: brown;
}
.axis--x path {
display: none;
}
}
Using the component’s width and height
A nice tweak would be to use the width and size of the component, so we can style the component from its parent. We can accomplish this by modifying the ‘src/app/bar-chart/bar-chart.component.scss‘ file like this:
app-bar-chart {An example use case would be to make the chart take 50% of its parent width. This is easily done by going to app.component.css and adding the following:
#chart {
height: inherit;
width: inherit;
.bar {
fill: steelblue;
}
.bar:hover {
fill: brown;
}
.axis--x path {
display: none;
}
}
}
app-bar-chart {This is especially useful when using grid systems, where the specific width of a chart in a column varies for almost every screen.
width: 50%;
}
Resizing dynamically
The last tweak that we are going to make is to recreate the chart when the window is resized. This can cause a hit to performance if your chart has a lot of data, but we can manage this by throttling (which won’t be explained in this post series). This should be a relatively unfrequent event anyway.
To resize dynamically, we can modify the file ‘src/app/bar-chart/bar-chart.component.html’ like such:
Conclusion
<div #chart id=”chart” (window:resize)=”onResize($event)”></div>
And we can add the following method to the file ‘src/app/bar-chart/bar-chart.component.ts’ :
onResize() {
this.createChart();
}
Integrating D3 with Angular can be extremely powerful, as D3 allows us to create amazing visualizations and Angular provides the framework to create everything else related to a web application in a scalable way. This is an example of the power of both libraries combined, as we find ourselves with a chart that can be reused easily, with the responsibility of displaying data completely isolated while still allowing us to style it and position it from its parent container. We do need to be careful when integrating the librairies since they are both modifying the DOM and can possibly alter the expected behavior. The ViewEncapsulation that is not working properly is a great example of this.
No comments:
Post a Comment