blob: 7def935988130198b6b040e1a570584f4f3ad278 [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, OnDestroy, EventEmitter, Output, OnChanges, SimpleChanges } from '@angular/core';
import { ChannelSelectionRow, MeasuredValues} from '../../model/chartviewer.model';
import { Subscription, of } from 'rxjs';
import { ChartViewerDataService } from '../../services/chart-viewer-data.service';
import { ChartViewerService } from '../../services/chart-viewer.service';
import { ArrayUtilService } from 'src/app/core/services/array-util.service';
import { MDMNotificationService } from 'src/app/core/mdm-notification.service';
import { TYPE_MEASUREMENT, TYPE_CHANNELGROUP, TYPE_CHANNEL } from '../../model/constants';
import { tap } from 'rxjs/operators';
import { Node } from '../../../navigator/node';
import {ConfirmationService} from 'primeng/api';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'mdm5-xy-chart-data-selection-panel',
templateUrl: './xy-chart-data-selection-panel.component.html',
styleUrls: ['../../chart-viewer.style.css']
})
export class XyChartDataSelectionPanelComponent implements OnInit, OnDestroy, OnChanges {
@Input()
public channelGroups: Node[];
@Input()
public channels: Map<string, Node[]>;
@Input()
public xyMode: boolean;
@Output()
public onSelectionChanged = new EventEmitter<ChannelSelectionRow[]>();
// select options for channels
public yChannelOptions: Node[] = [];
public xChannelOptions: {[key: string]: Node[]} = {};
// channel(group) selection for chart
// !! change via setter to update cache for workarround !!
public selectedChannelGroups: Node[] = [];
public selectedYChannels: Node[] = [];
private xYcache = new Map<string, MeasuredValues[]>();
private selectionChanged = false;
// listbox workarround to determine toggled item(s)
private lastChannelGroupSelection: Node[] = [];
private lastYChannelSelection: Node[] = [];
public selectedChannelRows: ChannelSelectionRow[] = [];
// Toggle selection panel visibility
public hiddenGroups = false;
public hiddenYChannels = false;
// for x-channel default value
private independentChannels = new Map<string, Node>();
/********** subscriptions */
private chartViewerSub: Subscription;
constructor(private chartviewerDataService: ChartViewerDataService,
private chartViewerService: ChartViewerService,
private arrayUtil: ArrayUtilService,
private confirmationService: ConfirmationService,
private notificationService: MDMNotificationService,
private translateService: TranslateService) { }
/**************** Angular lifecycle-hooks ****************************/
ngOnInit() {
this.chartViewerSub = this.chartViewerService.onNodeMetaChange().subscribe(node => this.handleNodeSelectionInNavTree(node));
}
ngOnDestroy() {
this.chartViewerSub.unsubscribe();
}
ngOnChanges(changes: SimpleChanges) {
if (changes['xyMode']) {
this.reloadAxisSelectOptions().subscribe(() => {
if (this.selectionChanged) {
this.fireSelectionChanged();
this.selectionChanged = false;
}
});
}
}
/**************** Html-template listeners *****************************/
// x-axis
public onSelectedXChannelChanged(event: any) {
if (event.value == undefined) {
// work around: if x-axis is deselected for an y-channel, plat over independent channel.
// should force selection on dropdown instead.
this.selectedChannelRows
.filter(row => row.xChannel == undefined)
.map(row => row.xChannel = this.getDefaultXChannel(row.channelGroup));
}
this.fireSelectionChanged();
}
public onShowSelectedXChannelChanged(event: any, rowData: ChannelSelectionRow) {
if (this.getDefaultXChannel(rowData.channelGroup) == undefined) {
this.confirmationService.confirm({
header: this.translateService.instant('chartviewer.xy-chart-data-selection-panel.no-x-channel-dialog-header'),
message: this.translateService.instant('chartviewer.xy-chart-data-selection-panel.no-x-channel-dialog-message'),
acceptLabel: 'OK',
rejectVisible: false
});
}
}
// y-axis
public onSelectedYChannelsChanged(event: any) {
let channel: Node;
// Deselect
if (this.selectedYChannels.length < this.lastYChannelSelection.length) {
channel = this.lastYChannelSelection.find(yChannel => !this.selectedYChannels.some(c => yChannel.id === c.id));
// .forEach(yChannel => this.addOrRemoveRow(this.findChannelGroup(yChannel), yChannel));
// Select
} else {
channel = this.selectedYChannels.find(group => !this.lastYChannelSelection.some(g => group.id === g.id));
}
this.addOrRemoveRow(this.findChannelGroup(channel), channel);
this.lastYChannelSelection = this.selectedYChannels;
this.fireSelectionChanged();
}
public onSelectedChannelGroupsChanged(event: any) {
// All deselected
if (this.selectedChannelGroups.length === 0) {
this.resetChannelData();
this.fireSelectionChanged();
} else {
// Deselect
if (this.selectedChannelGroups.length < this.lastChannelGroupSelection.length) {
this.lastChannelGroupSelection
.filter(group => !this.selectedChannelGroups.some(g => group.id === g.id))
.forEach(group => this.handleDeselectChannelGroup(group));
// Select
} else {
this.selectedChannelGroups
.filter(group => !this.lastChannelGroupSelection.some(g => group.id === g.id))
.forEach(group => this.handleSelectChannelGroup(group));
}
}
this.lastChannelGroupSelection = this.selectedChannelGroups;
}
public onToggleChannelGroupPanel(event: Event) {
this.hiddenGroups = !this.hiddenGroups;
}
public onToggleYChannelPanel(event: Event) {
this.hiddenYChannels = !this.hiddenYChannels;
}
private handleDeselectChannelGroup(channelGroup: Node) {
// remove rows
if (this.arrayUtil.isNotEmpty(this.selectedChannelRows)) {
this.selectedChannelRows = this.selectedChannelRows.filter(row => row.channelGroup.id !== channelGroup.id);
}
// remove selection
if (this.arrayUtil.isNotEmpty(this.selectedYChannels)) {
this.setSelectedYChannels(this.selectedYChannels.filter(channel =>
this.channels.get(channelGroup.id).findIndex(c => channel.id === c.id) === -1));
}
this.reloadAxisSelectOptions().subscribe(() => this.fireSelectionChanged());
}
/**
* Handle channelGroup selection via multiselect in html template
* @param channelGroup
*/
private handleSelectChannelGroup(channelGroup: Node) {
const cached = this.xYcache.get(channelGroup.id);
if (cached != undefined) {
this.setChannelOptions(cached, channelGroup);
this.fireSelectionChanged();
} else {
this.chartviewerDataService.readAxisType(channelGroup)
.subscribe(mvls => {
this.setChannelOptions(mvls, channelGroup);
this.fireSelectionChanged();
});
}
}
private handleNodeSelectionInNavTree(node: Node) {
if (node != undefined) {
switch (node.type) {
case TYPE_MEASUREMENT:
this.xYcache.clear();
this.resetChannelData();
this.fireSelectionChanged();
break;
case TYPE_CHANNELGROUP:
this.cleanOldState();
this.addChannelGroupToSelection(node.id, node);
break;
case TYPE_CHANNEL:
this.cleanOldState();
// add related channel group to selection
if (this.arrayUtil.isNotEmpty(this.channelGroups) && this.channels != undefined) {
const channelGroup = this.findChannelGroup(node);
// this.addChannelGroupToSelection(channelGroupId, this.channelGroups.find(cg => cg.id === channelGroupId));
if (!this.isNodeIn(channelGroup, this.selectedChannelGroups)) {
this.setSelectedChannelGroups(this.selectedChannelGroups.concat([channelGroup]));
this.chartviewerDataService.readAxisType(channelGroup)
.subscribe(mvls => {
this.setChannelOptions(mvls, channelGroup);
this.addChannelToSelection(node, channelGroup);
});
} else {
// if channel is in axis type y, set as selected, if not selected already
this.addChannelToSelection(node, channelGroup);
}
}
break;
}
}
}
/**
* reset chart data, select options and selection, if selected channelgroups
* contain a channelGroup which does not belongto this measurement.
*
* notice: this works for channels aswell, since a channel cannot be selected without
* selecting the parent channelGroup aswell.
*/
private cleanOldState() {
if (this.arrayUtil.isNotEmpty(this.selectedChannelGroups)
&& this.selectedChannelGroups.some(selected => !this.isNodeIn(selected, this.channelGroups))) {
this.xYcache.clear();
this.resetChannelData();
}
}
/**
* Handle channel selection via nav-tree
* @param channel
* @param channelGroup
*/
private addChannelToSelection(channel: Node, channelGroup: Node) {
// adds channel to selected y-channels if possible and creates row in table
if (this.isNodeIn(channel, this.yChannelOptions)) {
const index = this.selectedYChannels.findIndex(c => c.id === channel.id);
if (index === -1) {
this.setSelectedYChannels(this.selectedYChannels.concat([channel]));
this.addRow(channelGroup, channel);
this.fireSelectionChanged();
}
/**
* @TODO -> expected behaviour on selecting x-channel in tree.
* suggestion: set it as x-channel for all selected y-channels of that channelgroup.
*/
// if channel is of axis type x, set ... ?
} else if (this.isNodeIn(channel, this.xChannelOptions[channelGroup.id])) {
this.notificationService.notifyWarn('Selected X-Channel', 'Selected channel is of type x-axis. Change x/y mode.');
} else {
this.notificationService.notifyError('Unknown channel option', 'Not sure where you found this..');
}
}
/**
* Handle channelGroup selection via nav-tree
* @param id
* @param channelGroup
*/
private addChannelGroupToSelection(id: string, channelGroup: Node) {
if (this.selectedChannelGroups == undefined) {
this.setSelectedChannelGroups([]);
}
if (!this.isNodeIn(channelGroup, this.selectedChannelGroups)) {
this.setSelectedChannelGroups(this.selectedChannelGroups.concat([channelGroup]));
this.handleSelectChannelGroup(channelGroup);
}
}
/**
* Adds options for x- and y-channels depending on given channelGroup
*
* @param measuredValues
* @param channelGroup
*/
private setChannelOptions(measuredValues: MeasuredValues[], channelGroup: Node) {
if (channelGroup != undefined) {
// cache measuredValues to avoid reloading
this.xYcache.set(channelGroup.id, measuredValues);
// create empty array if property not exists
this.xChannelOptions[channelGroup.id] = this.xChannelOptions[channelGroup.id] || [];
if (this.xyMode) {
measuredValues.forEach(mvl => {
const tmpChannel = this.channels.get(channelGroup.id).filter(c => c.name === mvl.name)[0];
if (mvl.axisType === 'X_AXIS' || mvl.axisType === 'XY_AXIS' || ( mvl.axisType == undefined && mvl.independent)) {
this.xChannelOptions[channelGroup.id] = this.xChannelOptions[channelGroup.id].concat(tmpChannel);
// cache independent channels for default x-axis.
if (mvl.independent) {
this.independentChannels.set(channelGroup.id, tmpChannel);
}
} else if (mvl.axisType === 'Y_AXIS' || mvl.axisType === 'XY_AXIS') {
this.yChannelOptions = this.yChannelOptions.concat(tmpChannel);
}
});
} else {
// cache independent channel for default x-axis.
const value = measuredValues.find(mvl => mvl.independent);
if (value != undefined) {
const tmpChannel = this.channels.get(channelGroup.id).filter(c => c.name === value.name)[0];
this.independentChannels.set(channelGroup.id, tmpChannel);
}
const tmpChannels = this.channels.get(channelGroup.id);
this.xChannelOptions[channelGroup.id] = this.xChannelOptions[channelGroup.id].concat(tmpChannels);
this.yChannelOptions = this.yChannelOptions.concat(tmpChannels);
}
}
}
/**
* Resets selections and select options to empty state.
*/
private resetChannelData() {
this.setSelectedChannelGroups([]);
this.selectedChannelRows = [];
this.setSelectedYChannels([]);
this.channelGroups = [].concat(this.channelGroups);
this.xChannelOptions = {};
this.yChannelOptions = [];
}
/**
* Reloads the select options for the x- and y-axis dropdowns
*/
private reloadAxisSelectOptions() {
this.xChannelOptions = {};
this.yChannelOptions = [];
if (this.arrayUtil.isNotEmpty(this.selectedChannelGroups)) {
const cached = this.selectedChannelGroups.filter(g => this.xYcache.get(g.id) !== undefined);
return of(cached.map((group, index) => this.setChannelOptions(this.xYcache.get(group.id), cached[index])))
.pipe(tap(() => this.removeSelectionsNotMatchingXYMode()));
}
return of();
}
private removeSelectionsNotMatchingXYMode() {
if (this.selectedYChannels.length > 0) {
const l = this.selectedChannelRows.length;
this.selectedChannelRows = this.selectedChannelRows.filter(row => this.isNodeIn(row.yChannel, this.yChannelOptions));
this.setSelectedYChannels(this.selectedYChannels.filter(channel => this.isNodeIn(channel, this.yChannelOptions)));
this.selectionChanged = l !== this.selectedChannelRows.length;
}
}
/**
* Row control for table with x-channel selection.
*
* @param channelGroup
* @param yChannel
*/
private addOrRemoveRow(channelGroup: Node, yChannel: Node) {
const index = this.selectedChannelRows.findIndex(row => row.yChannel.id === yChannel.id);
if (index > -1) {
this.selectedChannelRows.splice(index, 1);
this.selectedChannelRows = [].concat(this.selectedChannelRows);
} else {
this.addRow(channelGroup, yChannel);
}
}
/**
* Add row to selection
* @param channelGroup
* @param yChannel
*/
private addRow(channelGroup: Node, yChannel: Node) {
const row = new ChannelSelectionRow(channelGroup, yChannel, this.getDefaultXChannel(channelGroup));
this.selectedChannelRows.push(row);
}
/**
* Get the default channel for x-axis. First tries to find an independent channel,
* if non is found the first channel with axistype X-Axis or XY-Axis is returned.
* @param channelGroup
*/
private getDefaultXChannel(channelGroup: Node) {
let defaultXChannel = this.independentChannels.get(channelGroup.id);
if (defaultXChannel == undefined && this.xChannelOptions[channelGroup.id].length > 0) {
defaultXChannel = this.xChannelOptions[channelGroup.id][0];
}
return defaultXChannel;
}
/**
* Returns true if node with given id is in the given array.
*
* @param array the array
* @param id the id
*/
private isNodeIn(node: Node, array: Node[]) {
let response = false;
if (node != undefined && this.arrayUtil.isNotEmpty(array)) {
response = array.findIndex(n => n.id === node.id) > -1;
}
return response;
}
/**
* Looks up parent channelGroup for channel via the channel map.
*
* @param channel
*/
private findChannelGroup(channel: Node) {
const channelGroupId = Array.from(this.channels.keys())
.find(key => this.isNodeIn(channel, this.channels.get(key)));
return this.channelGroups.find(cg => cg.id === channelGroupId);
}
private fireSelectionChanged() {
this.onSelectionChanged.emit(this.selectedChannelRows);
}
private setSelectedYChannels(channels: Node[]) {
this.selectedYChannels = channels;
this.lastYChannelSelection = channels;
}
private setSelectedChannelGroups(channelGroups: Node[]) {
this.selectedChannelGroups = channelGroups;
this.lastChannelGroupSelection = channelGroups;
}
}