| /******************************************************************************** |
| * 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, ViewChild, Input, SimpleChanges, OnChanges } from '@angular/core'; |
| import { Observable } from 'rxjs'; |
| import { map, tap, catchError } from 'rxjs/operators'; |
| |
| import { UIChart } from 'primeng/primeng'; |
| |
| import { HttpErrorHandler } from '../../../core/http-error-handler'; |
| import { Node } from '../../../navigator/node'; |
| import { ChartViewerDataService, getDataArray } from '../../services/chart-viewer-data.service'; |
| import { MeasuredValues } from '../../model/chartviewer.model'; |
| import { ChannelGroup } from '../../model/types/channelgroup.class'; |
| import { Measurement } from '../../model/types/measurement.class'; |
| import { Channel } from '../../model/types/channel.class'; |
| |
| @Component({ |
| selector: 'mdm5-chartViewer', |
| templateUrl: 'chart-viewer.component.html', |
| providers: [] |
| }) |
| export class ChartViewerComponent implements OnChanges { |
| |
| @Input() |
| measurement: Measurement; |
| |
| @ViewChild('lineChart') |
| chart: UIChart; |
| |
| maxRequestedValues = 10000; |
| previewEnabled = true; |
| numberOfChunks = 600; |
| numberOfRows = 0; |
| startIndex = 1; |
| requestedValues = 0; |
| chartTimer: any; |
| |
| // range of values to show; 1-based; start and end inclusive |
| rangeValues: number[] = [this.startIndex, this.requestedValues]; |
| step = 1000; |
| |
| data = this.getEmptyData(); |
| tmpData = this.getEmptyData(); |
| |
| options = { |
| elements: { |
| point: { |
| radius: 0 |
| }, |
| line: { |
| tension: 0 |
| } |
| }, |
| legend: { |
| display: true, |
| }, |
| plugins: { |
| zoom: { |
| pan: { |
| enabled: true, |
| mode: 'xy' |
| }, |
| zoom: { |
| enabled: true, |
| mode: 'xy' |
| } |
| } |
| }, |
| }; |
| |
| constructor(private httpErrorHandler: HttpErrorHandler, |
| private chartService: ChartViewerDataService) { |
| } |
| |
| // invoked from chart-viewer-nav-card.onSelectedNodeChange().subscribe calls |
| ngOnChanges(changes: SimpleChanges) { |
| this.numberOfRows = 0; |
| this.data = this.getEmptyData(); |
| this.tmpData = this.getEmptyData(); |
| for (const propName in changes) { |
| if (this.measurement !== undefined && (propName === 'measurement')) { |
| this.measurement.channelGroups.forEach(group => { |
| this.numberOfRows += group.numberOfRows; |
| if (this.numberOfRows < this.numberOfChunks) { |
| this.previewEnabled = false; |
| } |
| this.requestedValues = Math.min(this.numberOfRows, this.maxRequestedValues); |
| |
| this.updateRange(); |
| this.chartChannel(group); |
| }); |
| |
| break; |
| } |
| } |
| } |
| |
| getEmptyData() { |
| return { |
| labels: [], |
| datasets: [ |
| { |
| label: 'No data', |
| data: [], |
| borderColor: '#fff', |
| } |
| ] |
| }; |
| } |
| |
| getColor(name: string) { |
| return this.data.datasets.find(dataset => dataset.label === name).borderColor; |
| } |
| |
| applySettings(event: any) { |
| this.chartChannel(undefined); |
| } |
| |
| chartChannel(channelGroup: ChannelGroup) { |
| let group: ChannelGroup; |
| if (channelGroup) { |
| group = channelGroup; |
| } else { |
| // either the selected channel or all channels from the map |
| group = this.measurement.getFirstChannelGroup(); |
| } |
| |
| this.chartMeasuredValues(this.chartService.loadValues( |
| channelGroup, this.measurement.findChannels(channelGroup), |
| this.rangeValues[0] - 1, this.rangeValues[1], this.previewEnabled ? this.numberOfChunks : 0)); |
| } |
| |
| chartMeasuredValues(measuredValues: Observable<MeasuredValues[]>) { |
| measuredValues.pipe( |
| map(mvl => <any>{ |
| data: { |
| labels: this.getLabels(this.rangeValues[0], this.rangeValues[1], this.getLength(mvl)), |
| datasets: mvl.map(this.convertToDataset, this) |
| } |
| }), |
| catchError(this.httpErrorHandler.handleError) |
| ).subscribe(d => { |
| if (this.tmpData.labels.length === 0) { |
| this.tmpData = d.data; |
| } else { |
| this.tmpData.datasets = this.tmpData.datasets.concat(d.data.datasets); |
| } |
| |
| if (this.chartTimer) { |
| clearTimeout(this.chartTimer); |
| } |
| // adequate delay or the chart display will only render the latest group |
| this.chartTimer = setTimeout(() => this.processChartUpdate(), 250); |
| }); |
| } |
| |
| processChartUpdate() { |
| this.data = this.tmpData; |
| this.tmpData = this.getEmptyData(); |
| } |
| |
| updateRange() { |
| this.rangeValues = [this.startIndex, this.requestedValues]; |
| this.step = Math.min(1000, Math.max(Math.floor(this.numberOfRows / 1000), 1)); |
| } |
| |
| getLabels(startIndex: number, endIndex: number, numberOfChunks: number) { |
| const labels: number[] = []; |
| for (let i = startIndex; i <= endIndex; i += (endIndex - startIndex + 1) / numberOfChunks) { |
| labels.push(Math.floor(i)); |
| } |
| return labels; |
| } |
| |
| getLength(measuredValues: MeasuredValues[]) { |
| return measuredValues.map(mv => mv.length).reduce((p, l) => Math.max(p, l)); |
| } |
| |
| convertToDataset(measuredValues: MeasuredValues) { |
| const data = getDataArray(measuredValues); |
| return { |
| label: measuredValues.name + ' [' + measuredValues.unit + ']', |
| unit: measuredValues.unit, |
| data: data ? data.values : [], |
| borderColor: '#' + Math.random().toString(16).substr(-6), |
| measuredValues: map, |
| channel: new Node() |
| }; |
| } |
| } |