File

libs/plotly/src/lib/plotly-profile-graph/plotly-profile-graph.component.ts

Extends

DatasetPresenterComponent

Implements

AfterViewInit

Metadata

selector n52-plotly-profile-graph
styleUrls ./plotly-profile-graph.component.scss
templateUrl ./plotly-profile-graph.component.html

Index

Properties
Methods
Inputs
Outputs
HostListeners

Constructor

constructor(iterableDiffers: IterableDiffers, servicesConnector: HelgolandServicesConnector, datasetIdResolver: InternalIdHandler, timeSrvc: Time, translateSrvc: TranslateService, timezoneSrvc: TimezoneService)
Parameters :
Name Type Optional
iterableDiffers IterableDiffers No
servicesConnector HelgolandServicesConnector No
datasetIdResolver InternalIdHandler No
timeSrvc Time No
translateSrvc TranslateService No
timezoneSrvc TimezoneService No

Inputs

datasetIds
Type : string[]
Default value : []

List of presented dataset ids.

datasetOptions
Type : Map<string | T>

The corresponding dataset options.

presenterOptions
Type : U

Options for general presentation of the data.

reloadForDatasets
Type : string[]

List of datasets for which a reload should be triggered, when the Array is set to new value.

selectedDatasetIds
Type : string[]
Default value : []

List of presented selected dataset ids.

timeInterval
Type : TimeInterval

The time interval in which the data should presented.

Outputs

onHighlight
Type : EventEmitter<PresenterHighlight>
dataLoaded
Type : EventEmitter<Set<string>>

Event, which triggers list of datasets where data is currently loaded.

onContentLoading
Type : EventEmitter<boolean>

Event flag, while there is data loaded in the component.

onDatasetSelected
Type : EventEmitter<string[]>

Event with a list of selected datasets.

onMessageThrown
Type : EventEmitter<PresenterMessage>

Event, when there occured a message in the component.

onTimespanChanged
Type : EventEmitter<Timespan>

Event when the timespan in the presentation is adjusted.

HostListeners

window:resize
Arguments : '$event'
window:resize(event: Event)
Inherited from ResizableComponent

Methods

Protected addDataset
addDataset(id: string, url: string)
Parameters :
Name Type Optional
id string No
url string No
Returns : void
Private clearData
clearData()
Returns : void
Private clearLayout
clearLayout()
Returns : void
Private createXAxis
createXAxis(dataset: HelgolandProfile, data: ProfileDataEntry)
Parameters :
Name Type Optional
dataset HelgolandProfile No
data ProfileDataEntry No
Returns : string
Private createYAxis
createYAxis(dataset: HelgolandProfile, data: ProfileDataEntry)
Parameters :
Name Type Optional
dataset HelgolandProfile No
data ProfileDataEntry No
Returns : string
Protected datasetOptionsChanged
datasetOptionsChanged(internalId: string, options: TimedDatasetOptions[], firstChange: boolean)
Parameters :
Name Type Optional
internalId string No
options TimedDatasetOptions[] No
firstChange boolean No
Returns : void
Private drawChart
drawChart()
Returns : void
Public ngAfterViewInit
ngAfterViewInit()
Returns : void
Protected onLanguageChanged
onLanguageChanged(langChangeEvent: LangChangeEvent)
Parameters :
Name Type Optional
langChangeEvent LangChangeEvent No
Returns : void
Protected onResize
onResize()
Returns : void
Protected onTimezoneChanged
onTimezoneChanged(timezone: string)
Parameters :
Name Type Optional
timezone string No
Returns : void
Protected presenterOptionsChanged
presenterOptionsChanged(options: any)
Parameters :
Name Type Optional
options any No
Returns : void
Private processData
processData()
Returns : void
Private redrawChart
redrawChart()
Returns : void
Public reloadDataForDatasets
reloadDataForDatasets(datasetIds: string[])
Parameters :
Name Type Optional
datasetIds string[] No
Returns : void
Protected removeDataset
removeDataset(internalId: string)
Parameters :
Name Type Optional
internalId string No
Returns : void
Protected removeSelectedId
removeSelectedId(internalId: string)
Parameters :
Name Type Optional
internalId string No
Returns : void
Protected setSelectedId
setSelectedId(internalId: string)
Parameters :
Name Type Optional
internalId string No
Returns : void
Protected timeIntervalChanges
timeIntervalChanges()
Returns : void
Private updateAxis
updateAxis()
Returns : void
Protected Abstract addDataset
addDataset(id: string, url: string)
Parameters :
Name Type Optional
id string No
url string No
Returns : void
Protected addDatasetByInternalId
addDatasetByInternalId(internalId: string)
Parameters :
Name Type Optional
internalId string No
Returns : void
Protected Abstract datasetOptionsChanged
datasetOptionsChanged(internalId: string, options: T, firstChange: boolean)
Parameters :
Name Type Optional
internalId string No
options T No
firstChange boolean No
Returns : void
Public ngDoCheck
ngDoCheck()
Returns : void
Public ngOnChanges
ngOnChanges(changes: SimpleChanges)
Parameters :
Name Type Optional
changes SimpleChanges No
Returns : void
Public ngOnDestroy
ngOnDestroy()
Returns : void
Protected Abstract onLanguageChanged
onLanguageChanged(langChangeEvent: LangChangeEvent)
Parameters :
Name Type Optional
langChangeEvent LangChangeEvent No
Returns : void
Protected Abstract onTimezoneChanged
onTimezoneChanged(timezone: string)
Parameters :
Name Type Optional
timezone string No
Returns : void
Protected Abstract presenterOptionsChanged
presenterOptionsChanged(options: U)
Parameters :
Name Type Optional
options U No
Returns : void
Public Abstract reloadDataForDatasets
reloadDataForDatasets(datasets: string[])
Parameters :
Name Type Optional
datasets string[] No
Returns : void
Protected Abstract removeDataset
removeDataset(internalId: string)
Parameters :
Name Type Optional
internalId string No
Returns : void
Protected Abstract removeSelectedId
removeSelectedId(internalId: string)
Parameters :
Name Type Optional
internalId string No
Returns : void
Protected Abstract setSelectedId
setSelectedId(internalId: string)
Parameters :
Name Type Optional
internalId string No
Returns : void
Protected Abstract timeIntervalChanges
timeIntervalChanges()
Returns : void
Protected Abstract onResize
onResize()
Inherited from ResizableComponent
Returns : void

Properties

Private counterXAxis
Type : number
Default value : 0
Private counterYAxis
Type : number
Default value : 0
Private layout
Type : Layout
Default value : { autosize: true, showlegend: false, dragmode: 'pan', margin: { l: 40, r: 10, b: 40, t: 10 // pad: 100 }, hovermode: 'closest' }
Private plotlyArea
Type : any
Public plotlyElem
Type : ElementRef
Decorators :
@ViewChild('plotly', {static: true})
Private preparedData
Type : ExtendedScatterData[]
Default value : []
Private rawData
Type : Map<string | RawData>
Default value : new Map()
Private settings
Type : Partial<any>
Default value : { displayModeBar: false, modeBarButtonsToRemove: [ 'sendDataToCloud', 'hoverCompareCartesian' ], displaylogo: false, showTips: false, scrollZoom: true }
Private datasetIdsDiffer
Type : IterableDiffer<string>
Private langChangeSubscription
Type : Subscription
Protected oldDatasetOptions
Type : Map<string | T>
Protected oldPresenterOptions
Type : U
Private selectedDatasetIdsDiffer
Type : IterableDiffer<string>
Protected timespan
Type : Timespan
Private timezoneSubscription
Type : Subscription
import { AfterViewInit, Component, ElementRef, EventEmitter, IterableDiffers, Output, ViewChild } from '@angular/core';
import {
    DatasetPresenterComponent,
    DatasetType,
    HelgolandProfile,
    HelgolandServicesConnector,
    InternalIdHandler,
    PresenterHighlight,
    ProfileDataEntry,
    Time,
    TimedDatasetOptions,
    Timespan,
    TimezoneService,
} from '@helgoland/core';
import { LangChangeEvent, TranslateService } from '@ngx-translate/core';
import * as d3 from 'd3';
import * as Plotly from 'plotly.js';

interface RawData {
    dataset: HelgolandProfile;
    datas: ProfileDataEntry[];
    options: TimedDatasetOptions[];
}

interface ExtendedScatterData extends Partial<Plotly.ScatterData> {
    timestamp: number;
    id: string;
}

const LINE_WIDTH_SELECTED = 5;
const LINE_WIDTH = 2;
const MARKER_SIZE_SELECTED = 10;
const MARKER_SIZE = 6;

@Component({
    selector: 'n52-plotly-profile-graph',
    templateUrl: './plotly-profile-graph.component.html',
    styleUrls: ['./plotly-profile-graph.component.scss']
})
export class PlotlyProfileGraphComponent
    extends DatasetPresenterComponent<TimedDatasetOptions[], any>
    implements AfterViewInit {

    @Output()
    public onHighlight: EventEmitter<PresenterHighlight> = new EventEmitter();

    @ViewChild('plotly', { static: true })
    public plotlyElem: ElementRef;

    private plotlyArea: any;
    private preparedData: ExtendedScatterData[] = [];
    private rawData: Map<string, RawData> = new Map();
    private counterXAxis = 0;
    private counterYAxis = 0;

    private layout: Layout = {
        autosize: true,
        showlegend: false,
        dragmode: 'pan',
        margin: {
            l: 40,
            r: 10,
            b: 40,
            t: 10
            // pad: 100
        },
        hovermode: 'closest'
    };

    private settings: Partial<any> = {
        displayModeBar: false,
        modeBarButtonsToRemove: [
            'sendDataToCloud',
            'hoverCompareCartesian'
        ],
        displaylogo: false,
        showTips: false,
        scrollZoom: true
    };

    constructor(
        protected iterableDiffers: IterableDiffers,
        protected servicesConnector: HelgolandServicesConnector,
        protected datasetIdResolver: InternalIdHandler,
        protected timeSrvc: Time,
        protected translateSrvc: TranslateService,
        protected timezoneSrvc: TimezoneService
    ) {
        super(iterableDiffers, servicesConnector, datasetIdResolver, timeSrvc, translateSrvc, timezoneSrvc);
    }

    public ngAfterViewInit(): void {
        this.plotlyArea = this.plotlyElem.nativeElement;
        this.drawChart();
    }

    protected onLanguageChanged(langChangeEvent: LangChangeEvent): void { }

    protected onTimezoneChanged(timezone: string): void { }

    public reloadDataForDatasets(datasetIds: string[]): void {
        console.log('reload data at ' + new Date());
    }

    // tslint:disable-next-line:no-empty
    protected timeIntervalChanges(): void { }

    protected addDataset(id: string, url: string): void {
        this.servicesConnector.getDataset({ id, url }, { type: DatasetType.Profile }).subscribe(dataset => {
            const options = this.datasetOptions.get(dataset.internalId);
            options.forEach((option) => {
                if (option.timestamp) {
                    const timespan = new Timespan(option.timestamp);
                    this.servicesConnector.getDatasetData(dataset, timespan).subscribe(data => {
                        if (data.values.length === 1) {
                            if (this.rawData.has(dataset.internalId)) {
                                this.rawData.get(dataset.internalId).datas.push(data.values[0]);
                                this.rawData.get(dataset.internalId).options.push(option);
                            } else {
                                this.rawData.set(dataset.internalId, {
                                    dataset,
                                    datas: [data.values[0]],
                                    options: [option]
                                });
                            }
                        }
                        this.drawChart();
                    });
                }
            });
        });
    }

    protected removeDataset(internalId: string): void {
        this.rawData.delete(internalId);
        this.drawChart();
    }

    protected setSelectedId(internalId: string): void {
        this.drawChart();
    }

    protected removeSelectedId(internalId: string): void {
        this.drawChart();
    }

    // tslint:disable-next-line:no-empty
    protected presenterOptionsChanged(options: any): void { }

    protected datasetOptionsChanged(internalId: string, options: TimedDatasetOptions[], firstChange: boolean): void {
        if (!firstChange) {
            // remove unused options
            const removedIdx = this.rawData.get(internalId).options.findIndex((option) => {
                const idx = options.findIndex((e) => e.timestamp === option.timestamp);
                if (idx === -1) {
                    return true;
                }
            });
            if (removedIdx > -1) {
                this.rawData.get(internalId).options.splice(removedIdx, 1);
                this.rawData.get(internalId).datas.splice(removedIdx, 1);
            }
            this.drawChart();
        }
    }

    protected onResize(): void {
        this.redrawChart();
    }

    private processData() {
        this.clearLayout();
        this.clearData();
        this.rawData.forEach((dataEntry) => {
            dataEntry.options.forEach((option, key) => {
                if (option.visible) {
                    const x = new Array<number>();
                    const y = new Array<number>();
                    const selected = this.selectedDatasetIds.indexOf(dataEntry.dataset.internalId) >= 0;
                    dataEntry.datas[key].value.forEach((entry) => {
                        x.push(entry.value);
                        y.push(entry.vertical);
                    });
                    const prepared: ExtendedScatterData = {
                        x,
                        y,
                        type: 'scatter',
                        name: '',
                        timestamp: option.timestamp,
                        id: dataEntry.dataset.internalId,
                        yaxis: this.createYAxis(dataEntry.dataset, dataEntry.datas[key]),
                        xaxis: this.createXAxis(dataEntry.dataset, dataEntry.datas[key]),
                        // hovertext: dataEntry.label,
                        line: {
                            color: option.color,
                            width: selected ? LINE_WIDTH_SELECTED : LINE_WIDTH
                        },
                        marker: {
                            size: selected ? MARKER_SIZE_SELECTED : MARKER_SIZE
                        }
                    };
                    this.preparedData.push(prepared);
                }
            });
        });

        this.updateAxis();
    }

    private createXAxis(dataset: HelgolandProfile, data: ProfileDataEntry): string {
        let axis;
        for (const key in this.layout) {
            if (this.layout.hasOwnProperty(key) && key.startsWith('xaxis') && this.layout[key].title === dataset.uom) {
                axis = this.layout[key];
            }
        }
        const range = d3.extent(data.value, (d) => d.value);
        if (!axis) {
            this.counterXAxis = this.counterXAxis + 1;
            axis = this.layout['xaxis' + this.counterXAxis] = {
                id: 'x' + (this.counterXAxis > 1 ? this.counterXAxis : ''),
                anchor: 'free',
                title: dataset.uom,
                zeroline: true,
                hoverformat: '.2f',
                showline: false,
                range: [range[0], range[1]],
                overlaying: '',
                // rangemode: 'tozero',
                fixedrange: false
            };
            if (this.counterXAxis !== 1) {
                axis.overlaying = 'x';
            }
        } else {
            axis.range = d3.extent([range[0], range[1], axis.range[0], axis.range[1]]);
        }
        return axis.id;
    }

    private createYAxis(dataset: HelgolandProfile, data: ProfileDataEntry): string {
        let axis;
        // find axis
        for (const key in this.layout) {
            if (this.layout.hasOwnProperty(key) &&
                key.startsWith('yaxis') &&
                this.layout[key].title === data.verticalUnit) {
                axis = this.layout[key];
            }
        }
        if (!axis) {
            // add axis
            this.counterYAxis = this.counterYAxis + 1;
            axis = this.layout[('yaxis' + this.counterYAxis)] = {
                id: 'y' + (this.counterYAxis > 1 ? this.counterYAxis : ''),
                // zeroline: true,
                anchor: 'free',
                hoverformat: '.2r',
                side: 'left',
                autorange: 'reversed',
                showline: false,
                overlaying: '',
                title: data.verticalUnit,
                fixedrange: false
            };
            if (this.counterYAxis !== 1) {
                axis.overlaying = 'y';
            }
        }
        return axis.id;
    }

    private updateAxis() {
        if (this.counterYAxis > 1) {
            for (const key in this.layout) {
                if (this.layout.hasOwnProperty(key) && key.startsWith('xaxis')) {
                    this.layout[key].domain = [(0.1 * this.counterYAxis) - 0.1, 1];
                }
            }
            let yaxisCount = 0;
            for (const key in this.layout) {
                if (this.layout.hasOwnProperty(key) && key.startsWith('yaxis')) {
                    this.layout[key].position = 0.1 * yaxisCount;
                    yaxisCount += 1;
                }
            }
        }
        if (this.counterXAxis > 1) {
            for (const key in this.layout) {
                if (this.layout.hasOwnProperty(key) && key.startsWith('yaxis')) {
                    this.layout[key].domain = [(0.06 * this.counterXAxis) - 0.06, 1];
                }
            }
            let xaxisCount = 0;
            for (const key in this.layout) {
                if (this.layout.hasOwnProperty(key) && key.startsWith('xaxis')) {
                    this.layout[key].position = 0.06 * xaxisCount;
                    xaxisCount += 1;
                }
            }
        }
        // add offset to xaxis ranges
        for (const key in this.layout) {
            if (this.layout.hasOwnProperty(key) && key.startsWith('xaxis')) {
                const range = this.layout[key].range;
                const rangeOffset = (range[1] - range[0]) * 0.05;
                this.layout[key].range = [range[0] - rangeOffset, range[1] + rangeOffset];
            }
        }
    }

    private drawChart() {
        if (this.plotlyArea && this.rawData.size > 0) {
            this.processData();
            Plotly.newPlot(this.plotlyArea, this.preparedData, this.layout, this.settings);
            this.plotlyArea.on('plotly_hover', (entry: any) => {
                if (entry.points.length === 1) {
                    this.onHighlight.emit({
                        internalId: entry.points[0].data.id,
                        dataIndex: entry.points[0].pointNumber
                    });
                }
            });
        }
    }

    private clearLayout() {
        // todo remove yaxis
        for (const key in this.layout) {
            if (this.layout.hasOwnProperty(key) && (key.startsWith('yaxis') || key.startsWith('xaxis'))) {
                delete this.layout[key];
            }
        }
        // reset counter
        this.counterYAxis = 0;
        this.counterXAxis = 0;
    }

    private clearData() {
        this.preparedData = [];
    }

    private redrawChart() {
        if (this.plotlyArea) {
            Plotly.relayout(this.plotlyArea, {});
        }
    }
}

interface ScatterData extends Partial<any> {
    id: string;
    timestamp: number;
}

interface Layout extends Partial<any> {
    [key: string]: any;
}
<div #plotly></div>

./plotly-profile-graph.component.scss

:host {
    div {
        width: 100%;
        height: 100%;
    }
}
Legend
Html element
Component
Html element with directive

result-matching ""

    No results matching ""