| /******************************************************************************** |
| * 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'; |
| |
| @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 notificationService: MDMNotificationService) { } |
| |
| |
| /**************** 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 arround: 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.independentChannels.get(row.channelGroup.id)); |
| } |
| this.fireSelectionChanged(); |
| } |
| |
| // 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(); |
| } |
| // // y-axis |
| // public onSelectedYChannelsChanged(event: any) { |
| // const yChannel = event.itemValue; |
| // this.addOrRemoveRow(this.findChannelGroup(yChannel), ); |
| // this.fireSelectionChanged(); |
| // } |
| |
| // channelgroups |
| // public onSelectedChannelGroupsChanged(event: any) { |
| // const item = event.itemValue; |
| // // item is undefined, if select-all check box is clicked. |
| // if (item == undefined) { |
| // if (this.arrayUtil.isNotEmpty(this.selectedChannelGroups)) { |
| // this.selectedChannelGroups.forEach(g => this.handleOnSelectChannelGroup(g)); |
| // } else { |
| // this.resetChannelData(); |
| // this.fireSelectionChanged(); |
| // } |
| // } else { |
| // // item contains toggled channelGroup if single select box is clicked |
| // this.handleOnSelectChannelGroup(item); |
| // } |
| // } |
| |
| 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; |
| } |
| |
| // private handleOnSelectChannelGroup(channelGroup: any) { |
| // // the currently toggled group is selected (ngModel is updated faster then event.) |
| // if (this.isNodeIn(channelGroup, this.selectedChannelGroups)) { |
| // this.handleSelectChannelGroup(channelGroup); |
| // // the currently toggled group is deselected |
| // } else { |
| // this.handleDeselectChannelGroup(channelGroup); |
| // } |
| // } |
| |
| 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 == 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') { |
| this.yChannelOptions = this.yChannelOptions.concat(tmpChannel); |
| } |
| }); |
| } else { |
| // cache independent channel for default x-axis. |
| const value = measuredValues.find(mvl => mvl.independent); |
| 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 uncached = this.selectedChannelGroups.filter(g => this.xYcache.get(g.id) === undefined); |
| // const cached = this.selectedChannelGroups.filter(g => this.xYcache.get(g.id) !== undefined); |
| |
| // const obs = uncached.map(cg => this.chartviewerDataService.readAxisType(cg)); |
| // return forkJoin( |
| // forkJoin(obs) |
| // .pipe(tap(array => array.forEach((mvls, index) => this.setChannelOptions(mvls, uncached[index])))), |
| // of(cached.map((group, index) => this.setChannelOptions(this.xYcache.get(group.id), cached[index])))) |
| // .pipe(tap(() => this.removeSelectionsNotMatchingXYMode())); |
| // } |
| // return of(); |
| // } |
| |
| /** |
| * 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.independentChannels.get(channelGroup.id)); |
| this.selectedChannelRows.push(row); |
| } |
| |
| /** |
| * 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; |
| } |
| } |