| /******************************************************************************** |
| * 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; |
| } |
| } |