Sparklines with Angular and D3
By: Johnathan Sanders
Tags: Charts, Covid 19, D3js, Sparklines, Angular

Tablet with Charts

So you want to display a Sparkline? There’s no need to use bulky libraries, especially those built on top of D3 (don’t get me wrong, many of them are great), or pay for a commercial component here. We can make one fairly easily in order to bring otherwise boring numbers alive!

To tackle this, we are going to use D3.js which is described on Wikipedia as:

D3.js (also known as D3, short for Data-Driven Documents) is a JavaScript library for producing dynamic, interactive data visualizations in web browsers. It makes use of the widely implemented Scalable Vector Graphics (SVG), HTML5, and Cascading Style Sheets (CSS) standards.

Prerequisites

Most Angular targeted tutorials that I see these days like to walk you through installing Angular and creating a brand new project. I’m not going to do that, for one I strongly believe you should have foundational knowledge of the framework before you get here. However, I will provide a link on how to get started with Angular, so if you’re new — go through the tutorial (Tour of Heroes) and then come back and make a Sparkline with us! Angular Tour of Heroes

You will need to install the D3 library, which can be done at a prompt with: npm install d3 --save

Then, to ensure we do not go mad while implementing the awesomeness of D3, we need the typings: npm install @types/d3 --save-dev

All right, we now have the libraries so let’s get moving on the data. When dealing with the data for D3, many of the examples on Observable HQ use csv or tsv data (comma or tab separated data, respectively). These are great, however most APIs tend to return JSON data, so we need to do some work to prepare it before we dive into the creation of the actual sparkline.

tsconfig.json

Before we move on, I like to use path shortcuts when building Angular apps. This way, I can access src/app/feature/demo/demo.component.ts simply with ~app/feature/demo/demo.component. To do this, you’ll want to open the tsconfig.json file in the root directory and add the following to the compilerOptions section, which I usually insert just below the moduleResolution and before the importHelpers.

"paths": {

"~app/*": ["src/app/*"],

"~assets/*": ["src/assets/*"],

"~environment/*": ["src/environments/*"]

},

Chart Model

The Chart Model is what holds the structure to our data, and keeps us from fighting our chosen Typescript linter. Let’s create it with:

ng generate class feature/chart/chart

This should create a chart.ts file within the app/feature/chart directory . For this implementation, we only need to create a class called ‘ChartSeries’ which will hold the x and y axis data.

ChartSeries

xAxis — The x axis data

— The x axis data yAxis — The y axis data

Here is the full chart file:

chart.ts

export class ChartSeries {

public xAxis: any;

public yAxis: any;

}

Chart Component

We need a component to actually display the sparkline. we will do that by generating a chart component with: ng generate component feature/chart

Once it’s created, let’s open: app/feature/chart/chart.component.ts

chart.component.ts

We’re going to utilize quite a few core Angular interfaces, including AfterViewInit, ElementRef, HostListener, Input, and ViewChild. We do this through an import.

Replace the existing: import { Component, OnInit } from '@angular/core';

With: import { AfterViewInit, Component, ElementRef, HostListener, Input, ViewChild } from '@angular/core';

Once that’s done, let’s import the D3 library: import * as d3 from 'd3';

Last but not least, the ChartSeries class that we created earlier: import { ChartSeries } from './chart';

Alright, that should set us up for success!

You’ll probably have an error here because we are implementing OnInit and not AfterViewInit, so we need to make two adjustments:

Replace the export statement on what should be on or near line 10 that contains export class ChartComponent implements OnInit {

With export class ChartComponent implements AfterViewInit {

We also need to replace the ngOnInit() method with ngAfterViewInit(). On or around line 14 replace ngOnInit(): void {}

With ngAfterViewInit(): void {}

Why is this important? If you aren’t super familiar with the Angular Component Lifecycle it is essentially a way to ‘inject’ your code into the component at specific events in the lifecycle of the component (creation, updates, and destruction). From the official Angular documentation:

A component instance has a lifecycle that starts when Angular instantiates the component class and renders the component view along with its child views. The lifecycle continues with change detection, as Angular checks to see when data-bound properties change, and updates both the view and the component instance as needed. The lifecycle ends when Angular destroys the component instance and removes its rendered template from the DOM. Directives have a similar lifecycle, as Angular creates, updates, and destroys instances in the course of execution.

Therefore, we want to wait until the component is as initialized as it can be before attempting to draw anything. This is especially important as we will be utilizing the ViewChild decorator which per the Angular documentation is set just before ngAfterViewInit() is called, but after other initialization lifecycle hooks.

View queries are set before the ngAfterViewInit callback is called.

Host Listener

Alright! Now we also want to make sure that when the window is resized, our chart is sized with it. We want to capture any window resize events to re-draw our chart appropriately within its container. The easiest way to do this is via an Angular HostListener — we capture any ‘window:resize’ event and then execute our re-draw method of this.resize() which we will implement further on in this article.

@HostListener('window:resize', ['$event'])

onResize() {

this.resize();

}

Input

We will also need a few pieces of information from the calling component to initialize the chart and to size things properly.

@Input() data: ChartSeries[]; // The data to plot on the sparkline

@Input() lineColor: number[] = [70, 130, 180, 1]; // The RGBA value representing the color of the sparkline

@Input() margin: number = 0; // The margin to apply to sizing calculations - default to 0 but will typically be 20-24 pixels.

Keep in mind that if you don’t want to use RGBA values, you can simply make lineColor a string and utilize the HEX code such as ‘#4682B4’ but I like to allow myself the freedom of opacity (the fourth number in the RGBA array that can range between 0 — completely transparent and 1-no transparency, such as 0.4)

View Child

Moving right along! We will also want to utilize Angular’s ViewChild to easily (and without generating or requiring a specific element id which can get very troublesome when displaying multiple charts) refer to the container and chart DOM elements.

@ViewChild('chart', {static: false}) chart: ElementRef<SVGElement>;

@ViewChild('container', {static: false}) container: ElementRef<HTMLDivElement>;

Other Variables

Finally, we need to store a few global values to use throughout our component such as height, width, and a global svg component to reference later.

private height: number;

private svg: any;

private width: number;

Quick Recap

As of right now, our chart.component.ts file should look very similar to this:

import { AfterViewInit, Component, ElementRef, HostListener, Input, ViewChild } from '@angular/core';

import * as d3 from 'd3';

import { ChartSeries } from './chart'; @Component({

selector: 'app-chart',

templateUrl: './chart.component.html',

styleUrls: ['./chart.component.scss']

})

export class ChartComponent implements AfterViewInit { @HostListener('window:resize', ['$event'])

onResize() {

this.resize();

}



@Input() data: ChartSeries[] = []; // The data to plot on the sparkline

@Input() lineColor: number[] = [70, 130, 180, 1]; // The RGBA value representing the color of the sparkline

@Input() margin: number = 0; // The margin to apply to sizing calculations - default to 0 but will typically be 20-24 pixels. @ViewChild('chart', {static: false}) chart: ElementRef<SVGElement>;

@ViewChild('container', {static: false}) container: ElementRef<HTMLDivElement>; private height: number;

private svg: any;

private width: number; constructor() { } ngAfterViewInit(): void {

}

}

resize

The first method we want to implement is resize. This will allow us to modify our chart height and width any time a resize event is triggered, and it will allow us to perform initial size setup. We will use our ViewChild definitions of this.container and this.chart — and then access their nativeElement properties.

private resize(): void {

// We know how big we should be based on our parent element - and its offsetHeight/offsetWidth properties

this.height = this.container.nativeElement.parentElement.offsetHeight;

this.width = this.container.nativeElement.parentElement.offsetWidth; this.chart.nativeElement.style.height = `${this.height}px`;

this.chart.nativeElement.style.maxHeight = `${this.height}px`;

this.chart.nativeElement.style.maxWidth = `${this.width}px`;

this.chart.nativeElement.style.minHeight = `${this.height}px`;

this.chart.nativeElement.style.minWidth = `${this.width}px`;

this.chart.nativeElement.style.width = `${this.width}px`; this.container.nativeElement.style.height = `${this.height}px`;

this.container.nativeElement.style.maxHeight = `${this.height}px`;

this.container.nativeElement.style.maxWidth = `${this.width}px`;

this.container.nativeElement.style.minHeight = `${this.height}px`;

this.container.nativeElement.style.minWidth = `${this.width}px`;

this.container.nativeElement.style.width = `${this.width}px`;

}

Now that we have our resize event, let’s add it to our ngAfterViewInit method to set our height and width on initialization. In addition, let’s make it easer to reference our actual chart later on with a d3.select().

ngAfterViewInit(): void {

this.resize();

this.svg = d3.select(this.chart.nativeElement);

}

buildSparkline

Now we’re on to the real meat of the thing. We need to generate and draw our sparkline.

X and Y scales

Scales allow d3 to scale data along an axis. Per the d3 documentation:

Scale functions are JavaScript functions that: - take an input (usually a number, date or category) and

- return a value (such as a coordinate, a colour, a length or a radius) They’re typically used to transform (or ‘map’) data values into visual variables (such as position, length and colour).

We’re going to assume that our x axis is time (the axis that runs horizontally — e.g. left<->right) and that our y axis is a numeric value (the axis that runs vertically — e.g. top<->bottom). We start at a range of zero for the x axis (left most value) and end with the width of the component. On the y axis we start at a range of the height of the component (bottom most value) and end with 0. With the domains, we use d3.extent() to determine the minimum and maximum range of time within this.data on the x axis — for the y axis we have a domain that starts at zero and ends at the maximum value in this.data’s yAxis.

const xScale = d3.scaleTime()

.domain(d3.extent(this.data, (d) => d.xAxis))

.range([0, this.width]);

const yScale = d3.scaleLinear()

.domain([0, d3.max(this.data, (d) => d.yAxis)])

.range([this.height, 0]);

Line

Yes, this is the actual sparkline. we simply create a d3.line and set its x and y axis’ to the return of the xScale and yScale methods respectively. We also set a nice curve to the line (instead of a jagged edge) with the d3.line curve method using d3.curveCardinal.

const line: any = d3.line<ChartSeries>()

.curve(d3.curveCardinal)

.x((d) => xScale(d.xAxis))

.y((d) => yScale(d.yAxis));

Draw it

Now we write our line to an SVG path to draw the sparkline on the canvas.

this.svg.append('path')

.datum(this.data)

.attr('d', line)

.style('fill', 'none')

.style('stroke', `rgba(${this.lineColor[0]},${this.lineColor[1]},${this.lineColor[2]},${this.lineColor[3]})`)

.style('stroke-width', '2')

.style('width', `${this.width}px`);

Initialize it

We’ve gotten our handler for window resized and our SVG write for our sparkline — so we can initialize everything in ngAfterViewInit()

ngAfterViewInit(): void {

this.resize();

this.svg = d3.select(this.chart.nativeElement);

this.buildSparkline();

}

Wrap up

After all of that fun, we should wind up with a chart.component.ts file that looks just like this:

import { AfterViewInit, Component, ElementRef, HostListener, Input, ViewChild } from '@angular/core';

import * as d3 from 'd3';

import { ChartSeries } from './chart'; @Component({

selector: 'app-chart',

templateUrl: './chart.component.html',

styleUrls: ['./chart.component.scss']

})

export class ChartComponent implements AfterViewInit { @HostListener('window:resize', ['$event'])

onResize() {

this.resize();

} @Input() data: ChartSeries[]; // The data to plot on the sparkline

@Input() lineColor: number[] = [70, 130, 180, 1]; // The RGBA value representing the color of the sparkline

@Input() margin: number = 0; // The margin to apply to sizing calculations - default to 0 but will typically be 20-24 pixels. @ViewChild('chart', {static: false}) chart: ElementRef<SVGElement>;

@ViewChild('container', {static: false}) container: ElementRef<HTMLDivElement>; private height: number;

private svg: any;

private width: number; constructor() { } ngAfterViewInit(): void {

this.resize();

this.svg = d3.select(this.chart.nativeElement);

this.buildSparkline();

} private buildSparkline(): void { const xScale: any = d3.scaleTime()

.domain(d3.extent(this.data, (d) => d.xAxis))

.range([0, this.width]); const yScale: any = d3.scaleLinear()

.domain([0, d3.max(this.data, (d) => d.yAxis)])

.range([this.height, 0]); const line: any = d3.line<ChartSeries>()

.curve(d3.curveCardinal)

.x((d) => xScale(d.xAxis))

.y((d) => yScale(d.yAxis)); this.svg.append('path')

.datum(this.data)

.attr('d', line)

.style('fill', 'none')

.style('stroke', `rgba(${this.lineColor[0]},${this.lineColor[1]},${this.lineColor[2]},${this.lineColor[3]})`)

.style('stroke-width', '2')

.style('width', `${this.width}px`);

} private resize(): void {

// We know how big we should be based on our parent element - and its offsetHeight/offsetWidth properties

this.height = this.container.nativeElement.parentElement.offsetHeight;

this.width = this.container.nativeElement.parentElement.offsetWidth; this.chart.nativeElement.style.height = `${this.height}px`;

this.chart.nativeElement.style.maxHeight = `${this.height}px`;

this.chart.nativeElement.style.maxWidth = `${this.width}px`;

this.chart.nativeElement.style.minHeight = `${this.height}px`;

this.chart.nativeElement.style.minWidth = `${this.width}px`;

this.chart.nativeElement.style.width = `${this.width}px`; this.container.nativeElement.style.height = `${this.height}px`;

this.container.nativeElement.style.maxHeight = `${this.height}px`;

this.container.nativeElement.style.maxWidth = `${this.width}px`;

this.container.nativeElement.style.minHeight = `${this.height}px`;

this.container.nativeElement.style.minWidth = `${this.width}px`;

this.container.nativeElement.style.width = `${this.width}px`;

}

}

chart.component.html

Our component is great, but we’re not showing anything yet. Let’s do a quick replacement in chart.component.html of the placeholder from Angular with the following:

<div #container style="height: 100%; width: 100%;">

<svg #chart height="100%" style="overflow: visible;" width="100%">

</svg>

</div>

You should recognize the #container and #chart identifiers above — these are what we reference with ViewChild in chart.component.ts

Demo Component

The Chart component is now finished, but we’re not using it. Let’s create a new component with the purpose of using it. ng generate component feature/demo

data.ts

To load this component up, we need some data. Let’s create a file (do this manually, without the Angular CLI) called ‘data.ts’ in the app/feature/demo directory. We’re going to use static data here, but it could just as easily be brought in from an external source. That is outside the scope of this particular article, so we’re using static data for now. Here, i’m using the latest (as of the time of this writing) daily COVID-19 fatality data from Sweden. Feel free to use any x and y axis you choose!

import { ChartSeries } from '~app/feature/chart/chart'; export const DATA: ChartSeries[] = [{xAxis: new Date('3/01/2020'), yAxis: 0},{xAxis: new Date('3/02/2020'), yAxis: 0},{xAxis: new Date('3/03/2020'), yAxis: 0},{xAxis: new Date('3/04/2020'), yAxis: 0},{xAxis: new Date('3/05/2020'), yAxis: 0},{xAxis: new Date('3/06/2020'), yAxis: 0},{xAxis: new Date('3/07/2020'), yAxis: 0},{xAxis: new Date('3/08/2020'), yAxis: 0},{xAxis: new Date('3/09/2020'), yAxis: 0},{xAxis: new Date('3/10/2020'), yAxis: 0},{xAxis: new Date('3/11/2020'), yAxis: 0},{xAxis: new Date('3/12/2020'), yAxis: 6},{xAxis: new Date('3/13/2020'), yAxis: 0},{xAxis: new Date('3/14/2020'), yAxis: 1},{xAxis: new Date('3/15/2020'), yAxis: 1},{xAxis: new Date('3/16/2020'), yAxis: 2},{xAxis: new Date('3/17/2020'), yAxis: 2},{xAxis: new Date('3/18/2020'), yAxis: 1},{xAxis: new Date('3/19/2020'), yAxis: 6},{xAxis: new Date('3/20/2020'), yAxis: 7},{xAxis: new Date('3/21/2020'), yAxis: 9},{xAxis: new Date('3/22/2020'), yAxis: 8},{xAxis: new Date('3/23/2020'), yAxis: 11},{xAxis: new Date('3/24/2020'), yAxis: 11},{xAxis: new Date('3/25/2020'), yAxis: 21},{xAxis: new Date('3/26/2020'), yAxis: 22},{xAxis: new Date('3/27/2020'), yAxis: 31},{xAxis: new Date('3/28/2020'), yAxis: 32},{xAxis: new Date('3/29/2020'), yAxis: 35},{xAxis: new Date('3/30/2020'), yAxis: 38},{xAxis: new Date('3/31/2020'), yAxis: 45},{xAxis: new Date('4/01/2020'), yAxis: 48},{xAxis: new Date('4/02/2020'), yAxis: 53},{xAxis: new Date('4/03/2020'), yAxis: 70},{xAxis: new Date('4/04/2020'), yAxis: 80},{xAxis: new Date('4/05/2020'), yAxis: 70},{xAxis: new Date('4/06/2020'), yAxis: 85},{xAxis: new Date('4/07/2020'), yAxis: 90},{xAxis: new Date('4/08/2020'), yAxis: 84},{xAxis: new Date('4/09/2020'), yAxis: 115},{xAxis: new Date('4/10/2020'), yAxis: 86},{xAxis: new Date('4/11/2020'), yAxis: 90},{xAxis: new Date('4/12/2020'), yAxis: 103},{xAxis: new Date('4/13/2020'), yAxis: 97},{xAxis: new Date('4/14/2020'), yAxis: 85},{xAxis: new Date('4/15/2020'), yAxis: 91},{xAxis: new Date('4/16/2020'), yAxis: 115},{xAxis: new Date('4/17/2020'), yAxis: 111},{xAxis: new Date('4/18/2020'), yAxis: 82},{xAxis: new Date('4/19/2020'), yAxis: 86},{xAxis: new Date('4/20/2020'), yAxis: 88},{xAxis: new Date('4/21/2020'), yAxis: 84},{xAxis: new Date('4/22/2020'), yAxis: 62},{xAxis: new Date('4/23/2020'), yAxis: 77},{xAxis: new Date('4/24/2020'), yAxis: 86},{xAxis: new Date('4/25/2020'), yAxis: 89},{xAxis: new Date('4/26/2020'), yAxis: 73},{xAxis: new Date('4/27/2020'), yAxis: 75},{xAxis: new Date('4/28/2020'), yAxis: 73},{xAxis: new Date('4/29/2020'), yAxis: 82},{xAxis: new Date('4/30/2020'), yAxis: 84},{xAxis: new Date('5/01/2020'), yAxis: 78},{xAxis: new Date('5/02/2020'), yAxis: 78},{xAxis: new Date('5/03/2020'), yAxis: 73},{xAxis: new Date('5/04/2020'), yAxis: 75},{xAxis: new Date('5/05/2020'), yAxis: 84},{xAxis: new Date('5/06/2020'), yAxis: 72},{xAxis: new Date('5/07/2020'), yAxis: 73},{xAxis: new Date('5/08/2020'), yAxis: 80},{xAxis: new Date('5/09/2020'), yAxis: 60},{xAxis: new Date('5/10/2020'), yAxis: 69},{xAxis: new Date('5/11/2020'), yAxis: 74},{xAxis: new Date('5/12/2020'), yAxis: 64},{xAxis: new Date('5/13/2020'), yAxis: 61},{xAxis: new Date('5/14/2020'), yAxis: 50},{xAxis: new Date('5/15/2020'), yAxis: 46},{xAxis: new Date('5/16/2020'), yAxis: 58},{xAxis: new Date('5/17/2020'), yAxis: 49},{xAxis: new Date('5/18/2020'), yAxis: 53},{xAxis: new Date('5/19/2020'), yAxis: 61},{xAxis: new Date('5/20/2020'), yAxis: 40},{xAxis: new Date('5/21/2020'), yAxis: 54},{xAxis: new Date('5/22/2020'), yAxis: 54},{xAxis: new Date('5/23/2020'), yAxis: 56},{xAxis: new Date('5/24/2020'), yAxis: 56},{xAxis: new Date('5/25/2020'), yAxis: 44},{xAxis: new Date('5/26/2020'), yAxis: 42},{xAxis: new Date('5/27/2020'), yAxis: 28},{xAxis: new Date('5/28/2020'), yAxis: 39},{xAxis: new Date('5/29/2020'), yAxis: 40},{xAxis: new Date('5/30/2020'), yAxis: 40},{xAxis: new Date('5/31/2020'), yAxis: 39},{xAxis: new Date('6/01/2020'), yAxis: 45},{xAxis: new Date('6/02/2020'), yAxis: 40},{xAxis: new Date('6/03/2020'), yAxis: 37},{xAxis: new Date('6/04/2020'), yAxis: 26},{xAxis: new Date('6/05/2020'), yAxis: 45},{xAxis: new Date('6/06/2020'), yAxis: 38},{xAxis: new Date('6/07/2020'), yAxis: 31},{xAxis: new Date('6/08/2020'), yAxis: 33},{xAxis: new Date('6/09/2020'), yAxis: 38},{xAxis: new Date('6/10/2020'), yAxis: 33},{xAxis: new Date('6/11/2020'), yAxis: 40},{xAxis: new Date('6/12/2020'), yAxis: 36},{xAxis: new Date('6/13/2020'), yAxis: 30},{xAxis: new Date('6/14/2020'), yAxis: 33},{xAxis: new Date('6/15/2020'), yAxis: 27},{xAxis: new Date('6/16/2020'), yAxis: 31},{xAxis: new Date('6/17/2020'), yAxis: 28},{xAxis: new Date('6/18/2020'), yAxis: 33},{xAxis: new Date('6/19/2020'), yAxis: 29},{xAxis: new Date('6/20/2020'), yAxis: 29},{xAxis: new Date('6/21/2020'), yAxis: 29},{xAxis: new Date('6/22/2020'), yAxis: 21},{xAxis: new Date('6/23/2020'), yAxis: 21},{xAxis: new Date('6/24/2020'), yAxis: 25},{xAxis: new Date('6/25/2020'), yAxis: 22},{xAxis: new Date('6/26/2020'), yAxis: 24},{xAxis: new Date('6/27/2020'), yAxis: 12},{xAxis: new Date('6/28/2020'), yAxis: 14},{xAxis: new Date('6/29/2020'), yAxis: 22},{xAxis: new Date('6/30/2020'), yAxis: 18},{xAxis: new Date('7/01/2020'), yAxis: 20},{xAxis: new Date('7/02/2020'), yAxis: 17},{xAxis: new Date('7/03/2020'), yAxis: 15},{xAxis: new Date('7/04/2020'), yAxis: 8},{xAxis: new Date('7/05/2020'), yAxis: 15},{xAxis: new Date('7/06/2020'), yAxis: 9},{xAxis: new Date('7/07/2020'), yAxis: 16},{xAxis: new Date('7/08/2020'), yAxis: 12},{xAxis: new Date('7/09/2020'), yAxis: 11},{xAxis: new Date('7/10/2020'), yAxis: 15},{xAxis: new Date('7/11/2020'), yAxis: 14},{xAxis: new Date('7/12/2020'), yAxis: 10},{xAxis: new Date('7/13/2020'), yAxis: 9},{xAxis: new Date('7/14/2020'), yAxis: 14},{xAxis: new Date('7/15/2020'), yAxis: 8},{xAxis: new Date('7/16/2020'), yAxis: 6},{xAxis: new Date('7/17/2020'), yAxis: 7},{xAxis: new Date('7/18/2020'), yAxis: 7},{xAxis: new Date('7/19/2020'), yAxis: 11},{xAxis: new Date('7/20/2020'), yAxis: 8},{xAxis: new Date('7/21/2020'), yAxis: 7},{xAxis: new Date('7/22/2020'), yAxis: 7},{xAxis: new Date('7/23/2020'), yAxis: 6},{xAxis: new Date('7/24/2020'), yAxis: 5},{xAxis: new Date('7/25/2020'), yAxis: 3},{xAxis: new Date('7/26/2020'), yAxis: 1},{xAxis: new Date('7/27/2020'), yAxis: 2},{xAxis: new Date('7/28/2020'), yAxis: 6},{xAxis: new Date('7/29/2020'), yAxis: 5},{xAxis: new Date('7/30/2020'), yAxis: 2},{xAxis: new Date('7/31/2020'), yAxis: 0},{xAxis: new Date('8/01/2020'), yAxis: 3},{xAxis: new Date('8/02/2020'), yAxis: 3},{xAxis: new Date('8/03/2020'), yAxis: 1},{xAxis: new Date('8/04/2020'), yAxis: 4},{xAxis: new Date('8/05/2020'), yAxis: 2},{xAxis: new Date('8/06/2020'), yAxis: 3},{xAxis: new Date('8/07/2020'), yAxis: 4},{xAxis: new Date('8/08/2020'), yAxis: 3},{xAxis: new Date('8/09/2020'), yAxis: 1},{xAxis: new Date('8/10/2020'), yAxis: 4},{xAxis: new Date('8/11/2020'), yAxis: 2},{xAxis: new Date('8/12/2020'), yAxis: 4},{xAxis: new Date('8/13/2020'), yAxis: 3},{xAxis: new Date('8/14/2020'), yAxis: 6},{xAxis: new Date('8/15/2020'), yAxis: 1},{xAxis: new Date('8/16/2020'), yAxis: 1},{xAxis: new Date('8/17/2020'), yAxis: 0},{xAxis: new Date('8/18/2020'), yAxis: 3},{xAxis: new Date('8/19/2020'), yAxis: 5},{xAxis: new Date('8/20/2020'), yAxis: 1},{xAxis: new Date('8/21/2020'), yAxis: 2},{xAxis: new Date('8/22/2020'), yAxis: 5},{xAxis: new Date('8/23/2020'), yAxis: 1},{xAxis: new Date('8/24/2020'), yAxis: 3},{xAxis: new Date('8/25/2020'), yAxis: 1},{xAxis: new Date('8/26/2020'), yAxis: 1},{xAxis: new Date('8/27/2020'), yAxis: 2},{xAxis: new Date('8/28/2020'), yAxis: 0},{xAxis: new Date('8/29/2020'), yAxis: 1},{xAxis: new Date('8/30/2020'), yAxis: 1},{xAxis: new Date('8/31/2020'), yAxis: 2},{xAxis: new Date('9/01/2020'), yAxis: 1},{xAxis: new Date('9/02/2020'), yAxis: 1},{xAxis: new Date('9/03/2020'), yAxis: 1},{xAxis: new Date('9/04/2020'), yAxis: 0},{xAxis: new Date('9/05/2020'), yAxis: 0},{xAxis: new Date('9/06/2020'), yAxis: 0}

];

demo.component.ts

We only need to pull in the DATA from data.ts and then make it accessible to our demo.component.html file which actually calls our chart component. Open demo.component.ts and replace the content with the following:

import { Component, OnInit } from '@angular/core';

import { ChartSeries } from '~app/feature/chart/chart';

import { DATA } from './data'; @Component({

selector: 'app-demo',

templateUrl: './demo.component.html',

styleUrls: ['./demo.component.scss']

})

export class DemoComponent implements OnInit { public data: ChartSeries[] = DATA; constructor() { } ngOnInit(): void {} }

demo.component.html

Let’s display the sparkline now. We’ll wrap it in a few DIVs to make it stand out. We will style these in the next section. Notice how we are sending [data]=”data” to the app-chart component.

<div class="card">

<div class="card-content">

<h1>5,835</h1>

<h4 class="color-grey">COVID-19 Related Deaths in Sweden</h4>

</div>

<div class="card-footer">

<div class="sparkline-wrapper">

<app-chart [data]="data"></app-chart>

</div>

</div>

</div>

Styles

All of this work is great, but unless it’s somewhat appealing to the eye — why are we here? Am I right? Let’s open up the styles.scss file in the root of the src directory.

@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600;800&display=swap'); :root {

font-family: 'Open Sans', sans-serif;

font-size: 12px;

} h1, h2, h3, h4, h5, h6 {

margin: 0px;

padding: 0px;

} h1 {

font-size: 3.4em;

} h4 {

font-weight: 600;

} .card {

align-content: flex-start;

align-items: flex-start;

border-radius: 8px;

box-shadow: 0 0 8px 0 rgba(40, 40, 40, .3);

display: flex;

flex: 1 0 300px;

flex-flow: row wrap;

height: auto;

justify-content: flex-start;

padding: 16px;

position: relative;

width: 300px;

} .card-content {

width: 100%;

} .card-footer {

margin-top: 16px;

width: 100%;

} .color-grey {

color: rgba(140, 140, 140, 1);

} .sparkline-wrapper {

align-self: flex-end;

display: flex;

height: 50px;

min-height: 50px;

width: 100%;

}

App

To finish up, we need to set up a few more items like styling and routing (so we can get to our demo component).

app-routing.module.ts

Let’s open our app-routing.module.ts file located at app/app-routing.module.ts and set up our route and default to the demo component. Routing is outside the scope of this article, so I would recommend a read on the Angular Router if you are unsure of what is happening here.

import { NgModule } from '@angular/core';

import { Routes, RouterModule } from '@angular/router'; import { DemoComponent } from '~app/feature/demo/demo.component'; const routes: Routes = [

{

path: '',

pathMatch: 'full',

redirectTo: 'demo',

},

{

component: DemoComponent,

path: 'demo'

}

]; @NgModule({

imports: [RouterModule.forRoot(routes)],

exports: [RouterModule]

})

export class AppRoutingModule { }

app.component.html

We’re going to clear out all the nonsense in our app/app.component.html file and replace it with a simple one liner. No really, this is all we want in this file.

<router-outlet></router-outlet>

Finished Product

If all has gone well, you’ll see a beautiful little card with a sparkline when you load your app (typically http://localhost:4200).

Git Repository

You can access the full project at github here: https://github.com/johnathanesanders/angular-d3-sparkline

Thanks for reading!

Tablet with charts photo by PhotoMIX Company from Pexels