As more organizations focus on collecting and analyzing data, one of the challenges that product teams face is presenting that data in a useful way. While many software developers default to list and table views, these presentation formats have a tendency to overwhelm users.
Data visualization helps you better communicate meaning in your data by portraying it in a graphical format. This often means bar charts, scatter plots, or pie charts. Because most data visualizations on the web are generated on the frontend, JavaScript is the language of choice for data visualization libraries.
Of the available libraries, D3.js is one of the most popular. It allows you to create data visualizations by manipulating the DOM based on dynamic data. D3 is reactive, meaning that instead of generating a static image on the server and serving it to the client, it uses JavaScript to “draw” HTML elements onto your webpage. This makes D3 powerful, but also a little harder to use than some other charting libraries.
Angular is maintained by Google and is one of the most popular open-source frontend web frameworks. In this tutorial, you’ll see how you can add data visualizations to your Angular app using D3. You’ll go through the process of setting up Angular and D3, adding three common types of charts, and importing data from a third-party API or local CSV file. By the end, you should have a starting point for creating your own data visualizations using D3 and Angular.
Setting up Angular and D3
Angular uses a command line interface to help you generate new applications and components. You just need to have Node.js and npm installed to proceed. If you’re new to using Angular, you can read the local setup guide to learn more about the process, or if you’d like to skip the walkthrough and download the final application, you can get the code from GitHub. If you’d like to go through the entire process, read on!
First, install the Angular CLI. The command line tool offers a quick way to start new Angular projects:
npm install -g @angular/cli
Next, create a new Angular app. You can call it whatever you want, but I’ll use the name angular-d3
:
ng new angular-d3
Finally, navigate into the new project:
cd angular-d3/
Next, install D3 and the D3 type definitions from npm. Type definitions will allow TypeScript to apply type hints to the external D3 code.
npm install d3 && npm install @types/d3 --save-dev
Next, create three new components using the Angular CLI. In the following steps, you’ll use D3 to generate data visualizations within each one.
First, the bar
component:
ng g component bar
Next, the pie
component:
ng g component pie
And the scatter
component:
ng g component scatter
These components are now available in the src/app/
directory and Angular added them to your app.module.ts
file, but you still need to insert the components using their selectors. Open up your src/app/app.component.html
file and replace its contents with the following:
<header>
<h1>Angular + D3</h1>
</header>
<app-bar></app-bar>
<app-pie></app-pie>
<app-scatter></app-scatter>
Finally, you can make the site look a little prettier by adding new.css to your <head>
. Open up the src/index.html
file and add the following lines between the <head></head>
tags:
<link rel="stylesheet" href="https://fonts.xz.style/serve/inter.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@exampledev/new.css@1.1.2/new.min.css">
You’re ready to test it out. From your terminal, run ng serve --open
. Your browser should open up http://localhost:420``0
, and you’ll see something like this:
Now that your Angular app is ready, let’s add three charts it: a bar chart, pie chart, and scatter plot.
Creating a bar chart
The first data visualization you’ll add is a bar chart. Bar charts are typically used to show relative values of different categories of data. The data we’ll use is the number of stars each popular frontend web development framework has:
[
{"Framework": "Vue", "Stars": "166443", "Released": "2014"},
{"Framework": "React", "Stars": "150793", "Released": "2013"},
{"Framework": "Angular", "Stars": "62342", "Released": "2016"},
{"Framework": "Backbone", "Stars": "27647", "Released": "2010"},
{"Framework": "Ember", "Stars": "21471", "Released": "2011"}
]
Angular components usually consist of four files: an HTML template file, a CSS or SCSS stylesheet, a spec (test) file, and a TypeScript component file. Open up the template file (bar.component.html
) and add the following header and figure:
<h2>Bar Chart</h2>
<figure id="bar"></figure>
You’ll use D3 to add the chart inside the figure using a CSS selector. Next, open up the TypeScript component file (bar.component.ts
) and add the following properties:
...
export class BarComponent implements OnInit {
private data = [
{"Framework": "Vue", "Stars": "166443", "Released": "2014"},
{"Framework": "React", "Stars": "150793", "Released": "2013"},
{"Framework": "Angular", "Stars": "62342", "Released": "2016"},
{"Framework": "Backbone", "Stars": "27647", "Released": "2010"},
{"Framework": "Ember", "Stars": "21471", "Released": "2011"},
];
private svg;
private margin = 50;
private width = 750 - (this.margin * 2);
private height = 400 - (this.margin * 2);
...
The first private property, data
, hardcodes the data needed to generate the chart. You’ll see later how to use data from a file or API, but this will let you get started.
The svg
property will be used in the class to store the SVG image that D3 draws onto the DOM. The other properties set a height, width, and margin for the chart. While it’s possible to create responsive charts with D3, I won’t explore them in this tutorial.
Next, create a method in the BarComponent
called createSvg()
. This selects the element in the DOM and inserts a new SVG with a <g>
element:
private createSvg(): void {
this.svg = d3.select("figure#bar")
.append("svg")
.attr("width", this.width + (this.margin * 2))
.attr("height", this.height + (this.margin * 2))
.append("g")
.attr("transform", "translate(" + this.margin + "," + this.margin + ")");
}
Now create a method called drawBars()
that will add the bars using the svg
property:
private drawBars(data: any[]): void {
// Create the X-axis band scale
const x = d3.scaleBand()
.range([0, this.width])
.domain(data.map(d => d.Framework))
.padding(0.2);
// Draw the X-axis on the DOM
this.svg.append("g")
.attr("transform", "translate(0," + this.height + ")")
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end");
// Create the Y-axis band scale
const y = d3.scaleLinear()
.domain([0, 200000])
.range([this.height, 0]);
// Draw the Y-axis on the DOM
this.svg.append("g")
.call(d3.axisLeft(y));
// Create and fill the bars
this.svg.selectAll("bars")
.data(data)
.enter()
.append("rect")
.attr("x", d => x(d.Framework))
.attr("y", d => y(d.Stars))
.attr("width", x.bandwidth())
.attr("height", (d) => this.height - y(d.Stars))
.attr("fill", "#d04a35");
}
Finally, call both of these methods in your BarComponent
‘s ngOnInit()
method:
ngOnInit(): void {
this.createSvg();
this.drawBars(this.data);
}
If you stopped the Angular server from the previous step, restart it (ng serve
) and visit localhost:4200
in your browser. You should see your new bar chart:
Creating a pie chart
A pie chart is a good way to show the relative values of different data. In this case, you’ll use it to visualize the market share of different frontend frameworks based on GitHub stars.
The first step is to update the component’s HTML file (pie.component.html
) with a new figure and title:
<h2>Pie Chart</h2>
<figure id="pie"></figure>
Because this chart uses the same set of data as the bar chart, the component’s class starts off looking similar. Update the pie.component.ts
file with your data and the following private properties:
...
export class PieComponent implements OnInit {
private data = [
{"Framework": "Vue", "Stars": "166443", "Released": "2014"},
{"Framework": "React", "Stars": "150793", "Released": "2013"},
{"Framework": "Angular", "Stars": "62342", "Released": "2016"},
{"Framework": "Backbone", "Stars": "27647", "Released": "2010"},
{"Framework": "Ember", "Stars": "21471", "Released": "2011"},
];
private svg;
private margin = 50;
private width = 750;
private height = 600;
// The radius of the pie chart is half the smallest side
private radius = Math.min(this.width, this.height) / 2 - this.margin;
private colors;
...
The big difference here is the addition of the radius
and colors
properties. Because pie charts use a circle instead of a rectangle to display data, that rad``i``us
property ensures that the chart fits within the defined figure’s bounds. You’ll use colors
to define the colors for the pie chart in a coming step.
Next, create a private method called createSvg()
. This will select the element on the DOM and add the <g>
element where D3 will draw your pie chart:
private createSvg(): void {
this.svg = d3.select("figure#pie")
.append("svg")
.attr("width", this.width)
.attr("height", this.height)
.append("g")
.attr(
"transform",
"translate(" + this.width / 2 + "," + this.height / 2 + ")"
);
}
In this pie chart, you’ll use an ordinal scale to create a discrete color for each section of the chart. You could define each to be the dominant color of the framework, but I think a monochromatic scheme looks nicer.
private createColors(): void {
this.colors = d3.scaleOrdinal()
.domain(this.data.map(d => d.Stars.toString()))
.range(["#c7d3ec", "#a5b8db", "#879cc4", "#677795", "#5a6782"]);
}
Create a method to draw the chart and add labels. This method uses <path>
elements to create arcs for each framework and fill them with the colors defined in the createColors
method above.
private drawChart(): void {
// Compute the position of each group on the pie:
const pie = d3.pie<any>().value((d: any) => Number(d.Stars));
// Build the pie chart
this.svg
.selectAll('pieces')
.data(pie(this.data))
.enter()
.append('path')
.attr('d', d3.arc()
.innerRadius(0)
.outerRadius(this.radius)
)
.attr('fill', (d, i) => (this.colors(i)))
.attr("stroke", "#121926")
.style("stroke-width", "1px");
// Add labels
const labelLocation = d3.arc()
.innerRadius(100)
.outerRadius(this.radius);
this.svg
.selectAll('pieces')
.data(pie(this.data))
.enter()
.append('text')
.text(d => d.data.Framework)
.attr("transform", d => "translate(" + labelLocation.centroid(d) + ")")
.style("text-anchor", "middle")
.style("font-size", 15);
}
D3’s centroid
function allows you to put labels in the calculated centroid of each slice of the pie. In this case, by setting the innerRadius(100)
, the labels will be slightly outside the true centroid. You can adjust these numbers to reposition them wherever you think they look best.
Finally, call all three of these methods in the ngOnInit()
method:
ngOnInit(): void {
this.createSvg();
this.createColors();
this.drawChart();
}
Head back over to your browser to see the new pie chart in your Angular application.
Creating a scatter plot
The last type of data visualization you’ll create for this tutorial is a scatter plot. Scatter plots give us the ability to show the relationship between two pieces of data for each point in the graph. In this case, you’ll look at the relationship between the year that each framework was released and the number of stars it currently has.
Start off by updating the HTML template file (scatter.component.html
) in the same way as you did above:
<h2>Scatter Plot</h2>
<figure id="scatter"></figure>
Because this scatter plot uses the same data and figure size, it starts off with the same properties as the bar chart did:
...
export class ScatterComponent implements OnInit {
private data = [
{"Framework": "Vue", "Stars": "166443", "Released": "2014"},
{"Framework": "React", "Stars": "150793", "Released": "2013"},
{"Framework": "Angular", "Stars": "62342", "Released": "2016"},
{"Framework": "Backbone", "Stars": "27647", "Released": "2010"},
{"Framework": "Ember", "Stars": "21471", "Released": "2011"},
];
private svg;
private margin = 50;
private width = 750 - (this.margin * 2);
private height = 400 - (this.margin * 2);
...
In fact, the createSvg
method is the same as the bar chart, too:
private createSvg(): void {
this.svg = d3.select("figure#scatter")
.append("svg")
.attr("width", this.width + (this.margin * 2))
.attr("height", this.height + (this.margin * 2))
.append("g")
.attr("transform", "translate(" + this.margin + "," + this.margin + ")");
}
If your application has many bar and scatter plots that use similar properties, you might want to try using inheritance to cut down on the amount of duplicate code.
Next, create a new drawPlot()
method to create the x- and y-axes of your plot and add the dots to the canvas. This method makes the points semi-transparent and adds the name of each framework as a label.
private drawPlot(): void {
// Add X axis
const x = d3.scaleLinear()
.domain([2009, 2017])
.range([ 0, this.width ]);
this.svg.append("g")
.attr("transform", "translate(0," + this.height + ")")
.call(d3.axisBottom(x).tickFormat(d3.format("d")));
// Add Y axis
const y = d3.scaleLinear()
.domain([0, 200000])
.range([ this.height, 0]);
this.svg.append("g")
.call(d3.axisLeft(y));
// Add dots
const dots = this.svg.append('g');
dots.selectAll("dot")
.data(this.data)
.enter()
.append("circle")
.attr("cx", d => x(d.Released))
.attr("cy", d => y(d.Stars))
.attr("r", 7)
.style("opacity", .5)
.style("fill", "#69b3a2");
// Add labels
dots.selectAll("text")
.data(this.data)
.enter()
.append("text")
.text(d => d.Framework)
.attr("x", d => x(d.Released))
.attr("y", d => y(d.Stars))
}
Another important difference in the scatter plot is that your x-axis uses dates instead of strings (like you did in the bar chart), so you have to call .tickFormat(d3.format("d"))
to format them properly.
Finally, call both methods from your ngOnInit()
method:
ngOnInit(): void {
this.createSvg();
this.drawPlot();
}
Head back over to your browser to see the final plot rendered in your Angular application.
Loading data from external sources
So far, you’ve hardcoded data into your Angular components, but that’s probably not realistic. There are several ways you can load data from external sources, so let’s look at two of the most common patterns you might use in D3.
Loading a CSV file
Most spreadsheets can be exported as CSV files. CSV is a text-based data storage format that uses commas and line breaks to separate values in the file. Because CSV is such a common data format, D3 provides native support for loading CSV files that are publicly available.
To demonstrate using a CSV file, create a new file in your Angular application’s src/assets/
directory called frameworks.csv
. Add the following text to the file:
Framework,Stars,Released Vue,166443,2014 React,150793,2013 Angular,62342,2016 Backbone,27647,2010 Ember,21471,2011
Next, open up the bar chart component (bar.component.ts
) and update the ngOnInit()
method to call D3’s csv()
method:
ngOnInit(): void {
this.createSvg();
// Parse data from a CSV
d3.csv("/assets/frameworks.csv").then(data => this.drawBars(data));
}
D3 can load CSVs from your Angular application or a third-party URL and makes the data available as an array of objects in the resulting promise. If you go back to the browser, you should see the same bar chart as before, but this time it’s using data from the frameworks.csv
file instead of the Angular component.
Getting data from a JSON API
Another common data format used in web APIs is JSON. JSON is a string representation of JavaScript objects and is commonly returned by REST and GraphQL APIs. D3 natively supports JSON data, so it makes it really easy to integrate with your Angular application.
First, you’ll need a JSON API endpoint or file. I uploaded the framework data used throughout this tutorial to JSONbin, a free JSON file hosting platform. You can access this data here.
To use this endpoint, open up the bar.component.ts
file again and update the ngOnInit()
method with the following:
ngOnInit(): void {
this.createSvg();
// Fetch JSON from an external endpoint
d3.json('https://api.jsonbin.io/b/5eee6a5397cb753b4d149343').then(data => this.drawBars(data));
}
As in the CSV example, D3’s json
method returns a promise with your data parsed as an array of objects. By passing this in, your bar chart can now use the JSON API endpoint as its data source.
Conclusion
When you need to create rich data visualizations in your Angular app, D3 is a great choice. While it may take some time to master, D3 is very powerful and can be used to create almost any data visualization you can imagine. It also makes accessing datasets from CSVs or JSON APIs really easy.
If you’d like to learn more ways to customize your bar charts, I’d recommend looking at the official documentation or the D3 Graph Gallery for more examples.
Comments
Post a Comment