Quantcast
Channel: damienbod – Software Engineering
Viewing all articles
Browse latest Browse all 353

Creating an Angular 2 Component for Plotly

$
0
0

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/Angular2ComponentPlotly

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, Input, OnInit} from 'angular2/core';
import { CORE_DIRECTIVES } from 'angular2/common';
import { Subscription } from 'rxjs/Subscription';
import { Observable } from 'rxjs/Observable';
import { Router, ROUTER_DIRECTIVES } from 'angular2/router';

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>
`,
    directives: [CORE_DIRECTIVES, ROUTER_DIRECTIVES]
})

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>
    <base href="/" />
    <title>ASP.NET Core 1.0 Angular 2</title>
    <link rel="stylesheet" href="css/bootstrap.css">
    <script src="app/plotly/plotly.min.js"></script>
</head>

<body>
    <my-app>Loading...</my-app>

    <!-- 1. Load libraries -->
    <!-- IE required polyfills, in this exact order -->
    <script src="libs/es6-shim.min.js"></script>
    <script src="libs/system-polyfills.js"></script>
    <script src="libs/shims_for_ie.js"></script>
    <script src="libs/angular2-polyfills.js"></script>
    <script src="libs/system.js"></script>
    <script src="libs/Rx.js"></script>
    <script src="libs/angular2.dev.js"></script>
    <script src="libs/jquery.min.js"></script>
    <script src="js/bootstrap.js"></script>
    <script src="libs/router.dev.js"></script>
    <script src="libs/http.dev.js"></script>

    <!-- 2. Configure SystemJS -->
    <script>
        System.config({
            meta: {
                '*.js' : {
                    scriptLoad: true
                }
            },
            packages: {
                app: {
                    defaultExtension: 'js'
                }
            }
        });
        System.import('app/boot')
            .then(null, console.error.bind(console));
    </script>
</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 } from 'angular2/core';
import { CORE_DIRECTIVES } from 'angular2/common';
import { RouteParams, Router, ROUTER_DIRECTIVES } from 'angular2/router';

import { Observable }       from 'rxjs/Observable';

import { PlotlyComponent } from '../plotly/plotly.component';
import { SnakeDataService } from '../services/SnakeDataService';
import { GeographicalRegion } from '../models/GeographicalRegion';
import { GeographicalCountries } from '../models/GeographicalCountries';
import { BarTrace } from '../models/BarTrace';

@Component({
    selector: 'region',
    templateUrl: 'app/region/region.component.html',
    directives: [CORE_DIRECTIVES, ROUTER_DIRECTIVES, PlotlyComponent]
})

export class RegionComponent implements OnInit {

    public message: string;
    public GeographicalCountries: GeographicalCountries;

    private name: string;
    public PlotlyLayout: any;
    public PlotlyData: any;
    public PlotlyOptions: any;

    constructor(
        private _snakeDataService: SnakeDataService,
        private _routeParams: RouteParams,
        private _router: Router
    ) {
        this.message = "region";
    }

    ngOnInit() {
        this.name = this._routeParams.get('name');
        console.log("ngOnInit RegionComponent");
        if (!this.GeographicalCountries) {
            this.getGetRegionBarChartData();
        }
    }

    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 'angular2/core';
import { Http, Response, Headers } from 'angular2/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://plot.ly/javascript/

https://github.com/alonho/angular-plotly

https://www.elastic.co/products/elasticsearch



Viewing all articles
Browse latest Browse all 353

Trending Articles