1544 lines
		
	
	
		
			54 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			1544 lines
		
	
	
		
			54 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
///
 | 
						|
/// Copyright © 2016-2022 The Thingsboard Authors
 | 
						|
///
 | 
						|
/// Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
/// you may not use this file except in compliance with the License.
 | 
						|
/// You may obtain a copy of the License at
 | 
						|
///
 | 
						|
///     http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
///
 | 
						|
/// Unless required by applicable law or agreed to in writing, software
 | 
						|
/// distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
/// See the License for the specific language governing permissions and
 | 
						|
/// limitations under the License.
 | 
						|
///
 | 
						|
 | 
						|
 | 
						|
import { WidgetContext } from '@home/models/widget-component.models';
 | 
						|
import {
 | 
						|
  createLabelFromDatasource,
 | 
						|
  deepClone,
 | 
						|
  insertVariable,
 | 
						|
  isDefined,
 | 
						|
  isDefinedAndNotNull,
 | 
						|
  isEqual,
 | 
						|
  isNumber,
 | 
						|
  isNumeric,
 | 
						|
  isUndefined
 | 
						|
} from '@app/core/utils';
 | 
						|
import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models';
 | 
						|
import {
 | 
						|
  DataKey,
 | 
						|
  Datasource,
 | 
						|
  DatasourceData,
 | 
						|
  DatasourceType,
 | 
						|
  JsonSettingsSchema,
 | 
						|
  widgetType
 | 
						|
} from '@app/shared/models/widget.models';
 | 
						|
import {
 | 
						|
  ChartType,
 | 
						|
  TbFlotAxisOptions,
 | 
						|
  TbFlotHoverInfo,
 | 
						|
  TbFlotKeySettings, TbFlotLatestKeySettings,
 | 
						|
  TbFlotPlotAxis,
 | 
						|
  TbFlotPlotDataSeries,
 | 
						|
  TbFlotPlotItem,
 | 
						|
  TbFlotSeries,
 | 
						|
  TbFlotSeriesHoverInfo,
 | 
						|
  TbFlotSettings,
 | 
						|
  TbFlotThresholdKeySettings,
 | 
						|
  TbFlotThresholdMarking,
 | 
						|
  TbFlotTicksFormatterFunction,
 | 
						|
  TooltipValueFormatFunction
 | 
						|
} from './flot-widget.models';
 | 
						|
import * as moment_ from 'moment';
 | 
						|
import * as tinycolor_ from 'tinycolor2';
 | 
						|
import { AggregationType } from '@shared/models/time/time.models';
 | 
						|
import { CancelAnimationFrame } from '@core/services/raf.service';
 | 
						|
import { UtilsService } from '@core/services/utils.service';
 | 
						|
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
 | 
						|
import Timeout = NodeJS.Timeout;
 | 
						|
 | 
						|
const tinycolor = tinycolor_;
 | 
						|
const moment = moment_;
 | 
						|
 | 
						|
export class TbFlot {
 | 
						|
 | 
						|
  private readonly utils: UtilsService;
 | 
						|
 | 
						|
  private settings: TbFlotSettings;
 | 
						|
  private comparisonEnabled: boolean;
 | 
						|
 | 
						|
  private tooltip: JQuery<any>;
 | 
						|
 | 
						|
  private readonly yAxisTickFormatter: TbFlotTicksFormatterFunction;
 | 
						|
  private readonly yaxis: TbFlotAxisOptions;
 | 
						|
  private readonly xaxis: TbFlotAxisOptions;
 | 
						|
  private yaxes: Array<TbFlotAxisOptions>;
 | 
						|
 | 
						|
  private readonly options: JQueryPlotOptions;
 | 
						|
  private subscription: IWidgetSubscription;
 | 
						|
  private $element: JQuery<any>;
 | 
						|
 | 
						|
  private readonly trackUnits: string;
 | 
						|
  private readonly trackDecimals: number;
 | 
						|
  private readonly tooltipIndividual: boolean;
 | 
						|
  private readonly tooltipCumulative: boolean;
 | 
						|
  private readonly hideZeros: boolean;
 | 
						|
 | 
						|
  private readonly defaultBarWidth: number;
 | 
						|
 | 
						|
  private thresholdsSourcesSubscription: IWidgetSubscription;
 | 
						|
  private predefinedThresholds: TbFlotThresholdMarking[];
 | 
						|
  private latestDataThresholds: TbFlotThresholdMarking[];
 | 
						|
  private attributesThresholds: TbFlotThresholdMarking[];
 | 
						|
 | 
						|
  private labelPatternsSourcesSubscription: IWidgetSubscription;
 | 
						|
  private labelPatternsSourcesData: DatasourceData[];
 | 
						|
 | 
						|
  private plotInited = false;
 | 
						|
  private plot: JQueryPlot;
 | 
						|
 | 
						|
  private createPlotTimeoutHandle: Timeout;
 | 
						|
  private updateTimeoutHandle: Timeout;
 | 
						|
  private latestUpdateTimeoutHandle: Timeout;
 | 
						|
  private resizeTimeoutHandle: Timeout;
 | 
						|
 | 
						|
  private mouseEventsEnabled: boolean;
 | 
						|
  private isMouseInteraction = false;
 | 
						|
  private flotHoverHandler = this.onFlotHover.bind(this);
 | 
						|
  private flotSelectHandler = this.onFlotSelect.bind(this);
 | 
						|
  private dblclickHandler = this.onFlotDblClick.bind(this);
 | 
						|
  private mousedownHandler = this.onFlotMouseDown.bind(this);
 | 
						|
  private mouseupHandler = this.onFlotMouseUp.bind(this);
 | 
						|
  private mouseleaveHandler = this.onFlotMouseLeave.bind(this);
 | 
						|
  private flotClickHandler = this.onFlotClick.bind(this);
 | 
						|
 | 
						|
  private readonly showTooltip: boolean;
 | 
						|
  private readonly animatedPie: boolean;
 | 
						|
  private pieDataAnimationDuration: number;
 | 
						|
  private pieData: DatasourceData[];
 | 
						|
  private pieRenderedData: any[];
 | 
						|
  private pieTargetData: any[];
 | 
						|
  private pieAnimationStartTime: number;
 | 
						|
  private pieAnimationLastTime: number;
 | 
						|
  private pieAnimationCaf: CancelAnimationFrame;
 | 
						|
 | 
						|
  constructor(private ctx: WidgetContext, private readonly chartType: ChartType) {
 | 
						|
    this.chartType = this.chartType || 'line';
 | 
						|
    this.settings = ctx.settings as TbFlotSettings;
 | 
						|
    this.utils = this.ctx.$injector.get(UtilsService);
 | 
						|
    this.showTooltip = isDefined(this.settings.showTooltip) ? this.settings.showTooltip : true;
 | 
						|
    this.tooltip = this.showTooltip ? $('#flot-series-tooltip') : null;
 | 
						|
    if (this.tooltip?.length === 0) {
 | 
						|
      this.tooltip = this.createTooltipElement();
 | 
						|
    }
 | 
						|
 | 
						|
    this.trackDecimals = ctx.decimals;
 | 
						|
    this.trackUnits = ctx.units;
 | 
						|
    this.tooltipIndividual = this.chartType === 'pie' || (isDefined(this.settings.tooltipIndividual)
 | 
						|
      ? this.settings.tooltipIndividual : false);
 | 
						|
    this.tooltipCumulative = isDefined(this.settings.tooltipCumulative) ? this.settings.tooltipCumulative : false;
 | 
						|
    this.hideZeros = isDefined(this.settings.hideZeros) ? this.settings.hideZeros : false;
 | 
						|
 | 
						|
    const font = {
 | 
						|
      color: this.settings.fontColor || '#545454',
 | 
						|
      size: this.settings.fontSize || 10,
 | 
						|
      family: 'Roboto'
 | 
						|
    };
 | 
						|
 | 
						|
    this.options = {
 | 
						|
      title: null,
 | 
						|
      subtitile: null,
 | 
						|
      shadowSize: isDefined(this.settings.shadowSize) ? this.settings.shadowSize : 4,
 | 
						|
      HtmlText: false,
 | 
						|
      grid: {
 | 
						|
        hoverable: true,
 | 
						|
        mouseActiveRadius: 10,
 | 
						|
        autoHighlight: this.tooltipIndividual === true,
 | 
						|
        markings: []
 | 
						|
      },
 | 
						|
      selection : { mode : 'x' },
 | 
						|
      legend : {
 | 
						|
        show: false
 | 
						|
      }
 | 
						|
    };
 | 
						|
 | 
						|
    if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'state') {
 | 
						|
      this.options.xaxes = [];
 | 
						|
      this.xaxis = {
 | 
						|
        mode: 'time',
 | 
						|
        timezone: 'browser',
 | 
						|
        font: deepClone(font),
 | 
						|
        labelFont: deepClone(font)
 | 
						|
      };
 | 
						|
      this.yaxis = {
 | 
						|
        font: deepClone(font),
 | 
						|
        labelFont: deepClone(font)
 | 
						|
      };
 | 
						|
      if (this.settings.xaxis) {
 | 
						|
        this.xaxis.font.color = this.settings.xaxis.color || this.xaxis.font.color;
 | 
						|
        this.xaxis.label = this.utils.customTranslation(this.settings.xaxis.title, this.settings.xaxis.title) || null;
 | 
						|
        this.xaxis.labelFont.color = this.xaxis.font.color;
 | 
						|
        this.xaxis.labelFont.size = this.xaxis.font.size + 2;
 | 
						|
        this.xaxis.labelFont.weight = 'bold';
 | 
						|
      }
 | 
						|
 | 
						|
      this.yAxisTickFormatter = this.formatYAxisTicks.bind(this);
 | 
						|
 | 
						|
      this.yaxis.tickFormatter = this.yAxisTickFormatter;
 | 
						|
 | 
						|
      if (this.settings.yaxis) {
 | 
						|
        this.yaxis.font.color = this.settings.yaxis.color || this.yaxis.font.color;
 | 
						|
        this.yaxis.min = isDefined(this.settings.yaxis.min) ? this.settings.yaxis.min : null;
 | 
						|
        this.yaxis.max = isDefined(this.settings.yaxis.max) ? this.settings.yaxis.max : null;
 | 
						|
        this.yaxis.label = this.utils.customTranslation(this.settings.yaxis.title, this.settings.yaxis.title) || null;
 | 
						|
        this.yaxis.labelFont.color = this.yaxis.font.color;
 | 
						|
        this.yaxis.labelFont.size = this.yaxis.font.size + 2;
 | 
						|
        this.yaxis.labelFont.weight = 'bold';
 | 
						|
        if (isNumber(this.settings.yaxis.tickSize)) {
 | 
						|
          this.yaxis.tickSize = this.settings.yaxis.tickSize;
 | 
						|
        } else {
 | 
						|
          this.yaxis.tickSize = null;
 | 
						|
        }
 | 
						|
        if (isNumber(this.settings.yaxis.tickDecimals)) {
 | 
						|
          this.yaxis.tickDecimals = this.settings.yaxis.tickDecimals;
 | 
						|
        } else {
 | 
						|
          this.yaxis.tickDecimals = null;
 | 
						|
        }
 | 
						|
        if (this.settings.yaxis.ticksFormatter && this.settings.yaxis.ticksFormatter.length) {
 | 
						|
          try {
 | 
						|
            this.yaxis.ticksFormatterFunction = new Function('value',
 | 
						|
                                               this.settings.yaxis.ticksFormatter) as TbFlotTicksFormatterFunction;
 | 
						|
          } catch (e) {
 | 
						|
            this.yaxis.ticksFormatterFunction = null;
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      this.options.grid.borderWidth = 1;
 | 
						|
      this.options.grid.color = this.settings.fontColor || '#545454';
 | 
						|
 | 
						|
      if (this.settings.grid) {
 | 
						|
        this.options.grid.color = this.settings.grid.color || '#545454';
 | 
						|
        this.options.grid.backgroundColor = this.settings.grid.backgroundColor || null;
 | 
						|
        this.options.grid.tickColor = this.settings.grid.tickColor || '#DDDDDD';
 | 
						|
        this. options.grid.borderWidth = isDefined(this.settings.grid.outlineWidth) ?
 | 
						|
          this.settings.grid.outlineWidth : 1;
 | 
						|
        if (this.settings.grid.verticalLines === false) {
 | 
						|
          this.xaxis.tickLength = 0;
 | 
						|
        }
 | 
						|
        if (this.settings.grid.horizontalLines === false) {
 | 
						|
          this.yaxis.tickLength = 0;
 | 
						|
        }
 | 
						|
        if (isDefined(this.settings.grid.margin)) {
 | 
						|
          this.options.grid.margin = this.settings.grid.margin;
 | 
						|
        }
 | 
						|
        if (isDefined(this.settings.grid.minBorderMargin)) {
 | 
						|
          this.options.grid.minBorderMargin = this.settings.grid.minBorderMargin;
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      this.options.xaxes[0] = deepClone(this.xaxis);
 | 
						|
      if (this.settings.xaxis && this.settings.xaxis.showLabels === false) {
 | 
						|
        this.options.xaxes[0].tickFormatter = () => {
 | 
						|
          return '';
 | 
						|
        };
 | 
						|
      }
 | 
						|
 | 
						|
      this.options.series = {};
 | 
						|
 | 
						|
      this.options.crosshair = {
 | 
						|
        mode: 'x'
 | 
						|
      };
 | 
						|
 | 
						|
      if (this.chartType === 'line' && this.settings.smoothLines) {
 | 
						|
        this.options.series.curvedLines = {
 | 
						|
          active: true,
 | 
						|
          monotonicFit: true
 | 
						|
        };
 | 
						|
      }
 | 
						|
 | 
						|
      if ((this.chartType === 'line' || this.chartType === 'bar') && isFinite(this.settings.thresholdsLineWidth)) {
 | 
						|
        this.options.grid.markingsLineWidth = this.settings.thresholdsLineWidth;
 | 
						|
      }
 | 
						|
 | 
						|
      if (this.chartType === 'bar') {
 | 
						|
        this.options.series.lines = {
 | 
						|
          show: false,
 | 
						|
          fill: false,
 | 
						|
          steps: false
 | 
						|
        };
 | 
						|
        this.options.series.bars = {
 | 
						|
          show: true,
 | 
						|
          lineWidth: 0,
 | 
						|
          fill: 0.9,
 | 
						|
          align: this.settings.barAlignment || 'left'
 | 
						|
        };
 | 
						|
        this.defaultBarWidth = this.settings.defaultBarWidth || 600;
 | 
						|
      }
 | 
						|
 | 
						|
      if (this.chartType === 'state') {
 | 
						|
        this.options.series.lines = {
 | 
						|
          steps: true,
 | 
						|
          show: true
 | 
						|
        };
 | 
						|
      }
 | 
						|
    } else if (this.chartType === 'pie') {
 | 
						|
      this.options.series = {
 | 
						|
        pie: {
 | 
						|
          show: true,
 | 
						|
          label: {
 | 
						|
            show: this.settings.showLabels === true
 | 
						|
          },
 | 
						|
          radius: this.settings.radius || 1,
 | 
						|
          innerRadius: this.settings.innerRadius || 0,
 | 
						|
          stroke: {
 | 
						|
            color: '#fff',
 | 
						|
            width: 0
 | 
						|
          },
 | 
						|
          tilt: this.settings.tilt || 1,
 | 
						|
          shadow: {
 | 
						|
            left: 5,
 | 
						|
            top: 15,
 | 
						|
            alpha: 0.02
 | 
						|
          }
 | 
						|
        }
 | 
						|
      };
 | 
						|
 | 
						|
      this.options.grid.clickable = true;
 | 
						|
 | 
						|
      if (this.settings.stroke) {
 | 
						|
        this.options.series.pie.stroke.color = this.settings.stroke.color || '#fff';
 | 
						|
        this.options.series.pie.stroke.width = this.settings.stroke.width || 0;
 | 
						|
        if (this.options.series.pie.stroke.width) {
 | 
						|
          this.scalingPieRadius();
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      if (this.options.series.pie.label.show) {
 | 
						|
        this.options.series.pie.label.formatter = (label, series) => {
 | 
						|
          return `<div class='pie-label'>${series.dataKey.label}<br/>${Math.round(series.percent)}%</div>`;
 | 
						|
        };
 | 
						|
        this.options.series.pie.label.radius = 3 / 4;
 | 
						|
        this.options.series.pie.label.background = {
 | 
						|
          opacity: 0.8
 | 
						|
        };
 | 
						|
      }
 | 
						|
 | 
						|
      // Experimental
 | 
						|
      this.animatedPie = this.settings.animatedPie === true;
 | 
						|
    }
 | 
						|
 | 
						|
    if (this.ctx.defaultSubscription) {
 | 
						|
      this.init(this.ctx.$container, this.ctx.defaultSubscription);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  private init($element: JQuery<any>, subscription: IWidgetSubscription) {
 | 
						|
    this.$element = $element;
 | 
						|
    this.subscription = subscription;
 | 
						|
    this.comparisonEnabled = this.subscription ? this.subscription.comparisonEnabled : this.settings.comparisonEnabled;
 | 
						|
    if (this.comparisonEnabled) {
 | 
						|
      const xaxis = deepClone(this.xaxis);
 | 
						|
      xaxis.position = 'top';
 | 
						|
      if (this.settings.xaxisSecond) {
 | 
						|
        if (this.settings.xaxisSecond.showLabels === false) {
 | 
						|
          xaxis.tickFormatter = () => {
 | 
						|
            return '';
 | 
						|
          };
 | 
						|
        }
 | 
						|
        xaxis.label = this.utils.customTranslation(this.settings.xaxisSecond.title, this.settings.xaxisSecond.title) || null;
 | 
						|
        xaxis.position = this.settings.xaxisSecond.axisPosition;
 | 
						|
      }
 | 
						|
      xaxis.tickLength = 0;
 | 
						|
      this.options.xaxes.push(xaxis);
 | 
						|
 | 
						|
      this.options.series.stack = false;
 | 
						|
    } else {
 | 
						|
      this.options.series.stack = this.settings.stack === true;
 | 
						|
    }
 | 
						|
    const colors: string[] = [];
 | 
						|
    this.yaxes = [];
 | 
						|
    const yaxesMap: {[units: string]: TbFlotAxisOptions} = {};
 | 
						|
    const predefinedThresholds: TbFlotThresholdMarking[] = [];
 | 
						|
    const thresholdsDatasources: Datasource[] = [];
 | 
						|
    if (this.settings.customLegendEnabled && this.settings.dataKeysListForLabels?.length) {
 | 
						|
      this.labelPatternsSourcesData = [];
 | 
						|
      const labelPatternsDatasources: Datasource[] = [];
 | 
						|
      this.settings.dataKeysListForLabels.forEach((item) => {
 | 
						|
        item.settings = {};
 | 
						|
      });
 | 
						|
      this.subscription.datasources.forEach((item) => {
 | 
						|
        const datasource: Datasource = {
 | 
						|
          type: item.type,
 | 
						|
          entityType: item.entityType,
 | 
						|
          entityId: item.entityId,
 | 
						|
          dataKeys: this.settings.dataKeysListForLabels
 | 
						|
        };
 | 
						|
        labelPatternsDatasources.push(datasource);
 | 
						|
      });
 | 
						|
      this.subscribeForLabelPatternsSources(labelPatternsDatasources);
 | 
						|
    }
 | 
						|
 | 
						|
    let tooltipValueFormatFunction: TooltipValueFormatFunction = null;
 | 
						|
    if (this.settings.tooltipValueFormatter && this.settings.tooltipValueFormatter.length) {
 | 
						|
      try {
 | 
						|
        tooltipValueFormatFunction = new Function('value', this.settings.tooltipValueFormatter) as TooltipValueFormatFunction;
 | 
						|
      } catch (e) {
 | 
						|
        tooltipValueFormatFunction = null;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    for (let i = 0; i < this.subscription.data.length; i++) {
 | 
						|
      const series = this.subscription.data[i] as TbFlotSeries;
 | 
						|
      colors.push(series.dataKey.color);
 | 
						|
      const keySettings = series.dataKey.settings;
 | 
						|
      series.dataKey.tooltipValueFormatFunction = tooltipValueFormatFunction;
 | 
						|
      if (keySettings.tooltipValueFormatter && keySettings.tooltipValueFormatter.length) {
 | 
						|
        try {
 | 
						|
          series.dataKey.tooltipValueFormatFunction = new Function('value',
 | 
						|
            keySettings.tooltipValueFormatter) as TooltipValueFormatFunction;
 | 
						|
        } catch (e) {
 | 
						|
          series.dataKey.tooltipValueFormatFunction = tooltipValueFormatFunction;
 | 
						|
        }
 | 
						|
      }
 | 
						|
      series.lines = {
 | 
						|
        fill: keySettings.fillLines === true
 | 
						|
      };
 | 
						|
 | 
						|
      if (this.settings.stack && !this.comparisonEnabled) {
 | 
						|
        series.stack = !keySettings.excludeFromStacking;
 | 
						|
      } else {
 | 
						|
        series.stack = false;
 | 
						|
      }
 | 
						|
 | 
						|
      if (this.chartType === 'line' || this.chartType === 'state') {
 | 
						|
        series.lines.show = keySettings.showLines !== false;
 | 
						|
      } else {
 | 
						|
        series.lines.show = keySettings.showLines === true;
 | 
						|
      }
 | 
						|
      if (isDefined(keySettings.lineWidth) && keySettings.lineWidth !== null) {
 | 
						|
        series.lines.lineWidth = keySettings.lineWidth;
 | 
						|
      }
 | 
						|
      series.points = {
 | 
						|
        show: false,
 | 
						|
        radius: 8
 | 
						|
      };
 | 
						|
      if (keySettings.showPoints === true) {
 | 
						|
        series.points.show = true;
 | 
						|
        series.points.lineWidth = isDefined(keySettings.showPointsLineWidth) ? keySettings.showPointsLineWidth : 5;
 | 
						|
        series.points.radius = isDefined(keySettings.showPointsRadius) ? keySettings.showPointsRadius : 3;
 | 
						|
        series.points.symbol = isDefined(keySettings.showPointShape) ? keySettings.showPointShape : 'circle';
 | 
						|
        if (series.points.symbol === 'custom' && keySettings.pointShapeFormatter) {
 | 
						|
          try {
 | 
						|
            series.points.symbol = new Function('ctx, x, y, radius, shadow', keySettings.pointShapeFormatter);
 | 
						|
          } catch (e) {
 | 
						|
            series.points.symbol = 'circle';
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
      if (this.chartType === 'line' && this.settings.smoothLines && !series.points.show) {
 | 
						|
        series.curvedLines = {
 | 
						|
          apply: true
 | 
						|
        };
 | 
						|
      }
 | 
						|
 | 
						|
      const lineColor = tinycolor(series.dataKey.color);
 | 
						|
      lineColor.setAlpha(.75);
 | 
						|
 | 
						|
      series.highlightColor = lineColor.toRgbString();
 | 
						|
 | 
						|
      if (series.datasource.isAdditional) {
 | 
						|
        series.xaxisIndex = 1;
 | 
						|
        series.xaxis = 2;
 | 
						|
      } else {
 | 
						|
        series.xaxisIndex = 0;
 | 
						|
        series.xaxis = 1;
 | 
						|
      }
 | 
						|
 | 
						|
      if (this.yaxis) {
 | 
						|
        const units = series.dataKey.units && series.dataKey.units.length ? series.dataKey.units : this.trackUnits;
 | 
						|
        let yaxis: TbFlotAxisOptions;
 | 
						|
        if (keySettings.showSeparateAxis) {
 | 
						|
          yaxis = this.createYAxis(keySettings, units);
 | 
						|
          this.yaxes.push(yaxis);
 | 
						|
        } else {
 | 
						|
          yaxis = yaxesMap[units];
 | 
						|
          if (!yaxis) {
 | 
						|
            yaxis = this.createYAxis(keySettings, units);
 | 
						|
            yaxesMap[units] = yaxis;
 | 
						|
            this.yaxes.push(yaxis);
 | 
						|
          }
 | 
						|
        }
 | 
						|
        series.yaxisIndex = this.yaxes.indexOf(yaxis);
 | 
						|
        series.yaxis = series.yaxisIndex + 1;
 | 
						|
        yaxis.keysInfo[i] = {hidden: false};
 | 
						|
        yaxis.show = true;
 | 
						|
 | 
						|
        if (keySettings.thresholds && keySettings.thresholds.length) {
 | 
						|
          for (const threshold of keySettings.thresholds) {
 | 
						|
            if (threshold.thresholdValueSource === 'predefinedValue' && isFinite(threshold.thresholdValue)) {
 | 
						|
              const colorIndex = this.subscription.data.length + predefinedThresholds.length;
 | 
						|
              this.generateThreshold(predefinedThresholds, series.yaxis, threshold.lineWidth,
 | 
						|
                threshold.color, colorIndex, threshold.thresholdValue);
 | 
						|
            } else if (threshold.thresholdEntityAlias && threshold.thresholdAttribute) {
 | 
						|
              const entityAliasId = this.ctx.aliasController.getEntityAliasId(threshold.thresholdEntityAlias);
 | 
						|
              if (!entityAliasId) {
 | 
						|
                continue;
 | 
						|
              }
 | 
						|
              let datasource = thresholdsDatasources.filter((thresholdDatasource) => {
 | 
						|
                return thresholdDatasource.entityAliasId === entityAliasId;
 | 
						|
              })[0];
 | 
						|
              const dataKey: DataKey = {
 | 
						|
                type: DataKeyType.attribute,
 | 
						|
                name: threshold.thresholdAttribute,
 | 
						|
                label: threshold.thresholdAttribute,
 | 
						|
                settings: {
 | 
						|
                  yaxis: series.yaxis,
 | 
						|
                  lineWidth: threshold.lineWidth,
 | 
						|
                  color: threshold.color
 | 
						|
                } as TbFlotThresholdKeySettings,
 | 
						|
                _hash: Math.random()
 | 
						|
              };
 | 
						|
              if (datasource) {
 | 
						|
                datasource.dataKeys.push(dataKey);
 | 
						|
              } else {
 | 
						|
                datasource = {
 | 
						|
                  type: DatasourceType.entity,
 | 
						|
                  name: threshold.thresholdEntityAlias,
 | 
						|
                  aliasName: threshold.thresholdEntityAlias,
 | 
						|
                  entityAliasId,
 | 
						|
                  dataKeys: [ dataKey ]
 | 
						|
                };
 | 
						|
                thresholdsDatasources.push(datasource);
 | 
						|
              }
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
      if (this.labelPatternsSourcesData?.length) {
 | 
						|
        this.substituteLabelPatterns(series, i);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    this.subscribeForThresholdsAttributes(thresholdsDatasources);
 | 
						|
    this.predefinedThresholds = predefinedThresholds;
 | 
						|
 | 
						|
    this.options.colors = colors;
 | 
						|
    this.options.yaxes = deepClone(this.yaxes);
 | 
						|
    if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'state') {
 | 
						|
      if (this.chartType === 'bar') {
 | 
						|
        if (this.subscription.timeWindowConfig.aggregation &&
 | 
						|
          this.subscription.timeWindowConfig.aggregation.type === AggregationType.NONE) {
 | 
						|
          this.options.series.bars.barWidth = this.defaultBarWidth;
 | 
						|
        } else {
 | 
						|
          this.options.series.bars.barWidth = this.subscription.timeWindow.interval * 0.6;
 | 
						|
        }
 | 
						|
      }
 | 
						|
      this.options.xaxes[0].min = this.subscription.timeWindow.minTime;
 | 
						|
      this.options.xaxes[0].max = this.subscription.timeWindow.maxTime;
 | 
						|
      if (this.comparisonEnabled) {
 | 
						|
        this.options.xaxes[1].min = this.subscription.comparisonTimeWindow.minTime;
 | 
						|
        this.options.xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime;
 | 
						|
      }
 | 
						|
      let allThresholds = deepClone(this.predefinedThresholds);
 | 
						|
      if (this.attributesThresholds) {
 | 
						|
        allThresholds = allThresholds.concat(this.attributesThresholds);
 | 
						|
      }
 | 
						|
      this.latestDataThresholds = this.thresholdsSourcesDataUpdated(allThresholds, this.subscription.latestData, true);
 | 
						|
      this.options.grid.markings = allThresholds.concat(this.latestDataThresholds);
 | 
						|
    }
 | 
						|
 | 
						|
    this.checkMouseEvents();
 | 
						|
 | 
						|
    if (this.plot) {
 | 
						|
      this.plot.destroy();
 | 
						|
    }
 | 
						|
    if (this.chartType === 'pie' && this.animatedPie) {
 | 
						|
      this.pieDataAnimationDuration = 250;
 | 
						|
      this.pieData = deepClone(this.subscription.data);
 | 
						|
      this.pieRenderedData = [];
 | 
						|
      this.pieTargetData = [];
 | 
						|
      for (let i = 0; i < this.subscription.data.length; i++) {
 | 
						|
        this.pieTargetData[i] = (this.subscription.data[i].data && this.subscription.data[i].data[0])
 | 
						|
          ? this.subscription.data[i].data[0][1] : 0;
 | 
						|
      }
 | 
						|
      this.pieDataRendered();
 | 
						|
    }
 | 
						|
    this.plotInited = true;
 | 
						|
    this.createPlot();
 | 
						|
  }
 | 
						|
 | 
						|
  public update() {
 | 
						|
    if (this.updateTimeoutHandle) {
 | 
						|
      clearTimeout(this.updateTimeoutHandle);
 | 
						|
      this.updateTimeoutHandle = null;
 | 
						|
    }
 | 
						|
    if (this.subscription) {
 | 
						|
      if (!this.isMouseInteraction && this.plot) {
 | 
						|
        if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'state') {
 | 
						|
          let axisVisibilityChanged = false;
 | 
						|
          if (this.yaxis) {
 | 
						|
            for (let i = 0; i < this.subscription.data.length; i++) {
 | 
						|
              const series = this.subscription.data[i] as TbFlotSeries;
 | 
						|
              const yaxisIndex = series.yaxisIndex;
 | 
						|
              if (this.yaxes[yaxisIndex].keysInfo[i].hidden !== series.dataKey.hidden) {
 | 
						|
                this.yaxes[yaxisIndex].keysInfo[i].hidden = series.dataKey.hidden;
 | 
						|
                axisVisibilityChanged = true;
 | 
						|
              }
 | 
						|
 | 
						|
              if (this.labelPatternsSourcesData?.length) {
 | 
						|
                this.substituteLabelPatterns(series, i);
 | 
						|
              }
 | 
						|
            }
 | 
						|
            if (axisVisibilityChanged) {
 | 
						|
              this.options.yaxes.length = 0;
 | 
						|
              this.yaxes.forEach((yaxis) => {
 | 
						|
                let hidden = true;
 | 
						|
                yaxis.keysInfo.forEach((info) => {
 | 
						|
                  if (info) {
 | 
						|
                    hidden = hidden && info.hidden;
 | 
						|
                  }
 | 
						|
                });
 | 
						|
                yaxis.hidden = hidden;
 | 
						|
                let newIndex = 1;
 | 
						|
                if (!yaxis.hidden) {
 | 
						|
                  this.options.yaxes.push(yaxis);
 | 
						|
                  newIndex = this.options.yaxes.length;
 | 
						|
                }
 | 
						|
                for (let k = 0; k < yaxis.keysInfo.length; k++) {
 | 
						|
                  if (yaxis.keysInfo[k]) {
 | 
						|
                    (this.subscription.data[k] as TbFlotSeries).yaxis = newIndex;
 | 
						|
                  }
 | 
						|
                }
 | 
						|
 | 
						|
              });
 | 
						|
              this.options.yaxis = {
 | 
						|
                show: this.options.yaxes.length ? true : false
 | 
						|
              };
 | 
						|
            }
 | 
						|
          }
 | 
						|
 | 
						|
          this.options.xaxes[0].min = this.subscription.timeWindow.minTime;
 | 
						|
          this.options.xaxes[0].max = this.subscription.timeWindow.maxTime;
 | 
						|
          if (this.comparisonEnabled) {
 | 
						|
            this.options.xaxes[1].min = this.subscription.comparisonTimeWindow.minTime;
 | 
						|
            this.options.xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime;
 | 
						|
          }
 | 
						|
          if (this.chartType === 'bar') {
 | 
						|
            if (this.subscription.timeWindowConfig.aggregation &&
 | 
						|
              this.subscription.timeWindowConfig.aggregation.type === AggregationType.NONE) {
 | 
						|
              this.options.series.bars.barWidth = this.defaultBarWidth;
 | 
						|
            } else {
 | 
						|
              this.options.series.bars.barWidth = this.subscription.timeWindow.interval * 0.6;
 | 
						|
            }
 | 
						|
          }
 | 
						|
 | 
						|
          if (axisVisibilityChanged) {
 | 
						|
            this.redrawPlot();
 | 
						|
          } else {
 | 
						|
            this.plot.getOptions().xaxes[0].min = this.subscription.timeWindow.minTime;
 | 
						|
            this.plot.getOptions().xaxes[0].max = this.subscription.timeWindow.maxTime;
 | 
						|
            if (this.comparisonEnabled) {
 | 
						|
              this.plot.getOptions().xaxes[1].min = this.subscription.comparisonTimeWindow.minTime;
 | 
						|
              this.plot.getOptions().xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime;
 | 
						|
            }
 | 
						|
            if (this.chartType === 'bar') {
 | 
						|
              if (this.subscription.timeWindowConfig.aggregation &&
 | 
						|
                this.subscription.timeWindowConfig.aggregation.type === AggregationType.NONE) {
 | 
						|
                this.plot.getOptions().series.bars.barWidth = this.defaultBarWidth;
 | 
						|
              } else {
 | 
						|
                this.plot.getOptions().series.bars.barWidth = this.subscription.timeWindow.interval * 0.6;
 | 
						|
              }
 | 
						|
            }
 | 
						|
            this.updateData();
 | 
						|
          }
 | 
						|
        } else if (this.chartType === 'pie') {
 | 
						|
          if (this.animatedPie) {
 | 
						|
            this.nextPieDataAnimation(true);
 | 
						|
          } else {
 | 
						|
            this.updateData();
 | 
						|
          }
 | 
						|
        }
 | 
						|
      } else if (this.isMouseInteraction && this.plot) {
 | 
						|
        this.updateTimeoutHandle = setTimeout(this.update.bind(this), 30);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  public latestDataUpdate() {
 | 
						|
    if (this.latestUpdateTimeoutHandle) {
 | 
						|
      clearTimeout(this.latestUpdateTimeoutHandle);
 | 
						|
      this.latestUpdateTimeoutHandle = null;
 | 
						|
    }
 | 
						|
    if (this.subscription) {
 | 
						|
      if (!this.isMouseInteraction && this.plot) {
 | 
						|
        if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'state') {
 | 
						|
          let allThresholds = deepClone(this.predefinedThresholds);
 | 
						|
          if (this.attributesThresholds) {
 | 
						|
            allThresholds = allThresholds.concat(this.attributesThresholds);
 | 
						|
          }
 | 
						|
          this.latestDataThresholds = this.thresholdsSourcesDataUpdated(allThresholds, this.subscription.latestData, true);
 | 
						|
          this.options.grid.markings = allThresholds.concat(this.latestDataThresholds);
 | 
						|
          if (this.plot) {
 | 
						|
            this.plot.getOptions().grid.markings = this.options.grid.markings;
 | 
						|
            this.updateData();
 | 
						|
          }
 | 
						|
        }
 | 
						|
      } else if (this.isMouseInteraction && this.plot) {
 | 
						|
        this.latestUpdateTimeoutHandle = setTimeout(this.latestDataUpdate.bind(this), 30);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  private scalingPieRadius() {
 | 
						|
      let scalingLine;
 | 
						|
      this.ctx.width > this.ctx.height ? scalingLine = this.ctx.height : scalingLine = this.ctx.width;
 | 
						|
      let changeRadius = this.options.series.pie.stroke.width / scalingLine;
 | 
						|
      this.options.series.pie.radius = changeRadius < 1 ? this.settings.radius - changeRadius : 0;
 | 
						|
  }
 | 
						|
 | 
						|
  public resize() {
 | 
						|
    if (this.resizeTimeoutHandle) {
 | 
						|
      clearTimeout(this.resizeTimeoutHandle);
 | 
						|
      this.resizeTimeoutHandle = null;
 | 
						|
    }
 | 
						|
    if (this.plot && this.plotInited) {
 | 
						|
      if (this.chartType === 'pie' && this.settings.stroke?.width) {
 | 
						|
          this.scalingPieRadius();
 | 
						|
          this.redrawPlot();
 | 
						|
      } else {
 | 
						|
        const width = this.$element.width();
 | 
						|
        const height = this.$element.height();
 | 
						|
        if (width && height) {
 | 
						|
          this.plot.resize();
 | 
						|
          if (this.chartType !== 'pie') {
 | 
						|
            this.plot.setupGrid();
 | 
						|
          }
 | 
						|
          this.plot.draw();
 | 
						|
        } else {
 | 
						|
          this.resizeTimeoutHandle = setTimeout(this.resize.bind(this), 30);
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  public checkMouseEvents() {
 | 
						|
    const enabled = !this.ctx.isEdit;
 | 
						|
    if (isUndefined(this.mouseEventsEnabled) || this.mouseEventsEnabled !== enabled) {
 | 
						|
      this.mouseEventsEnabled = enabled;
 | 
						|
      if (this.$element) {
 | 
						|
        if (enabled) {
 | 
						|
          this.enableMouseEvents();
 | 
						|
        } else {
 | 
						|
          this.disableMouseEvents();
 | 
						|
        }
 | 
						|
        this.redrawPlot();
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  public destroy() {
 | 
						|
    this.cleanup();
 | 
						|
    if (this.tooltip) {
 | 
						|
      this.tooltip.stop(true);
 | 
						|
      this.tooltip.hide();
 | 
						|
    }
 | 
						|
    if (this.plot) {
 | 
						|
      this.plot.destroy();
 | 
						|
      this.plot = null;
 | 
						|
      this.plotInited = false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  private cleanup() {
 | 
						|
    if (this.updateTimeoutHandle) {
 | 
						|
      clearTimeout(this.updateTimeoutHandle);
 | 
						|
      this.updateTimeoutHandle = null;
 | 
						|
    }
 | 
						|
    if (this.latestUpdateTimeoutHandle) {
 | 
						|
      clearTimeout(this.latestUpdateTimeoutHandle);
 | 
						|
      this.latestUpdateTimeoutHandle = null;
 | 
						|
    }
 | 
						|
    if (this.createPlotTimeoutHandle) {
 | 
						|
      clearTimeout(this.createPlotTimeoutHandle);
 | 
						|
      this.createPlotTimeoutHandle = null;
 | 
						|
    }
 | 
						|
    if (this.resizeTimeoutHandle) {
 | 
						|
      clearTimeout(this.resizeTimeoutHandle);
 | 
						|
      this.resizeTimeoutHandle = null;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  private createPlot() {
 | 
						|
    if (this.createPlotTimeoutHandle) {
 | 
						|
      clearTimeout(this.createPlotTimeoutHandle);
 | 
						|
      this.createPlotTimeoutHandle = null;
 | 
						|
    }
 | 
						|
    if (this.plotInited && !this.plot) {
 | 
						|
      const width = this.$element.width();
 | 
						|
      const height = this.$element.height();
 | 
						|
      if (width && height) {
 | 
						|
        if (this.chartType === 'pie' && this.animatedPie) {
 | 
						|
          this.plot = $.plot(this.$element, this.pieData, this.options) as JQueryPlot;
 | 
						|
        } else {
 | 
						|
          this.plot = $.plot(this.$element, this.subscription.data, this.options) as JQueryPlot;
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        this.createPlotTimeoutHandle = setTimeout(this.createPlot.bind(this), 30);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  private updateData() {
 | 
						|
    this.plot.setData(this.subscription.data);
 | 
						|
    if (this.chartType !== 'pie') {
 | 
						|
      this.plot.setupGrid();
 | 
						|
    }
 | 
						|
    this.plot.draw();
 | 
						|
  }
 | 
						|
 | 
						|
  private redrawPlot() {
 | 
						|
    if (this.plot && this.plotInited) {
 | 
						|
      this.plot.destroy();
 | 
						|
      this.plot = null;
 | 
						|
      this.createPlot();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  private createYAxis(keySettings: TbFlotKeySettings, units: string): TbFlotAxisOptions {
 | 
						|
    const yaxis = deepClone(this.yaxis);
 | 
						|
    let tickDecimals: number;
 | 
						|
    let tickSize: number;
 | 
						|
    const label = keySettings.axisTitle && keySettings.axisTitle.length ? keySettings.axisTitle : yaxis.label;
 | 
						|
    if (isNumber(keySettings.axisTickDecimals)) {
 | 
						|
      tickDecimals = keySettings.axisTickDecimals;
 | 
						|
    } else {
 | 
						|
      tickDecimals = yaxis.tickDecimals;
 | 
						|
    }
 | 
						|
    if (isNumber(keySettings.axisTickSize)) {
 | 
						|
      tickSize = keySettings.axisTickSize;
 | 
						|
    } else {
 | 
						|
      tickSize = yaxis.tickSize;
 | 
						|
    }
 | 
						|
    const position = keySettings.axisPosition && keySettings.axisPosition.length ? keySettings.axisPosition : 'left';
 | 
						|
    const min = isDefined(keySettings.axisMin) ? keySettings.axisMin : yaxis.min;
 | 
						|
    const max = isDefined(keySettings.axisMax) ? keySettings.axisMax : yaxis.max;
 | 
						|
    yaxis.label = label;
 | 
						|
    yaxis.min = min;
 | 
						|
    yaxis.max = max;
 | 
						|
    yaxis.tickUnits = units;
 | 
						|
    yaxis.tickDecimals = tickDecimals;
 | 
						|
    yaxis.tickSize = tickSize;
 | 
						|
    if (position === 'right' && tickSize === null) {
 | 
						|
      yaxis.alignTicksWithAxis = 1;
 | 
						|
    } else {
 | 
						|
      yaxis.alignTicksWithAxis = null;
 | 
						|
    }
 | 
						|
    yaxis.position = position;
 | 
						|
 | 
						|
    yaxis.keysInfo = [];
 | 
						|
 | 
						|
    if (keySettings.axisTicksFormatter && keySettings.axisTicksFormatter.length) {
 | 
						|
      try {
 | 
						|
        yaxis.ticksFormatterFunction = new Function('value', keySettings.axisTicksFormatter) as TbFlotTicksFormatterFunction;
 | 
						|
      } catch (e) {
 | 
						|
        yaxis.ticksFormatterFunction = this.yaxis.ticksFormatterFunction;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return yaxis;
 | 
						|
  }
 | 
						|
 | 
						|
  private subscribeForThresholdsAttributes(datasources: Datasource[]) {
 | 
						|
    const thresholdsSourcesSubscriptionOptions: WidgetSubscriptionOptions = {
 | 
						|
      datasources,
 | 
						|
      useDashboardTimewindow: false,
 | 
						|
      type: widgetType.latest,
 | 
						|
      callbacks: {
 | 
						|
        onDataUpdated: (subscription) => {
 | 
						|
          let allThresholds = deepClone(this.predefinedThresholds);
 | 
						|
          if (this.latestDataThresholds) {
 | 
						|
            allThresholds = allThresholds.concat(this.latestDataThresholds);
 | 
						|
          }
 | 
						|
          this.attributesThresholds = this.thresholdsSourcesDataUpdated(allThresholds, subscription.data);
 | 
						|
          this.options.grid.markings = allThresholds.concat(this.attributesThresholds);
 | 
						|
          if (this.plot) {
 | 
						|
            this.plot.getOptions().grid.markings = this.options.grid.markings;
 | 
						|
            this.updateData();
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    };
 | 
						|
    this.ctx.subscriptionApi.createSubscription(thresholdsSourcesSubscriptionOptions, true).subscribe(
 | 
						|
      (subscription) => {
 | 
						|
        this.thresholdsSourcesSubscription = subscription;
 | 
						|
      }
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  private thresholdsSourcesDataUpdated(existingThresholds: TbFlotThresholdMarking[], data: DatasourceData[],
 | 
						|
                                       isLatest = false): TbFlotThresholdMarking[] {
 | 
						|
    const thresholds: TbFlotThresholdMarking[] = [];
 | 
						|
    data.forEach((keyData) => {
 | 
						|
      let skip = false;
 | 
						|
      let latestSettings: TbFlotLatestKeySettings;
 | 
						|
      if (isLatest) {
 | 
						|
        latestSettings = keyData.dataKey.settings;
 | 
						|
        if (!latestSettings.useAsThreshold) {
 | 
						|
          skip = true;
 | 
						|
        }
 | 
						|
      }
 | 
						|
      if (!skip && keyData && keyData.data && keyData.data[0]) {
 | 
						|
        const attrValue = keyData.data[0][1];
 | 
						|
        if (isNumeric(attrValue) && isFinite(attrValue)) {
 | 
						|
          let yaxis: number;
 | 
						|
          let lineWidth: number;
 | 
						|
          let color: string;
 | 
						|
          if (isLatest) {
 | 
						|
            yaxis = 1;
 | 
						|
            lineWidth = latestSettings.thresholdLineWidth;
 | 
						|
            color = latestSettings.thresholdColor || keyData.dataKey.color;
 | 
						|
          } else {
 | 
						|
            const settings: TbFlotThresholdKeySettings = keyData.dataKey.settings;
 | 
						|
            yaxis = settings.yaxis;
 | 
						|
            lineWidth = settings.lineWidth;
 | 
						|
            color = settings.color || keyData.dataKey.color;
 | 
						|
          }
 | 
						|
          const colorIndex = this.subscription.data.length + existingThresholds.length;
 | 
						|
          const threshold = this.generateThreshold(existingThresholds, yaxis, lineWidth, color, colorIndex, attrValue);
 | 
						|
          if (threshold != null) {
 | 
						|
            thresholds.push(threshold);
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    });
 | 
						|
    return thresholds;
 | 
						|
  }
 | 
						|
 | 
						|
  private generateThreshold(existingThresholds: TbFlotThresholdMarking[], yaxis: number, lineWidth: number,
 | 
						|
                            color: string, defaultColorIndex: number, thresholdValue: number): TbFlotThresholdMarking {
 | 
						|
    const marking: TbFlotThresholdMarking = {};
 | 
						|
    let markingYAxis;
 | 
						|
 | 
						|
    if (isDefined(yaxis) && yaxis !== 1) {
 | 
						|
      markingYAxis = 'y' + yaxis + 'axis';
 | 
						|
    } else {
 | 
						|
      markingYAxis = 'yaxis';
 | 
						|
    }
 | 
						|
 | 
						|
    if (isFinite(lineWidth)) {
 | 
						|
      marking.lineWidth = lineWidth;
 | 
						|
    }
 | 
						|
 | 
						|
    if (isDefined(color)) {
 | 
						|
      marking.color = color;
 | 
						|
    } else {
 | 
						|
      marking.color = this.utils.getMaterialColor(defaultColorIndex);
 | 
						|
    }
 | 
						|
 | 
						|
    marking[markingYAxis] = {
 | 
						|
      from: thresholdValue,
 | 
						|
      to: thresholdValue
 | 
						|
    };
 | 
						|
 | 
						|
    const similarMarkings = existingThresholds.filter((existingMarking) => {
 | 
						|
      return isEqual(existingMarking[markingYAxis], marking[markingYAxis]);
 | 
						|
    });
 | 
						|
    if (!similarMarkings.length) {
 | 
						|
     return marking;
 | 
						|
    }
 | 
						|
    return null;
 | 
						|
  }
 | 
						|
 | 
						|
  private subscribeForLabelPatternsSources(datasources: Datasource[]) {
 | 
						|
    const labelPatternsSourcesSubscriptionOptions: WidgetSubscriptionOptions = {
 | 
						|
      datasources,
 | 
						|
      useDashboardTimewindow: false,
 | 
						|
      type: widgetType.latest,
 | 
						|
      callbacks: {
 | 
						|
        onDataUpdated: (subscription) => {
 | 
						|
          this.labelPatternsParamsDataUpdated(subscription.data);
 | 
						|
        }
 | 
						|
      }
 | 
						|
    };
 | 
						|
    this.ctx.subscriptionApi.createSubscription(labelPatternsSourcesSubscriptionOptions, true).subscribe(
 | 
						|
      (subscription) => {
 | 
						|
        this.labelPatternsSourcesSubscription = subscription;
 | 
						|
      }
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  private labelPatternsParamsDataUpdated(data: DatasourceData[]) {
 | 
						|
    this.labelPatternsSourcesData = data;
 | 
						|
    for (let i = 0; i < this.subscription.data.length; i++) {
 | 
						|
      const series = this.subscription.data[i] as TbFlotSeries;
 | 
						|
      this.substituteLabelPatterns(series, i);
 | 
						|
    }
 | 
						|
    this.updateData();
 | 
						|
    this.ctx.detectChanges();
 | 
						|
  }
 | 
						|
 | 
						|
  private substituteLabelPatterns(series: TbFlotSeries, seriesIndex: number) {
 | 
						|
    const seriesLabelPatternsSourcesData = this.labelPatternsSourcesData.filter((item) => {
 | 
						|
      return item.datasource.entityId === series.datasource.entityId;
 | 
						|
    });
 | 
						|
    let label = createLabelFromDatasource(series.datasource, series.dataKey.pattern);
 | 
						|
    for (let i = 0; i < seriesLabelPatternsSourcesData.length; i++) {
 | 
						|
      const keyData = seriesLabelPatternsSourcesData[i];
 | 
						|
      if (keyData && keyData.data && keyData.data[0]) {
 | 
						|
        const attrValue = keyData.data[0][1];
 | 
						|
        const attrName = keyData.dataKey.name;
 | 
						|
        if (isDefined(attrValue) && (attrValue !== null)) {
 | 
						|
          label = insertVariable(label, attrName, attrValue);
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
    if (isDefined(this.subscription.legendData)) {
 | 
						|
      const targetLegendKeyIndex = this.subscription.legendData.keys.findIndex((key) => {
 | 
						|
        return key.dataIndex === seriesIndex;
 | 
						|
      });
 | 
						|
      if (targetLegendKeyIndex !== -1) {
 | 
						|
        this.subscription.legendData.keys[targetLegendKeyIndex].dataKey.label = label;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    series.dataKey.label = label;
 | 
						|
  }
 | 
						|
 | 
						|
  private seriesInfoDiv(label: string, color: string, value: any,
 | 
						|
                        units: string, trackDecimals: number, active: boolean,
 | 
						|
                        percent: number, valueFormatFunction: TooltipValueFormatFunction): JQuery<HTMLElement> {
 | 
						|
    const divElement = $('<div></div>');
 | 
						|
    divElement.css({
 | 
						|
      display: 'flex',
 | 
						|
      alignItems: 'center',
 | 
						|
      justifyContent: 'space-between'
 | 
						|
    });
 | 
						|
    const lineSpan = $('<span></span>');
 | 
						|
    lineSpan.css({
 | 
						|
      backgroundColor: color,
 | 
						|
      width: '20px',
 | 
						|
      height: '3px',
 | 
						|
      display: 'inline-block',
 | 
						|
      verticalAlign: 'middle',
 | 
						|
      marginRight: '5px'
 | 
						|
    });
 | 
						|
    divElement.append(lineSpan);
 | 
						|
    const labelSpan = $(`<span>${label}:</span>`);
 | 
						|
    labelSpan.css({
 | 
						|
      marginRight: '10px'
 | 
						|
    });
 | 
						|
    if (active) {
 | 
						|
      labelSpan.css({
 | 
						|
        color: '#FFF',
 | 
						|
        fontWeight: '700'
 | 
						|
      });
 | 
						|
    }
 | 
						|
    divElement.append(labelSpan);
 | 
						|
    let valueContent: string;
 | 
						|
    if (valueFormatFunction) {
 | 
						|
      valueContent = valueFormatFunction(value);
 | 
						|
    } else {
 | 
						|
      valueContent = this.ctx.utils.formatValue(value, trackDecimals, units);
 | 
						|
    }
 | 
						|
    if (isNumber(percent)) {
 | 
						|
      valueContent += ' (' + Math.round(percent) + ' %)';
 | 
						|
    }
 | 
						|
    const valueSpan =  $(`<span>${valueContent}</span>`);
 | 
						|
    valueSpan.css({
 | 
						|
      marginLeft: 'auto',
 | 
						|
      fontWeight: '700'
 | 
						|
    });
 | 
						|
    if (active) {
 | 
						|
      valueSpan.css({
 | 
						|
        color: '#FFF'
 | 
						|
      });
 | 
						|
    }
 | 
						|
    divElement.append(valueSpan);
 | 
						|
    return divElement;
 | 
						|
  }
 | 
						|
 | 
						|
  private seriesInfoDivFromInfo(seriesHoverInfo: TbFlotSeriesHoverInfo, seriesIndex: number): string {
 | 
						|
    const units = seriesHoverInfo.units && seriesHoverInfo.units.length ? seriesHoverInfo.units : this.trackUnits;
 | 
						|
    const decimals = isDefinedAndNotNull(seriesHoverInfo.decimals) ? seriesHoverInfo.decimals : this.trackDecimals;
 | 
						|
    const divElement = this.seriesInfoDiv(seriesHoverInfo.label, seriesHoverInfo.color,
 | 
						|
      seriesHoverInfo.value, units, decimals, seriesHoverInfo.index === seriesIndex, null, seriesHoverInfo.tooltipValueFormatFunction);
 | 
						|
    return divElement.prop('outerHTML');
 | 
						|
  }
 | 
						|
 | 
						|
  private createTooltipElement(): JQuery<any> {
 | 
						|
    const tooltip = $('<div id="flot-series-tooltip" class="flot-mouse-value"></div>');
 | 
						|
    tooltip.css({
 | 
						|
      fontSize: '12px',
 | 
						|
      fontFamily: 'Roboto',
 | 
						|
      fontWeight: '300',
 | 
						|
      lineHeight: '18px',
 | 
						|
      opacity: '1',
 | 
						|
      backgroundColor: 'rgba(0,0,0,0.7)',
 | 
						|
      color: '#D9DADB',
 | 
						|
      position: 'absolute',
 | 
						|
      display: 'none',
 | 
						|
      zIndex: '1100',
 | 
						|
      padding: '4px 10px',
 | 
						|
      borderRadius: '4px'
 | 
						|
    }).appendTo('body');
 | 
						|
    return tooltip;
 | 
						|
  }
 | 
						|
 | 
						|
  private formatPieTooltip(item: TbFlotPlotItem): string {
 | 
						|
    const units = item.series.dataKey.units && item.series.dataKey.units.length ? item.series.dataKey.units : this.trackUnits;
 | 
						|
    const decimals = isDefinedAndNotNull(item.series.dataKey.decimals) ? item.series.dataKey.decimals : this.trackDecimals;
 | 
						|
    const divElement = this.seriesInfoDiv(item.series.dataKey.label, item.series.dataKey.color,
 | 
						|
      item.datapoint[1][0][1], units, decimals, true, item.series.percent, item.series.dataKey.tooltipValueFormatFunction);
 | 
						|
    return divElement.prop('outerHTML');
 | 
						|
  }
 | 
						|
 | 
						|
  private formatChartTooltip(hoverInfo: TbFlotHoverInfo[], seriesIndex: number): string {
 | 
						|
    let content = '';
 | 
						|
    if (this.tooltipIndividual) {
 | 
						|
      let seriesHoverArray: TbFlotSeriesHoverInfo[];
 | 
						|
      if (hoverInfo[1] && hoverInfo[1].seriesHover.length) {
 | 
						|
        seriesHoverArray = hoverInfo[0].seriesHover.concat(hoverInfo[1].seriesHover);
 | 
						|
      } else {
 | 
						|
        seriesHoverArray = hoverInfo[0].seriesHover;
 | 
						|
      }
 | 
						|
      const found = seriesHoverArray.filter((seriesHover) => {
 | 
						|
        return seriesHover.index === seriesIndex;
 | 
						|
      });
 | 
						|
      if (found && found.length) {
 | 
						|
        const timestamp = parseInt(found[0].time, 10);
 | 
						|
        const date = moment(timestamp).format('YYYY-MM-DD HH:mm:ss');
 | 
						|
        const dateDiv = $('<div>' + date + '</div>');
 | 
						|
        dateDiv.css({
 | 
						|
          display: 'flex',
 | 
						|
          alignItems: 'center',
 | 
						|
          justifyContent: 'center',
 | 
						|
          padding: '4px',
 | 
						|
          fontWeight: '700'
 | 
						|
        });
 | 
						|
        content += dateDiv.prop('outerHTML');
 | 
						|
        content += this.seriesInfoDivFromInfo(found[0], seriesIndex);
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      let maxRows: number;
 | 
						|
      if (hoverInfo[1] && hoverInfo[1].seriesHover.length) {
 | 
						|
        maxRows = 5;
 | 
						|
      } else {
 | 
						|
        maxRows = 15;
 | 
						|
      }
 | 
						|
      let columns = 0;
 | 
						|
      if (hoverInfo[1] && (hoverInfo[1].seriesHover.length > hoverInfo[0].seriesHover.length)) {
 | 
						|
        columns = Math.ceil(hoverInfo[1].seriesHover.length / maxRows);
 | 
						|
      } else {
 | 
						|
        columns = Math.ceil(hoverInfo[0].seriesHover.length / maxRows);
 | 
						|
      }
 | 
						|
      hoverInfo.forEach((hoverData) => {
 | 
						|
        if (isNumber(hoverData.time)) {
 | 
						|
          let columnsContent = '';
 | 
						|
          const timestamp = parseInt(hoverData.time, 10);
 | 
						|
          const date = moment(timestamp).format('YYYY-MM-DD HH:mm:ss');
 | 
						|
          const dateDiv = $('<div>' + date + '</div>');
 | 
						|
          dateDiv.css({
 | 
						|
            display: 'flex',
 | 
						|
            alignItems: 'center',
 | 
						|
            justifyContent: 'center',
 | 
						|
            padding: '4px',
 | 
						|
            fontWeight: '700'
 | 
						|
          });
 | 
						|
          content += dateDiv.prop('outerHTML');
 | 
						|
 | 
						|
          const seriesDiv = $('<div></div>');
 | 
						|
          seriesDiv.css({
 | 
						|
            display: 'flex',
 | 
						|
            flexDirection: 'row'
 | 
						|
          });
 | 
						|
          for (let c = 0; c < columns; c++) {
 | 
						|
            const columnDiv = $('<div></div>');
 | 
						|
            columnDiv.css({
 | 
						|
              display: 'flex',
 | 
						|
              flexDirection: 'column',
 | 
						|
              flex: '1'
 | 
						|
            });
 | 
						|
            let columnContent = '';
 | 
						|
            for (let i = c * maxRows; i < (c + 1) * maxRows; i++) {
 | 
						|
              if (i >= hoverData.seriesHover.length) {
 | 
						|
                break;
 | 
						|
              }
 | 
						|
              const seriesHoverInfo = hoverData.seriesHover[i];
 | 
						|
              columnContent += this.seriesInfoDivFromInfo(seriesHoverInfo, seriesIndex);
 | 
						|
            }
 | 
						|
            columnDiv.html(columnContent);
 | 
						|
 | 
						|
            if (columnContent) {
 | 
						|
              if (c > 0) {
 | 
						|
                columnsContent += '<span style="min-width: 20px;"></span>';
 | 
						|
              }
 | 
						|
              columnsContent += columnDiv.prop('outerHTML');
 | 
						|
            }
 | 
						|
          }
 | 
						|
          seriesDiv.html(columnsContent);
 | 
						|
          content += seriesDiv.prop('outerHTML');
 | 
						|
        }
 | 
						|
      });
 | 
						|
    }
 | 
						|
    return content;
 | 
						|
  }
 | 
						|
 | 
						|
  private formatYAxisTicks(value: number, axis?: TbFlotPlotAxis): string {
 | 
						|
    if (this.settings.yaxis && this.settings.yaxis.showLabels === false) {
 | 
						|
      return '';
 | 
						|
    }
 | 
						|
    if (axis.options.ticksFormatterFunction) {
 | 
						|
      return axis.options.ticksFormatterFunction(value);
 | 
						|
    }
 | 
						|
    const factor = axis.options.tickDecimals ? Math.pow(10, axis.options.tickDecimals) : 1;
 | 
						|
    let formatted = '' + Math.round(value * factor) / factor;
 | 
						|
    if (isDefined(axis.options.tickDecimals) && axis.options.tickDecimals !== null) {
 | 
						|
      const decimal = formatted.indexOf('.');
 | 
						|
      const precision = decimal === -1 ? 0 : formatted.length - decimal - 1;
 | 
						|
      if (precision < axis.options.tickDecimals) {
 | 
						|
        formatted = (precision ? formatted : formatted + '.') + ('' + factor).substr(1, axis.options.tickDecimals - precision);
 | 
						|
      }
 | 
						|
    }
 | 
						|
    if (axis.options.tickUnits) {
 | 
						|
      formatted += ' ' + axis.options.tickUnits;
 | 
						|
    }
 | 
						|
    return formatted;
 | 
						|
  }
 | 
						|
 | 
						|
  private enableMouseEvents() {
 | 
						|
    this.$element.css('pointer-events', '');
 | 
						|
    this.$element.addClass('mouse-events');
 | 
						|
    this.options.selection = { mode : 'x' };
 | 
						|
    this.$element.bind('plothover', this.flotHoverHandler);
 | 
						|
    this.$element.bind('plotselected', this.flotSelectHandler);
 | 
						|
    this.$element.bind('dblclick', this.dblclickHandler);
 | 
						|
    this.$element.bind('mousedown', this.mousedownHandler);
 | 
						|
    this.$element.bind('mouseup', this.mouseupHandler);
 | 
						|
    this.$element.bind('mouseleave', this.mouseleaveHandler);
 | 
						|
    this.$element.bind('plotclick', this.flotClickHandler);
 | 
						|
  }
 | 
						|
 | 
						|
  private disableMouseEvents() {
 | 
						|
    this.$element.css('pointer-events', 'none');
 | 
						|
    this.$element.removeClass('mouse-events');
 | 
						|
    this.options.selection = { mode : null };
 | 
						|
    this.$element.unbind('plothover', this.flotHoverHandler);
 | 
						|
    this.$element.unbind('plotselected', this.flotSelectHandler);
 | 
						|
    this.$element.unbind('dblclick', this.dblclickHandler);
 | 
						|
    this.$element.unbind('mousedown', this.mousedownHandler);
 | 
						|
    this.$element.unbind('mouseup', this.mouseupHandler);
 | 
						|
    this.$element.unbind('mouseleave', this.mouseleaveHandler);
 | 
						|
    this.$element.unbind('plotclick', this.flotClickHandler);
 | 
						|
  }
 | 
						|
 | 
						|
  private onFlotHover(e: any, pos: JQueryPlotPoint, item: TbFlotPlotItem) {
 | 
						|
    if (!this.plot || !this.tooltip) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    if ((!this.tooltipIndividual || item) && !this.ctx.isEdit) {
 | 
						|
      const multipleModeTooltip = !this.tooltipIndividual;
 | 
						|
      if (multipleModeTooltip) {
 | 
						|
        this.plot.unhighlight();
 | 
						|
      }
 | 
						|
      const pageX = pos.pageX;
 | 
						|
      const pageY = pos.pageY;
 | 
						|
 | 
						|
      let tooltipHtml;
 | 
						|
      let hoverInfo: TbFlotHoverInfo[];
 | 
						|
 | 
						|
      if (this.chartType === 'pie') {
 | 
						|
        tooltipHtml = this.formatPieTooltip(item);
 | 
						|
      } else {
 | 
						|
        hoverInfo = this.getHoverInfo(this.plot.getData(), pos);
 | 
						|
        if (isNumber(hoverInfo[0].time) || (hoverInfo[1] && isNumber(hoverInfo[1].time))) {
 | 
						|
          hoverInfo[0].seriesHover.sort((a, b) => {
 | 
						|
            return b.value - a.value;
 | 
						|
          });
 | 
						|
          if (hoverInfo[1] && hoverInfo[1].seriesHover.length) {
 | 
						|
            hoverInfo[1].seriesHover.sort((a, b) => {
 | 
						|
              return b.value - a.value;
 | 
						|
            });
 | 
						|
          }
 | 
						|
          tooltipHtml = this.formatChartTooltip(hoverInfo, item ? item.seriesIndex : -1);
 | 
						|
        }
 | 
						|
      }
 | 
						|
      if (tooltipHtml) {
 | 
						|
        this.tooltip.html(tooltipHtml)
 | 
						|
          .css({top: 0, left: 0})
 | 
						|
          .fadeIn(200);
 | 
						|
 | 
						|
        const windowWidth = $( window ).width();
 | 
						|
        const windowHeight = $( window ).height();
 | 
						|
        const tooltipWidth = this.tooltip.width();
 | 
						|
        const tooltipHeight = this.tooltip.height();
 | 
						|
        let left = pageX + 5;
 | 
						|
        let top = pageY + 5;
 | 
						|
        if (windowWidth - pageX < tooltipWidth + 50) {
 | 
						|
          left = pageX - tooltipWidth - 10;
 | 
						|
        }
 | 
						|
        if (windowHeight - pageY < tooltipHeight + 20) {
 | 
						|
          top = pageY - tooltipHeight - 10;
 | 
						|
        }
 | 
						|
        this.tooltip.css({
 | 
						|
          top,
 | 
						|
          left
 | 
						|
        });
 | 
						|
        if (multipleModeTooltip) {
 | 
						|
          hoverInfo.forEach((hoverData) => {
 | 
						|
            hoverData.seriesHover.forEach((seriesHoverInfo) => {
 | 
						|
              this.plot.highlight(seriesHoverInfo.index, seriesHoverInfo.hoverIndex);
 | 
						|
            });
 | 
						|
          });
 | 
						|
        }
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      this.tooltip.stop(true);
 | 
						|
      this.tooltip.hide();
 | 
						|
      this.plot.unhighlight();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  private onFlotSelect(e: any, ranges: JQueryPlotSelectionRanges) {
 | 
						|
    if (!this.plot) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    this.plot.clearSelection();
 | 
						|
    this.subscription.onUpdateTimewindow(ranges.xaxis.from, ranges.xaxis.to);
 | 
						|
  }
 | 
						|
 | 
						|
  private onFlotDblClick() {
 | 
						|
    this.subscription.onResetTimewindow();
 | 
						|
  }
 | 
						|
 | 
						|
  private onFlotMouseDown() {
 | 
						|
    this.isMouseInteraction = true;
 | 
						|
  }
 | 
						|
 | 
						|
  private onFlotMouseUp() {
 | 
						|
    this.isMouseInteraction = false;
 | 
						|
  }
 | 
						|
 | 
						|
  private onFlotMouseLeave() {
 | 
						|
    if (!this.tooltip) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    this.tooltip.stop(true);
 | 
						|
    this.tooltip.hide();
 | 
						|
    if (this.plot) {
 | 
						|
      this.plot.unhighlight();
 | 
						|
    }
 | 
						|
    this.isMouseInteraction = false;
 | 
						|
  }
 | 
						|
 | 
						|
  private onFlotClick(e: any, pos: JQueryPlotPoint, item: TbFlotPlotItem) {
 | 
						|
    if (!this.plot) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    this.onPieSliceClick(e, item);
 | 
						|
  }
 | 
						|
 | 
						|
  private getHoverInfo(seriesList: TbFlotPlotDataSeries[], pos: JQueryPlotPoint): TbFlotHoverInfo[] {
 | 
						|
    let i: number;
 | 
						|
    let series: TbFlotPlotDataSeries;
 | 
						|
    let hoverIndex: number;
 | 
						|
    let hoverDistance: number;
 | 
						|
    let minDistance: number;
 | 
						|
    let pointTime: any;
 | 
						|
    let minTime: any;
 | 
						|
    let minTimeHistorical: any;
 | 
						|
    let hoverData: TbFlotSeriesHoverInfo;
 | 
						|
    let value: any;
 | 
						|
    let lastValue = 0;
 | 
						|
    let minDistanceHistorical: number;
 | 
						|
    let deltaX = 0;
 | 
						|
    const results: TbFlotHoverInfo[] = [{
 | 
						|
      seriesHover: []
 | 
						|
    }];
 | 
						|
    if (this.comparisonEnabled) {
 | 
						|
      results.push({
 | 
						|
        seriesHover: []
 | 
						|
      });
 | 
						|
    }
 | 
						|
    if (this.chartType === 'bar' && this.options.series.bars.align !== 'left') {
 | 
						|
      if (this.options.series.bars.align === 'center') {
 | 
						|
        deltaX = this.options.series.bars.barWidth / 2;
 | 
						|
      } else {
 | 
						|
        deltaX = this.options.series.bars.barWidth;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    for (i = 0; i < seriesList.length; i++) {
 | 
						|
      series = seriesList[i];
 | 
						|
      let posx: number;
 | 
						|
      if (series.datasource.isAdditional) {
 | 
						|
        posx = pos.x2;
 | 
						|
      } else {
 | 
						|
        posx = pos.x;
 | 
						|
      }
 | 
						|
      posx += deltaX;
 | 
						|
      hoverIndex = this.findHoverIndexFromData(posx, series);
 | 
						|
      if (series.data[hoverIndex] && series.data[hoverIndex][0]) {
 | 
						|
        hoverDistance = posx - series.data[hoverIndex][0];
 | 
						|
        pointTime = series.data[hoverIndex][0];
 | 
						|
 | 
						|
        if (series.datasource.isAdditional) {
 | 
						|
          if (!minDistanceHistorical
 | 
						|
            || (hoverDistance >= 0 && (hoverDistance < minDistanceHistorical || minDistanceHistorical < 0))
 | 
						|
            || (hoverDistance < 0 && hoverDistance > minDistanceHistorical)) {
 | 
						|
            minDistanceHistorical = hoverDistance;
 | 
						|
            minTimeHistorical = pointTime;
 | 
						|
          }
 | 
						|
        } else if (!minDistance
 | 
						|
          || (hoverDistance >= 0 && (hoverDistance < minDistance || minDistance < 0))
 | 
						|
          || (hoverDistance < 0 && hoverDistance > minDistance)) {
 | 
						|
          minDistance = hoverDistance;
 | 
						|
          minTime = pointTime;
 | 
						|
        }
 | 
						|
        if (series.stack) {
 | 
						|
          if (this.tooltipIndividual || !this.tooltipCumulative) {
 | 
						|
            value = series.data[hoverIndex][1];
 | 
						|
          } else {
 | 
						|
            lastValue += series.data[hoverIndex][1];
 | 
						|
            value = lastValue;
 | 
						|
          }
 | 
						|
        } else {
 | 
						|
          value = series.data[hoverIndex][1];
 | 
						|
        }
 | 
						|
        if (series.stack || (series.curvedLines && series.curvedLines.apply)) {
 | 
						|
          hoverIndex = this.findHoverIndexFromDataPoints(posx, series, hoverIndex);
 | 
						|
        }
 | 
						|
        if (!this.hideZeros || value) {
 | 
						|
          hoverData = {
 | 
						|
            value,
 | 
						|
            hoverIndex,
 | 
						|
            color: series.dataKey.color,
 | 
						|
            label: series.dataKey.label,
 | 
						|
            units: series.dataKey.units,
 | 
						|
            decimals: series.dataKey.decimals,
 | 
						|
            tooltipValueFormatFunction: series.dataKey.tooltipValueFormatFunction,
 | 
						|
            time: pointTime,
 | 
						|
            distance: hoverDistance,
 | 
						|
            index: i
 | 
						|
          };
 | 
						|
          if (series.datasource.isAdditional) {
 | 
						|
            results[1].seriesHover.push(hoverData);
 | 
						|
          } else {
 | 
						|
            results[0].seriesHover.push(hoverData);
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
    if (results[1] && results[1].seriesHover.length) {
 | 
						|
      results[1].time = minTimeHistorical;
 | 
						|
    }
 | 
						|
    results[0].time = minTime;
 | 
						|
    return results;
 | 
						|
  }
 | 
						|
 | 
						|
  private findHoverIndexFromData(posX: number, series: TbFlotPlotDataSeries): number {
 | 
						|
    let lower = 0;
 | 
						|
    let upper = series.data.length - 1;
 | 
						|
    let middle: number;
 | 
						|
    const index: number = null;
 | 
						|
    while (index === null) {
 | 
						|
      if (lower > upper) {
 | 
						|
        return Math.max(upper, 0);
 | 
						|
      }
 | 
						|
      middle = Math.floor((lower + upper) / 2);
 | 
						|
      if (series.data[middle][0] === posX) {
 | 
						|
        return middle;
 | 
						|
      } else if (series.data[middle][0] < posX) {
 | 
						|
        lower = middle + 1;
 | 
						|
      } else {
 | 
						|
        upper = middle - 1;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  private findHoverIndexFromDataPoints(posX: number, series: TbFlotPlotDataSeries, last: number): number {
 | 
						|
    const ps = series.datapoints.pointsize;
 | 
						|
    const initial = last * ps;
 | 
						|
    const len = series.datapoints.points.length;
 | 
						|
    let j: number;
 | 
						|
    for (j = initial; j < len; j += ps) {
 | 
						|
      if ((!series.lines.steps && series.datapoints.points[initial] != null && series.datapoints.points[j] == null)
 | 
						|
        || series.datapoints.points[j] > posX) {
 | 
						|
        return Math.max(j - ps,  0) / ps;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return j / ps - 1;
 | 
						|
  }
 | 
						|
 | 
						|
  pieDataRendered() {
 | 
						|
    for (let i = 0; i < this.pieTargetData.length; i++) {
 | 
						|
      const value = this.pieTargetData[i] ? this.pieTargetData[i] : 0;
 | 
						|
      this.pieRenderedData[i] = value;
 | 
						|
      if (!this.pieData[i].data[0]) {
 | 
						|
        this.pieData[i].data[0] = [0, 0];
 | 
						|
      }
 | 
						|
      this.pieData[i].data[0][1] = value;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  nextPieDataAnimation(start) {
 | 
						|
    if (start) {
 | 
						|
      this.finishPieDataAnimation();
 | 
						|
      this.pieAnimationStartTime = this.pieAnimationLastTime = Date.now();
 | 
						|
      for (let i = 0;  i < this.subscription.data.length; i++) {
 | 
						|
        this.pieTargetData[i] = (this.subscription.data[i].data && this.subscription.data[i].data[0])
 | 
						|
          ? this.subscription.data[i].data[0][1] : 0;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    if (this.pieAnimationCaf) {
 | 
						|
      this.pieAnimationCaf();
 | 
						|
      this.pieAnimationCaf = null;
 | 
						|
    }
 | 
						|
    this.pieAnimationCaf = this.ctx.$scope.raf.raf(this.onPieDataAnimation.bind(this));
 | 
						|
  }
 | 
						|
 | 
						|
  onPieDataAnimation() {
 | 
						|
    const time = Date.now();
 | 
						|
    const elapsed = time - this.pieAnimationLastTime; // this.pieAnimationStartTime;
 | 
						|
    const progress = (time - this.pieAnimationStartTime) / this.pieDataAnimationDuration;
 | 
						|
    if (progress >= 1) {
 | 
						|
      this.finishPieDataAnimation();
 | 
						|
    } else {
 | 
						|
      if (elapsed >= 40) {
 | 
						|
        for (let i = 0; i < this.pieTargetData.length; i++) {
 | 
						|
          const prevValue = this.pieRenderedData[i];
 | 
						|
          const targetValue = this.pieTargetData[i];
 | 
						|
          const value = prevValue + (targetValue - prevValue) * progress;
 | 
						|
          if (!this.pieData[i].data[0]) {
 | 
						|
            this.pieData[i].data[0] = [0, 0];
 | 
						|
          }
 | 
						|
          this.pieData[i].data[0][1] = value;
 | 
						|
        }
 | 
						|
        this.plot.setData(this.pieData);
 | 
						|
        this.plot.draw();
 | 
						|
        this.pieAnimationLastTime = time;
 | 
						|
      }
 | 
						|
      this.nextPieDataAnimation(false);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  private finishPieDataAnimation() {
 | 
						|
    this.pieDataRendered();
 | 
						|
    this.plot.setData(this.pieData);
 | 
						|
    this.plot.draw();
 | 
						|
  }
 | 
						|
 | 
						|
  private onPieSliceClick($event: any, item: TbFlotPlotItem) {
 | 
						|
    const descriptors = this.ctx.actionsApi.getActionDescriptors('sliceClick');
 | 
						|
    if ($event && descriptors.length) {
 | 
						|
      $event.stopPropagation();
 | 
						|
      const entityInfo = this.ctx.actionsApi.getActiveEntityInfo();
 | 
						|
      const entityId = entityInfo ? entityInfo.entityId : null;
 | 
						|
      const entityName = entityInfo ? entityInfo.entityName : null;
 | 
						|
      const entityLabel = entityInfo ? entityInfo.entityLabel : null;
 | 
						|
      this.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName, item, entityLabel);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
}
 |