This article shows how the Plotly javascript library can be used inside an Angular 2 Component. The Angular 2 component can then be used anywhere inside an application using only the Angular Component selector. The data used for the chart is provided in an ASP.NET Core MVC application using Elasticsearch.
Code: https://github.com/damienbod/AngularComponentPlotly
2017.02.07: Updated Updated angular-cli, ASP.NET Core 1.1.0 VS2017 RC3
2016.08.18: Updated to Angular 2 release, ASP.NET Core 1.0.1
2016.07.01: Updated to ASP.NET Core RTM
2016.06.26: Updated to Angular2 rc3 and new routing
2016.06.21: Updated to Angular2 rc2 and angular-cli beta 6
2016.05.06: Updated to ASP.NET Core RC2 and ElasticsearchCrud dotnet RC2
2016.05.06: Updated Angular 2 to rc1
Split solution into 2 projects, UI and service.
Service is a ASP.NET core MVC service RC1
UI in an Angular2 RC1 project running on nodejs.
Setup
The Angular 2 project is setup using angular-cli. This project is then run from the command line using nodejs. The ASP.NET Core MVC service needs to be running before the application can request the data.
Angular 2 Plotly Component
The Plotly component is defined using the plotlychart selector. This Angular 2 selector can then be used in templates to add the component to existing ones. The component uses the template property to define the HTML template. The Plotly component has 4 input properties. The properties are used to pass the chart data into the component and also define if the chart raw data should be displayed or not. The raw data and the layout data are displayed in the HTML template using pipes.
The Plotly javascript library has no typescript definitions. Because of this, the ‘declare var’ is used so that the Plotly javascript library can be used inside the typescript class.
import { Component, EventEmitter, Input, Output, OnInit, ElementRef} from '@angular/core'; import { Subscription } from 'rxjs/Subscription'; import { Observable } from 'rxjs/Observable'; declare var Plotly: any; @Component({ selector: 'plotlychart', template: ` <div style="margin-bottom:100px;"> <div id="myPlotlyDiv" name="myPlotlyDiv" style="width: 480px; height: 400px;"> <!-- Plotly chart will be drawn inside this DIV --> </div> </div> <div *ngIf="displayRawData"> raw data: <hr /> <span>{{data | json}}</span> <hr /> layout: <hr /> <span>{{layout | json}}</span> <hr /> </div> `, styleUrls: ['plotly.component.css'] }) export class PlotlyComponent implements OnInit { @Input() data: any; @Input() layout: any; @Input() options: any; @Input() displayRawData: boolean; ngOnInit() { console.log("ngOnInit PlotlyComponent"); console.log(this.data); console.log(this.layout); Plotly.newPlot('myPlotlyDiv', this.data, this.layout, this.options); } }
The Plotly library is used inside an Angular 2 component. This needs the be added in the head of the index.html file where the app is bootstrapped.
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Angular 2 cli plotly</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> <script src="app/plotly/plotly.min.js"></script> </head> <body> <app-root>Loading...</app-root> </body> </html>
Using the Angular 2 Plotly Component
The Plotly component is used in the RegionComponent component. This component gets the data from the server, and adds it using the defined input parameters of the Plotly directive component. The HTML template uses the plotlychart directive. This takes four properties with the required Json objects. The component is only used after the data has been got from the server using Angular 2 ngIf.
<div *ngIf="PlotlyData"> <plotlychart [data]="PlotlyData" [layout]="PlotlyLayout" [options]="PlotlyOptions" [displayRawData]="true"> </plotlychart> </div>
The RegionComponent adds an import for the PlotlyComponent, the required model classes and the Angular 2 service which are used to retrieve the chart data from the server. The PlotlyComponent is defined as a directive inside the @Component. When the component is initialized, the service GetRegionBarChartData function is used to GET the data and returns an GeographicalCountries observable object. This data is then used to prepare the Json objects which can be used to create the Plotly chart. In this demo, the data is prepared for a vertical bar chart. See the Plotly Javascript documentation for this.
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs/Observable'; import { PlotlyComponent } from '../plotly/plotly.component'; import { SnakeDataService } from '../snake-data.service'; import { GeographicalRegion } from '../models/GeographicalRegion'; import { GeographicalCountries } from '../models/GeographicalCountries'; import { BarTrace } from '../models/BarTrace'; @Component({ selector: 'app-region', templateUrl: 'region.component.html', styleUrls: ['region.component.css'] }) export class RegionComponent implements OnInit, OnDestroy { private id: number; public message: string; private sub: any; public GeographicalCountries: GeographicalCountries; private name: string; public PlotlyLayout: any; public PlotlyData: any; public PlotlyOptions: any; constructor( private _snakeDataService: SnakeDataService, private _route: ActivatedRoute, private _router: Router ) { this.message = "region"; } ngOnInit() { this.sub = this._route.params.subscribe(params => { this.name = params['name']; if (!this.GeographicalCountries) { this.getGetRegionBarChartData(); } }); } ngOnDestroy() { this.sub.unsubscribe(); } private getGetRegionBarChartData() { console.log('RegionComponent:getData starting...'); this._snakeDataService .GetRegionBarChartData(this.name) .subscribe(data => this.setReturnedData(data), error => console.log(error), () => console.log('Get GeographicalCountries completed for region')); } private setReturnedData(data: any) { this.GeographicalCountries = data; this.PlotlyLayout = { title: this.GeographicalCountries.RegionName + ": Number of snake bite deaths", height: 500, width: 1200 }; this.PlotlyData = [ { x: this.GeographicalCountries.X, y: this.getYDatafromDatPoint(), name: "Number of snake bite deaths", type: 'bar', orientation: 'v' } ]; console.log("recieved plotly data"); console.log(this.PlotlyData); } private getYDatafromDatPoint() { return this.GeographicalCountries.NumberOfDeathsHighData.Y; } }
The Angular 2 service is used to access the ASP.NET Core MVC service. This uses the Http, Response, and Headers from the angular2/http import. The service is marked as an @Injectable() object. The headers are used the configure the HTTP request with the standard headers. An Observable of type T is returned, which can be consumed by the calling component. A promise could also be returned, if required.
The service is added to the Angular 2 application using component providers. This is a singleton object for the component where the providers are defined and all child components of this component. In this demo application, the SnakeDataService is defined in the top level AppComponent component.
import { Injectable } from '@angular/core'; import { Http, Response, Headers } from '@angular/http'; import 'rxjs/add/operator/map' import { Observable } from 'rxjs/Observable'; import { Configuration } from './app.constants'; import { GeographicalRegion } from './models/GeographicalRegion'; import { GeographicalCountries } from './models/GeographicalCountries'; @Injectable() export class SnakeDataService { private actionUrl: string; private headers: Headers; constructor(private _http: Http, private _configuration: Configuration) { this.actionUrl = `${_configuration.Server}api/SnakeData/`; } private setHeaders() { this.headers = new Headers(); this.headers.append('Content-Type', 'application/json'); this.headers.append('Accept', 'application/json'); } public GetGeographicalRegions = (): Observable<GeographicalRegion[]> => { this.setHeaders(); return this._http.get(`${this.actionUrl}GeographicalRegions`, { headers: this.headers }).map(res => res.json()); } public GetRegionBarChartData = (region: string): Observable<GeographicalCountries> => { this.setHeaders(); return this._http.get(`${this.actionUrl}RegionBarChart/${region}`, { headers: this.headers }).map(res => res.json()); } }
The model classes are used to define the service DTOs used in the HTTP requests. These model classes contain all the data required to produce a Plotly bar chart.
import { BarTrace } from './BarTrace'; export class GeographicalCountries { NumberOfCasesLowData: BarTrace; NumberOfCasesHighData: BarTrace; NumberOfDeathsLowData: BarTrace; NumberOfDeathsHighData: BarTrace; RegionName: string; X: string[]; } export class BarTrace { Y: number[]; } export class GeographicalRegion { Name: string; Countries: number; NumberOfCasesHigh: number; NumberOfDeathsHigh: number; DangerHigh: boolean; }
ASP.NET Core MVC API using Elasticsearch
An ASP.NET Core MVC service is used as a data source for the Angular 2 application. Details on how this is setup can be found here:
Plotly charts using Angular, ASP.NET Core 1.0 and Elasticsearch
Notes:
One problem when developing with Angular 2 router, it that when something goes wrong, no logs, or diagnostics exist for this router. This is a major disadvantage compared to Angular UI Router. For example, if a child component has a run time problem, the ngInit method is not called for any component with the first request. This has nothing to do with the real problem, and you have no information on how to debug this. Big pain.
Links
https://angular.io/docs/ts/latest/guide/router.html
http://www.codeproject.com/Articles/1087605/Angular-typescript-configuration-and-debugging-for
https://auth0.com/blog/2016/01/25/angular-2-series-part-4-component-router-in-depth/
https://github.com/johnpapa/angular-styleguide
https://mgechev.github.io/angular2-style-guide/
https://toddmotto.com/component-events-event-emitter-output-angular-2
http://blog.thoughtram.io/angular/2016/03/21/template-driven-forms-in-angular-2.html
http://raibledesigns.com/rd/entry/getting_started_with_angular_2
https://toddmotto.com/transclusion-in-angular-2-with-ng-content
http://www.bennadel.com/blog/3062-creating-an-html-dropdown-menu-component-in-angular-2-beta-11.htm
http://asp.net-hacker.rocks/2016/04/04/aspnetcore-and-angular2-part1.html
https://github.com/alonho/angular-plotly
plotly.js has typings now @types/plotly.js
Could you update your example as well? Many thanks
Thanks for the info, will do
Greetings Damien
Thanks for this, I found it invaluable for getting a reusable component working.
I did run into an issue where if I tried to add multiple charts there was a collision on the myPlotlyDiv ID.
Not sure if my solution is a “good” one, but I’ll leave it here in case someone else runs into the same issue and it proves useful:
I got around it by dropping the id and name in the template and adding a local variable:
And then used a @ViewChild to get an ElementRef to that local variable.
@ViewChild(‘myPlotlyDiv’) plotlyDiv: ElementRef;
Which I then used when creating the new plot:
Plotly.newPlot(this.plotlyDiv.nativeElement, this.data, this.layout, this.options);
[…] added a plotly component to my project following this guide. When the user clicks on the bar chart, I need to get the data corresponding to the bar that the […]