Skip to content
This repository has been archived by the owner on Sep 30, 2022. It is now read-only.

Commit

Permalink
Simple D3 chart component example
Browse files Browse the repository at this point in the history
  • Loading branch information
keathmilligan committed Aug 26, 2017
1 parent 5b669f8 commit 96e5830
Show file tree
Hide file tree
Showing 10 changed files with 708 additions and 28 deletions.
526 changes: 522 additions & 4 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@
"@angular/platform-browser-dynamic": "^4.2.4",
"@angular/router": "^4.2.4",
"core-js": "^2.4.1",
"d3": "^4.10.0",
"rxjs": "^5.4.2",
"zone.js": "^0.8.14"
},
"devDependencies": {
"@angular/cli": "1.3.2",
"@angular/compiler-cli": "^4.2.4",
"@angular/language-service": "^4.2.4",
"@types/d3": "^4.10.0",
"@types/jasmine": "~2.5.53",
"@types/jasminewd2": "~2.0.2",
"@types/node": "~6.0.60",
Expand Down
22 changes: 2 additions & 20 deletions src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,20 +1,2 @@
<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
<h1>
Welcome to {{title}}!
</h1>
<img width="300" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxOS4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiDQoJIHZpZXdCb3g9IjAgMCAyNTAgMjUwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAyNTAgMjUwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDojREQwMDMxO30NCgkuc3Qxe2ZpbGw6I0MzMDAyRjt9DQoJLnN0MntmaWxsOiNGRkZGRkY7fQ0KPC9zdHlsZT4NCjxnPg0KCTxwb2x5Z29uIGNsYXNzPSJzdDAiIHBvaW50cz0iMTI1LDMwIDEyNSwzMCAxMjUsMzAgMzEuOSw2My4yIDQ2LjEsMTg2LjMgMTI1LDIzMCAxMjUsMjMwIDEyNSwyMzAgMjAzLjksMTg2LjMgMjE4LjEsNjMuMiAJIi8+DQoJPHBvbHlnb24gY2xhc3M9InN0MSIgcG9pbnRzPSIxMjUsMzAgMTI1LDUyLjIgMTI1LDUyLjEgMTI1LDE1My40IDEyNSwxNTMuNCAxMjUsMjMwIDEyNSwyMzAgMjAzLjksMTg2LjMgMjE4LjEsNjMuMiAxMjUsMzAgCSIvPg0KCTxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0xMjUsNTIuMUw2Ni44LDE4Mi42aDBoMjEuN2gwbDExLjctMjkuMmg0OS40bDExLjcsMjkuMmgwaDIxLjdoMEwxMjUsNTIuMUwxMjUsNTIuMUwxMjUsNTIuMUwxMjUsNTIuMQ0KCQlMMTI1LDUyLjF6IE0xNDIsMTM1LjRIMTA4bDE3LTQwLjlMMTQyLDEzNS40eiIvPg0KPC9nPg0KPC9zdmc+DQo=">
</div>
<h2>Here are some links to help you start: </h2>
<ul>
<li>
<h2><a target="_blank" href="https://angular.io/tutorial">Tour of Heroes</a></h2>
</li>
<li>
<h2><a target="_blank" href="https://github.com/angular/angular-cli/wiki">CLI Documentation</a></h2>
</li>
<li>
<h2><a target="_blank" href="https://blog.angular.io//">Angular blog</a></h2>
</li>
</ul>

<h3>Bar Chart</h3>
<app-bar-chart *ngIf="chartData" [data]="chartData"></app-bar-chart>
28 changes: 25 additions & 3 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app';
export class AppComponent implements OnInit {
private chartData: Array<any>;

constructor() {}

ngOnInit() {
// give everything a chance to get loaded before starting the animation to reduce choppiness
setTimeout(() => {
this.generateData();

// change the data periodically
setInterval(() => this.generateData(), 3000);
}, 1000);
}

generateData() {
this.chartData = [];
for (let i = 0; i < (8 + Math.floor(Math.random() * 10)); i++) {
this.chartData.push([
`Index ${i}`,
Math.floor(Math.random() * 100)
]);
}
}
}
4 changes: 3 additions & 1 deletion src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { BarChartComponent } from './shared/bar-chart/bar-chart.component';

@NgModule({
declarations: [
AppComponent
AppComponent,
BarChartComponent
],
imports: [
BrowserModule
Expand Down
13 changes: 13 additions & 0 deletions src/app/shared/bar-chart/bar-chart.component.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.d3-chart {
width: 100%;
height: 400px;
}

.d3-chart .axis path,
.d3-chart .axis line {
stroke: #999;
}

.d3-chart .axis text {
fill: #999;
}
1 change: 1 addition & 0 deletions src/app/shared/bar-chart/bar-chart.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div class="d3-chart" #chart></div>
25 changes: 25 additions & 0 deletions src/app/shared/bar-chart/bar-chart.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { BarChartComponent } from './bar-chart.component';

describe('BarChartComponent', () => {
let component: BarChartComponent;
let fixture: ComponentFixture<BarChartComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ BarChartComponent ]
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(BarChartComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should be created', () => {
expect(component).toBeTruthy();
});
});
110 changes: 110 additions & 0 deletions src/app/shared/bar-chart/bar-chart.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { Component, OnInit, OnChanges, ViewChild, ElementRef, Input, ViewEncapsulation } from '@angular/core';
import * as d3 from 'd3';

@Component({
selector: 'app-bar-chart',
templateUrl: './bar-chart.component.html',
styleUrls: ['./bar-chart.component.css'],
encapsulation: ViewEncapsulation.None
})
export class BarChartComponent implements OnInit, OnChanges {
@ViewChild('chart') private chartContainer: ElementRef;
@Input() private data: Array<any>;
private margin: any = { top: 20, bottom: 20, left: 20, right: 20};
private chart: any;
private width: number;
private height: number;
private xScale: any;
private yScale: any;
private colors: any;
private xAxis: any;
private yAxis: any;

constructor() { }

ngOnInit() {
this.createChart();
if (this.data) {
this.updateChart();
}
}

ngOnChanges() {
if (this.chart) {
this.updateChart();
}
}

createChart() {
const element = this.chartContainer.nativeElement;
this.width = element.offsetWidth - this.margin.left - this.margin.right;
this.height = element.offsetHeight - this.margin.top - this.margin.bottom;
const svg = d3.select(element).append('svg')
.attr('width', element.offsetWidth)
.attr('height', element.offsetHeight);

// chart plot area
this.chart = svg.append('g')
.attr('class', 'bars')
.attr('transform', `translate(${this.margin.left}, ${this.margin.top})`);

// define X & Y domains
const xDomain = this.data.map(d => d[0]);
const yDomain = [0, d3.max(this.data, d => d[1])];

// create scales
this.xScale = d3.scaleBand().padding(0.1).domain(xDomain).rangeRound([0, this.width]);
this.yScale = d3.scaleLinear().domain(yDomain).range([this.height, 0]);

// bar colors
this.colors = d3.scaleLinear().domain([0, this.data.length]).range(<any[]>['red', 'blue']);

// x & y axis
this.xAxis = svg.append('g')
.attr('class', 'axis axis-x')
.attr('transform', `translate(${this.margin.left}, ${this.margin.top + this.height})`)
.call(d3.axisBottom(this.xScale));
this.yAxis = svg.append('g')
.attr('class', 'axis axis-y')
.attr('transform', `translate(${this.margin.left}, ${this.margin.top})`)
.call(d3.axisLeft(this.yScale));
}

updateChart() {
// update scales & axis
this.xScale.domain(this.data.map(d => d[0]));
this.yScale.domain([0, d3.max(this.data, d => d[1])]);
this.colors.domain([0, this.data.length]);
this.xAxis.transition().call(d3.axisBottom(this.xScale));
this.yAxis.transition().call(d3.axisLeft(this.yScale));

const update = this.chart.selectAll('.bar')
.data(this.data);

// remove exiting bars
update.exit().remove();

// update existing bars
this.chart.selectAll('.bar').transition()
.attr('x', d => this.xScale(d[0]))
.attr('y', d => this.yScale(d[1]))
.attr('width', d => this.xScale.bandwidth())
.attr('height', d => this.height - this.yScale(d[1]))
.style('fill', (d, i) => this.colors(i));

// add new bars
update
.enter()
.append('rect')
.attr('class', 'bar')
.attr('x', d => this.xScale(d[0]))
.attr('y', d => this.yScale(0))
.attr('width', this.xScale.bandwidth())
.attr('height', 0)
.style('fill', (d, i) => this.colors(i))
.transition()
.delay((d, i) => i * 10)
.attr('y', d => this.yScale(d[1]))
.attr('height', d => this.height - this.yScale(d[1]));
}
}
5 changes: 5 additions & 0 deletions src/styles.css
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
/* You can add global styles to this file, and also import other style files */
body {
font-family: "Segoe UI", Helvetica, Arial, sans-serif;
color: #eee;
background-color: black;
}

0 comments on commit 96e5830

Please sign in to comment.