blob: 549a4187f0f864671e79dc6237767d9ef5c821bc [file] [log] [blame]
/********************************************************************************
* Copyright (c) 2015-2018 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
********************************************************************************/
import { Component, OnInit, Input, ViewChild } from '@angular/core';
import { UIChart } from 'primeng/chart';
import { map, flatMap, tap } from 'rxjs/operators';
import { Node } from '../../../navigator/node';
import { ChartData, ChartDataSet, MeasuredValues, RequestOptions, ChannelSelectionRow,
ChartXyDataSet, ChartToolbarProperties} from '../../model/chartviewer.model';
import { ChartViewerDataService } from '../../services/chart-viewer-data.service';
import { ArrayUtilService } from 'src/app/core/services/array-util.service';
import { ChartViewerService } from '../../services/chart-viewer.service';
import { forkJoin} from 'rxjs';
import { MDMNotificationService } from 'src/app/core/mdm-notification.service';
@Component({
selector: 'app-xy-chart-viewer',
templateUrl: './xy-chart-viewer.component.html',
styleUrls: ['../../chart-viewer.style.css']
})
export class XyChartViewerComponent implements OnInit {
@ViewChild('xyChart')
public chart: UIChart;
@Input()
public channelGroups: Node[];
@Input()
public channels: Map<string, Node[]>;
// the chart data
public data: ChartData;
// options for meausred value data request
private requestOptions: RequestOptions;
// chart properties set via toolbar
private toolbarProperties: ChartToolbarProperties;
// shows selection pannel if true, set via toolbar
public showSelection: boolean;
// passed to selection pannel
public xyMode: boolean;
public selectedChannelRows: ChannelSelectionRow[] = [];
constructor(private chartviewerDataService: ChartViewerDataService,
private chartviewerService: ChartViewerService,
private arrayUtil: ArrayUtilService,
private notificationService: MDMNotificationService) { }
ngOnInit() {
this.initChartData();
}
/**************** Html-template listeners *****************************/
/**
* Draws chart when data selection changes
* @param rows
*/
public onDataSelectionChanged(rows: ChannelSelectionRow[]) {
this.selectedChannelRows = rows;
this.doChart();
}
/**
* Passes xyModeChanges from toolbar to selection pannel
* @param isXyMode
*/
public onXyModeChanged(isXyMode: boolean) {
this.xyMode = isXyMode;
}
/**
* Handles toolbar property changes
* @param properties
*/
public onToolbarPropertiesChanged(properties: ChartToolbarProperties) {
this.toolbarProperties = properties;
this.showSelection = properties.showSelection;
this.chart.options = properties.options;
if (this.chart.data != undefined) {
this.chart.data.datasets.forEach( dataSet => {
dataSet.showLine = properties.showLines;
dataSet.pointRadius = properties.drawPoints ? 3 : 0;
dataSet.borderWidth = properties.lineWidth;
dataSet.lineTension = properties.lineTension;
dataSet.fill = properties.fillArea;
});
this.chart.reinit();
}
}
// Sets new data request options and redraws the graph
public onRequestOptionsChanged(options: RequestOptions) {
this.requestOptions = options;
this.doChart();
}
/********** private methods / class logic ********************************************************/
// empty chart on initialization
private initChartData() {
const dataset = new ChartDataSet('No data', []);
dataset.borderColor = '#fff';
this.data = new ChartData([], [dataset]);
}
/**
* Loads measured values for current selection and draws the new chart.
*/
private doChart() {
if (this.arrayUtil.isNotEmpty(this.selectedChannelRows) && this.requestOptions != undefined) {
const xChannels = this.selectedChannelRows.filter(row => row.xChannel != undefined)
.map(row => this.chartviewerDataService.loadMeasuredValues(
row.channelGroup, [row.xChannel], this.requestOptions.startIndex, this.requestOptions.requestSize));
forkJoin(xChannels)
.pipe(
map(array => array.map((mea, index) => ({yChannel: this.selectedChannelRows[index].yChannel, measuredValues: mea[0]}))),
flatMap(xValues => this.loadYData(xValues)),
tap(x => console.log(x))
)
.subscribe(resp => this.applyData(resp));
} else {
this.initChartData();
}
}
/**
* Apply the ChartData to data and options attributes of the chart component
* @param xValues
*/
private applyData(resp: ChartData) {
this.data = resp;
this.chart.options = {
scales: {
xAxes: [{
scaleLabel: {
display: true,
labelString: resp.datasets.map(ds => (<ChartXyDataSet> ds).xUnit).join(', ')
}
}],
},
};
}
/**
* Loads measured values for y-channels, merges them with given values for x
* @param xValues
*/
private loadYData(xValues: {yChannel: Node; measuredValues: MeasuredValues}[]) {
if (xValues != undefined) {
const yChannels = this.groupYChannelsByChannelGroup(this.selectedChannelRows);
// const channelGroups = this.selectedChannelGroups.filter(cg => this.arrayUtil.isNotEmpty(yChannels[cg.id]));
const channelGroups = Array.from(new Set(this.selectedChannelRows.map(row => row.channelGroup)));
const obs = channelGroups.map(cg => this.chartviewerDataService.loadMeasuredValues(
cg, yChannels[cg.id], this.requestOptions.startIndex, this.requestOptions.requestSize));
// forkJoin return observables in order of input array. Therefore channelgroup id has to be like tmpChannelGroups[index].id
return forkJoin(obs)
.pipe(
map(array => array.map((yData, channelGroupIndex) =>
yData.map((yValues, yChannelIndex) => {
const cgId = channelGroups[channelGroupIndex].id;
const xMeasuredValues = this.extractXMeasuredValues(xValues, yChannels[cgId][yChannelIndex].id);
const dataSet = this.chartviewerService.toXyDataSet(xMeasuredValues, yValues);
return this.setChartProperties(dataSet);
}))),
map(sets => new ChartData([], sets.reduce((a, b) => a.concat(b), [])))
);
}
}
private extractXMeasuredValues(xValues: {yChannel: Node; measuredValues: MeasuredValues}[], channelId: string) {
const val = xValues.find(v => v.yChannel.id === channelId);
if (val != undefined ) {
return val.measuredValues;
}
}
/******* Helping functions */
private groupYChannelsByChannelGroup(array: ChannelSelectionRow[]) {
return array.reduce(
(obj, next) => {
const value = next.channelGroup.id;
obj[value] = obj[value] || [];
obj[value] = obj[value].concat(next.yChannel);
return obj;
}, {});
}
private setChartProperties(dataset: ChartXyDataSet) {
if (!this.toolbarProperties.drawPoints) {
dataset.pointRadius = 0;
}
dataset.showLine = this.toolbarProperties.showLines;
dataset.fill = this.toolbarProperties.fillArea;
dataset.lineTension = this.toolbarProperties.lineTension;
dataset.borderWidth = this.toolbarProperties.lineWidth;
const color = '#' + Math.random().toString(16).substr(-6);
dataset.borderColor = color;
dataset.backgroundColor = color;
return dataset;
}
}