');\n }\n\n clear(plot) {\n this._visible = false;\n this.$tooltip.detach();\n plot.clearCrosshair();\n plot.unhighlight();\n };\n\n show(pos, item?) {\n if(item === undefined) {\n item = this._lastItem;\n } else {\n this._lastItem = item;\n }\n\n this._visible = true;\n var plot = this.$elem.data().plot;\n var plotData = plot.getData();\n var xAxes = plot.getXAxes();\n var xMode = xAxes[0].options.mode;\n var seriesList = this.getSeriesFn();\n var allSeriesMode = this.panel.tooltip.shared;\n var group, value, absoluteTime, hoverInfo, i, series, seriesHtml, tooltipFormat;\n\n // if panelRelY is defined another panel wants us to show a tooltip\n // get pageX from position on x axis and pageY from relative position in original panel\n if (pos.panelRelY) {\n var pointOffset = plot.pointOffset({x: pos.x});\n if (Number.isNaN(pointOffset.left) || pointOffset.left < 0 || pointOffset.left > this.$elem.width()) {\n this.clear(plot);\n return;\n }\n pos.pageX = this.$elem.offset().left + pointOffset.left;\n pos.pageY = this.$elem.offset().top + this.$elem.height() * pos.panelRelY;\n var isVisible = pos.pageY >= $(window).scrollTop() && \n pos.pageY <= $(window).innerHeight() + $(window).scrollTop();\n if (!isVisible) {\n this.clear(plot);\n return;\n }\n plot.setCrosshair(pos);\n allSeriesMode = true;\n\n if (this.dashboard.sharedCrosshairModeOnly()) {\n // if only crosshair mode we are done\n return;\n }\n }\n\n if (seriesList.length === 0) {\n return;\n }\n\n if (seriesList[0].hasMsResolution) {\n tooltipFormat = 'YYYY-MM-DD HH:mm:ss.SSS';\n } else {\n tooltipFormat = 'YYYY-MM-DD HH:mm:ss';\n }\n\n if (allSeriesMode) {\n plot.unhighlight();\n\n var seriesHoverInfo = this._getMultiSeriesPlotHoverInfo(plotData, pos);\n seriesHtml = '';\n absoluteTime = this.dashboard.formatDate(seriesHoverInfo.time, tooltipFormat);\n\n // Dynamically reorder the hovercard for the current time point if the\n // option is enabled.\n if (this.panel.tooltip.sort === 2) {\n seriesHoverInfo.series.sort((a: any, b: any) => b.value - a.value);\n } else if (this.panel.tooltip.sort === 1) {\n seriesHoverInfo.series.sort((a: any, b: any) => a.value - b.value);\n }\n\n for (i = 0; i < seriesHoverInfo.series.length; i++) {\n hoverInfo = seriesHoverInfo.series[i];\n\n if (hoverInfo.hidden) {\n continue;\n }\n\n var highlightClass = '';\n if (item && hoverInfo.index === item.seriesIndex) {\n highlightClass = 'graph-tooltip-list-item--highlight';\n }\n\n series = seriesList[hoverInfo.index];\n\n value = series.formatValue(hoverInfo.value);\n\n seriesHtml += '
';\n plot.highlight(hoverInfo.index, hoverInfo.hoverIndex);\n }\n\n seriesHtml += this._appendAnomaliesHTML(pos.x);\n\n this._renderAndShow(absoluteTime, seriesHtml, pos, xMode);\n }\n // single series tooltip\n else if (item) {\n series = seriesList[item.seriesIndex];\n group = '
';\n group += ' ' + series.aliasEscaped + ':
';\n\n if (this.panel.stack && this.panel.tooltip.value_type === 'individual') {\n value = item.datapoint[1] - item.datapoint[2];\n }\n else {\n value = item.datapoint[1];\n }\n\n value = series.formatValue(value);\n\n absoluteTime = this.dashboard.formatDate(item.datapoint[0], tooltipFormat);\n\n group += '
' + value + '
';\n\n group += this._appendAnomaliesHTML(pos.x);\n\n this._renderAndShow(absoluteTime, group, pos, xMode);\n }\n // no hit\n else {\n this.$tooltip.detach();\n }\n };\n\n \n destroy() {\n this._visible = false;\n this.$tooltip.remove();\n };\n\n get visible() { return this._visible; }\n\n private _findHoverIndexFromDataPoints(posX, series, last) {\n var ps = series.datapoints.pointsize;\n var initial = last*ps;\n var len = series.datapoints.points.length;\n for (var j = initial; j < len; j += ps) {\n // Special case of a non stepped line, highlight the very last point just before a null point\n if ((!series.lines.steps && series.datapoints.points[initial] != null && series.datapoints.points[j] == null)\n //normal case\n || series.datapoints.points[j] > posX) {\n return Math.max(j - ps, 0)/ps;\n }\n }\n return j/ps - 1;\n };\n\n private _findHoverIndexFromData(posX, series) {\n var lower = 0;\n var upper = series.data.length - 1;\n var middle;\n while (true) {\n if (lower > upper) {\n return Math.max(upper, 0);\n }\n middle = Math.floor((lower + upper) / 2);\n if (series.data[middle][0] === posX) {\n return middle;\n } else if (series.data[middle][0] < posX) {\n lower = middle + 1;\n } else {\n upper = middle - 1;\n }\n }\n };\n\n private _appendAnomaliesHTML(pos: number): string {\n var result = '';\n var segments = this._anomalySegmentsSearcher(pos);\n if(segments.length === 0) {\n return '';\n }\n segments.forEach(s => {\n var from = this.dashboard.formatDate(s.segment.from, 'HH:mm:ss.SSS');\n var to = this.dashboard.formatDate(s.segment.to, 'HH:mm:ss.SSS');\n \n result += `\n
\n `;\n\n \n });\n return result;\n }\n\n private _renderAndShow(absoluteTime, innerHtml, pos, xMode) {\n if (xMode === 'time') {\n innerHtml = '
'+ absoluteTime + '
' + innerHtml;\n }\n (this.$tooltip.html(innerHtml) as any).place_tt(pos.pageX + 20, pos.pageY);\n };\n\n private _getMultiSeriesPlotHoverInfo(seriesList, pos): { series: any[][], time: any } {\n var value, series, hoverIndex, hoverDistance, pointTime, yaxis;\n // 3 sub-arrays, 1st for hidden series, 2nd for left yaxis, 3rd for right yaxis.\n var results = [[],[],[]];\n\n //now we know the current X (j) position for X and Y values\n var lastValue = 0; //needed for stacked values\n\n var minDistance, minTime;\n\n for (let i = 0; i < seriesList.length; i++) {\n series = seriesList[i];\n\n if (!series.data.length || (this.panel.legend.hideEmpty && series.allIsNull)) {\n // Init value so that it does not brake series sorting\n results[0].push({ hidden: true, value: 0 });\n continue;\n }\n\n if (!series.data.length || (this.panel.legend.hideZero && series.allIsZero)) {\n // Init value so that it does not brake series sorting\n results[0].push({ hidden: true, value: 0 });\n continue;\n }\n\n hoverIndex = this._findHoverIndexFromData(pos.x, series);\n hoverDistance = pos.x - series.data[hoverIndex][0];\n pointTime = series.data[hoverIndex][0];\n\n // Take the closest point before the cursor, or if it does not exist, the closest after\n if (! minDistance\n || (hoverDistance >=0 && (hoverDistance < minDistance || minDistance < 0))\n || (hoverDistance < 0 && hoverDistance > minDistance)\n ) {\n minDistance = hoverDistance;\n minTime = pointTime;\n }\n\n if (series.stack) {\n if (this.panel.tooltip.value_type === 'individual') {\n value = series.data[hoverIndex][1];\n } else if (!series.stack) {\n value = series.data[hoverIndex][1];\n } else {\n lastValue += series.data[hoverIndex][1];\n value = lastValue;\n }\n } else {\n value = series.data[hoverIndex][1];\n }\n\n // Highlighting multiple Points depending on the plot type\n if (series.lines.steps || series.stack) {\n // stacked and steppedLine plots can have series with different length.\n // Stacked series can increase its length on each new stacked serie if null points found,\n // to speed the index search we begin always on the last found hoverIndex.\n hoverIndex = this._findHoverIndexFromDataPoints(pos.x, series, hoverIndex);\n }\n\n // Be sure we have a yaxis so that it does not brake series sorting\n yaxis = 0;\n if (series.yaxis) {\n yaxis = series.yaxis.n;\n }\n\n results[yaxis].push({\n value: value,\n hoverIndex: hoverIndex,\n color: series.color,\n label: series.aliasEscaped,\n time: pointTime,\n distance: hoverDistance,\n index: i\n });\n }\n\n // Contat the 3 sub-arrays\n results = results[0].concat(results[1], results[2]);\n\n // Time of the point closer to pointer\n\n return { series: results, time: minTime };\n };\n}\n\n","\nimport _ from 'lodash';\n\n/**\n * Convert series into array of series values.\n * @param data Array of series\n */\nexport function getSeriesValues(dataList: any[]): number[] {\n const VALUE_INDEX = 0;\n let values = [];\n\n // Count histogam stats\n for (let i = 0; i < dataList.length; i++) {\n let series = dataList[i];\n let datapoints = series.datapoints;\n for (let j = 0; j < datapoints.length; j++) {\n if (datapoints[j][VALUE_INDEX] !== null) {\n values.push(datapoints[j][VALUE_INDEX]);\n }\n }\n }\n\n return values;\n}\n\n/**\n * Convert array of values into timeseries-like histogram:\n * [[val_1, count_1], [val_2, count_2], ..., [val_n, count_n]]\n * @param values\n * @param bucketSize\n */\nexport function convertValuesToHistogram(values: number[], bucketSize: number): any[] {\n let histogram = {};\n\n for (let i = 0; i < values.length; i++) {\n let bound = getBucketBound(values[i], bucketSize);\n if (histogram[bound]) {\n histogram[bound] = histogram[bound] + 1;\n } else {\n histogram[bound] = 1;\n }\n }\n\n let histogam_series = _.map(histogram, (count, bound) => {\n return [Number(bound), count];\n });\n\n // Sort by Y axis values\n return _.sortBy(histogam_series, point => point[0]);\n}\n\nfunction getBucketBound(value: number, bucketSize: number): number {\n return Math.floor(value / bucketSize) * bucketSize;\n}\n","import { SegmentsSet } from './segment_set';\nimport { SegmentArray } from './segment_array';\nimport { Segment, SegmentKey } from './segment';\nimport { Metric } from './metric';\n\nimport _ from 'lodash';\n\nexport type AnomalySermentPair = { anomalyType: AnomalyType, segment: AnomalySegment };\nexport type AnomalySegmentsSearcher = (point: number) => AnomalySermentPair[];\n\nexport type AnomalyKey = string;\n\nexport class AnomalySegment extends Segment {\n constructor(public labeled: boolean, key: SegmentKey, from: number, to: number) {\n super(key, from, to);\n if(!_.isBoolean(labeled)) {\n throw new Error('labeled value is not boolean');\n }\n }\n}\n\nexport class AnomalyType {\n\n private _selected: boolean = false;\n private _deleteMode: boolean = false;\n private _saving: boolean = false;\n private _segmentSet = new SegmentArray
();\n private _status: string;\n private _error: string;\n private _metric: Metric;\n \n private _alertEnabled?: boolean;\n\n constructor(private _panelObject?: any) {\n if(_panelObject === undefined) {\n this._panelObject = {};\n }\n _.defaults(this._panelObject, {\n name: 'anomaly_name', confidence: 0.2, color: 'red', pattern: 'General approach'\n });\n\n //this._metric = new Metric(_panelObject.metric);\n }\n\n get key(): AnomalyKey { return this.name; }\n\n set name(value: string) { this._panelObject.name = value; }\n get name(): string { return this._panelObject.name; }\n\n set pattern(value: string) { this._panelObject.pattern = value; }\n get pattern(): string { return this._panelObject.pattern; }\n\n set confidence(value: number) { this._panelObject.confidence = value; }\n get confidence(): number { return this._panelObject.confidence; }\n\n set color(value: string) { this._panelObject.color = value; }\n get color(): string { return this._panelObject.color; }\n\n get selected(): boolean { return this._selected; }\n set selected(value: boolean) { this._selected = value; }\n\n get deleteMode(): boolean { return this._deleteMode; }\n set deleteMode(value: boolean) { this._deleteMode = value; }\n\n get saving(): boolean { return this._saving; }\n set saving(value: boolean) { this._saving = value; }\n\n get visible(): boolean { \n return (this._panelObject.visible === undefined) ? true : this._panelObject.visible\n }\n set visible(value: boolean) {\n this._panelObject.visible = value;\n }\n\n get metric() { return this._metric; }\n\n addLabeledSegment(segment: Segment): AnomalySegment {\n var asegment = new AnomalySegment(true, segment.key, segment.from, segment.to);\n this._segmentSet.addSegment(asegment);\n return asegment;\n }\n\n removeSegmentsInRange(from: number, to: number): AnomalySegment[] {\n return this._segmentSet.removeInRange(from, to);\n }\n\n get segments(): SegmentsSet { return this._segmentSet; }\n set segments(value: SegmentsSet) {\n this._segmentSet.setSegments(value.getSegments());\n }\n \n get status() { return this._status; }\n set status(value) {\n if(\n value !== 'ready' && \n value !== 'learning' && \n value !== 'pending' && \n value !== 'failed'\n ) {\n throw new Error('Unsupported status value: ' + value);\n }\n this._status = value;\n }\n\n get error() { return this._error; }\n set error(value) { this._error = value; }\n\n get isActiveStatus() {\n return this.status !== 'ready' && this.status !== 'failed';\n }\n\n get panelObject() { return this._panelObject; }\n\n get alertEnabled(): boolean {\n return this._alertEnabled;\n }\n\n set alertEnabled(value) {\n this._alertEnabled = value;\n }\n\n}\n\nexport class AnomalyTypesSet {\n\n private _mapAnomalyKeyIndex: Map;\n private _anomalyTypes: AnomalyType[];\n\n constructor(private _panelObject: any[]) {\n if(_panelObject === undefined) {\n throw new Error('panel object can`t be undefined');\n }\n this._mapAnomalyKeyIndex = new Map();\n this._anomalyTypes = _panelObject.map(p => new AnomalyType(p));\n this._rebuildIndex();\n }\n\n get anomalyTypes() { return this._anomalyTypes; }\n\n addAnomalyType(anomalyType: AnomalyType) {\n this._panelObject.push(anomalyType.panelObject);\n this._mapAnomalyKeyIndex[anomalyType.name] = this._anomalyTypes.length;\n this._anomalyTypes.push(anomalyType);\n }\n\n removeAnomalyType(key: AnomalyKey) {\n var index = this._mapAnomalyKeyIndex[key];\n this._panelObject.splice(index, 1);\n this._anomalyTypes.splice(index, 1);\n this._rebuildIndex();\n }\n\n _rebuildIndex() {\n this._anomalyTypes.forEach((a, i) => {\n this._mapAnomalyKeyIndex[a.key] = i;\n });\n }\n\n byKey(key: AnomalyKey): AnomalyType {\n return this._anomalyTypes[this._mapAnomalyKeyIndex[key]];\n }\n\n byIndex(index: number): AnomalyType {\n return this._anomalyTypes[index];\n }\n}\n","import _ from 'lodash';\nimport md5 from 'md5';\n\n\nexport type TargetHash = string;\n\nexport class Target {\n private _data: any;\n constructor(any) {\n this._data = _.cloneDeep(any);\n this._strip();\n }\n\n private _strip() {\n delete this._data.alias;\n }\n\n getHash(): TargetHash {\n return md5(JSON.stringify(this._data));\n }\n\n getJSON() {\n return this._data;\n }\n}\n\nexport class Metric {\n constructor(private _panelObj: any) {\n if(_panelObj === undefined) {\n throw new Error('_panelObj is undefined');\n }\n }\n get datasource(): string { return this._panelObj.datasource; }\n get targetHashs(): TargetHash[] { return this._panelObj.targetHashs; }\n}\n\nexport class MetricExpanded {\n private _targets: Target[];\n constructor(public datasource: string, targets: any[]) {\n this._targets = targets.map(t => new Target(t));\n }\n\n toJSON(): any {\n return {\n datasource: this.datasource,\n targets: this._targets.map(t => t.getJSON())\n }\n }\n}\n\nexport class MetricMap {\n private _cache: Map = new Map();\n constructor(datasource: string, targets: Target[]) {\n targets.forEach(t => {\n this._cache.set(t.getHash(), t);\n });\n }\n}","export type SegmentKey = number;\n\nexport class Segment {\n constructor(private _key: SegmentKey, public from: number, public to: number) {\n if(isNaN(this._key)) {\n throw new Error('Key can`t be NaN');\n }\n if(isNaN(+from)) {\n throw new Error('from can`t be NaN');\n }\n if(isNaN(+to)) {\n throw new Error('to can`t be NaN');\n }\n }\n \n get key(): SegmentKey { return this._key; }\n set key(value) { this._key = value; }\n\n get middle() { return (this.from + this.to) / 2; }\n\n get length() {\n return Math.max(this.from, this.to) - Math.min(this.from, this.to);\n }\n\n expandDist(allDist: number, portion: number): Segment {\n var p = Math.round(this.middle - this.length / 2);\n var q = Math.round(this.middle + this.length / 2);\n p = Math.min(p, this.from);\n q = Math.max(q, this.to);\n return new Segment(this._key, p, q);\n }\n\n equals(segment: Segment) {\n return this._key === segment._key;\n }\n}\n","import { SegmentsSet } from './segment_set';\nimport { Segment, SegmentKey } from './segment';\n\nimport _ from 'lodash';\n\n\nexport class SegmentArray implements SegmentsSet {\n private _segments: T[];\n private _keyToSegment: Map = new Map();\n\n constructor(private segments?: T[]) {\n this.setSegments(segments);\n }\n\n getSegments(from?: number, to?: number): T[] {\n if(from === undefined) {\n from = -Infinity;\n }\n if(to === undefined) {\n to = Infinity;\n }\n var result = [];\n for(var i = 0; i < this._segments.length; i++) {\n var s = this._segments[i];\n if(from <= s.from && s.to <= to) {\n result.push(s);\n }\n }\n return result;\n }\n\n setSegments(segments: T[]) {\n this._segments = [];\n this._keyToSegment.clear();\n if(segments) {\n segments.forEach(s => {\n this.addSegment(s);\n });\n }\n }\n\n addSegment(segment: T) {\n if(this.has(segment.key)) {\n throw new Error(`Segment with key ${segment.key} exists in set`);\n }\n this._keyToSegment.set(segment.key, segment);\n this._segments.push(segment);\n }\n\n findSegments(point: number): T[] {\n return this._segments.filter(s => (s.from <= point) && (point <= s.to));\n }\n\n removeInRange(from: number, to: number): T[] {\n var deleted = [];\n var newSegments = [];\n for(var i = 0; i < this._segments.length; i++) {\n var s = this._segments[i];\n if(from <= s.from && s.to <= to) {\n this._keyToSegment.delete(s.key);\n deleted.push(s);\n } else {\n newSegments.push(s);\n }\n }\n this._segments = newSegments;\n return deleted;\n }\n\n get length() {\n return this._segments.length;\n }\n\n clear() {\n this._segments = [];\n this._keyToSegment.clear();\n }\n\n has(key: SegmentKey): boolean {\n return this._keyToSegment.has(key);\n }\n\n remove(key: SegmentKey): boolean {\n if(!this.has(key)) {\n return false;\n }\n var index = this._segments.findIndex(s => s.key === key);\n this._segments.splice(index, 1);\n this._keyToSegment.delete(key);\n return true;\n }\n\n updateKey(fromKey: SegmentKey, toKey: SegmentKey) {\n var segment = this._keyToSegment.get(fromKey);\n this._keyToSegment.delete(fromKey);\n segment.key = toKey;\n this._keyToSegment.set(toKey, segment);\n \n }\n\n}","import './series_overrides_ctrl';\nimport './thresholds_form';\n\nimport template from './template';\n\nimport { GraphRenderer } from './graph_renderer';\nimport { GraphLegend } from './graph_legend';\nimport { DataProcessor } from './data_processor';\nimport { Metric, MetricExpanded } from './model/metric';\nimport { DatasourceRequest } from './model/datasource';\nimport { AnomalyKey, AnomalyType } from './model/anomaly';\nimport { AnomalyService } from './services/anomaly_service';\nimport { AnomalyController } from './controllers/anomaly_controller';\n\nimport { axesEditorComponent } from './axes_editor';\n\nimport { MetricsPanelCtrl, alertTab } from 'grafana/app/plugins/sdk';\nimport { BackendSrv } from 'grafana/app/core/services/backend_srv';\nimport { appEvents } from 'grafana/app/core/core'\nimport config from 'grafana/app/core/config';\n\nimport _ from 'lodash';\n\nconst BACKEND_VARIABLE_NAME = 'HASTIC_SERVER_URL';\n\n\nclass GraphCtrl extends MetricsPanelCtrl {\n static template = template;\n\n hiddenSeries: any = {};\n seriesList: any = [];\n dataList: any = [];\n annotations: any = [];\n alertState: any;\n\n _panelPath: any;\n\n annotationsPromise: any;\n dataWarning: any;\n colors: any = [];\n subTabIndex: number;\n processor: DataProcessor;\n\n datasourceRequest: DatasourceRequest;\n patterns: Array = ['General approach', 'Peaks', 'Jumps', 'Drops'];\n anomalyTypes = []; // TODO: remove it later. Only for alert tab\n anomalyController: AnomalyController;\n\n _graphRenderer: GraphRenderer;\n _graphLegend: GraphLegend;\n\n panelDefaults = {\n // datasource name, null = default datasource\n datasource: null,\n // sets client side (flot) or native graphite png renderer (png)\n renderer: 'flot',\n yaxes: [\n {\n label: null,\n show: true,\n logBase: 1,\n min: null,\n max: null,\n format: 'short',\n },\n {\n label: null,\n show: true,\n logBase: 1,\n min: null,\n max: null,\n format: 'short',\n },\n ],\n xaxis: {\n show: true,\n mode: 'time',\n name: null,\n values: [],\n buckets: null,\n },\n // show/hide lines\n lines: true,\n // fill factor\n fill: 1,\n // line width in pixels\n linewidth: 1,\n // show/hide dashed line\n dashes: false,\n // length of a dash\n dashLength: 10,\n // length of space between two dashes\n spaceLength: 10,\n // show hide points\n points: false,\n // point radius in pixels\n pointradius: 5,\n // show hide bars\n bars: false,\n // enable/disable stacking\n stack: false,\n // stack percentage mode\n percentage: false,\n // legend options\n legend: {\n show: true, // disable/enable legend\n values: false, // disable/enable legend values\n min: false,\n max: false,\n current: false,\n total: false,\n avg: false,\n },\n // how null points should be handled\n nullPointMode: 'null',\n // staircase line mode\n steppedLine: false,\n // tooltip options\n tooltip: {\n value_type: 'individual',\n shared: true,\n sort: 0,\n },\n // time overrides\n timeFrom: null,\n timeShift: null,\n // metric queries\n targets: [{}],\n // series color overrides\n aliasColors: {},\n // other style overrides\n seriesOverrides: [],\n thresholds: [],\n anomalyType: '',\n };\n\n /** @ngInject */\n constructor(\n $scope, $injector, private annotationsSrv,\n private keybindingSrv, private backendSrv,\n private popoverSrv, private contextSrv,\n private alertSrv\n) {\n super($scope, $injector);\n\n _.defaults(this.panel, this.panelDefaults);\n _.defaults(this.panel.tooltip, this.panelDefaults.tooltip);\n _.defaults(this.panel.legend, this.panelDefaults.legend);\n _.defaults(this.panel.xaxis, this.panelDefaults.xaxis);\n\n this.processor = new DataProcessor(this.panel);\n\n \n var anomalyService = new AnomalyService(this.backendURL, backendSrv as BackendSrv);\n \n this.runBackendConnectivityCheck();\n\n this.anomalyController = new AnomalyController(this.panel, anomalyService, this.events);\n this.anomalyTypes = this.panel.anomalyTypes;\n keybindingSrv.bind('d', this.onDKey.bind(this));\n\n this.events.on('render', this.onRender.bind(this));\n this.events.on('data-received', this.onDataReceived.bind(this));\n this.events.on('data-error', this.onDataError.bind(this));\n this.events.on('data-snapshot-load', this.onDataSnapshotLoad.bind(this));\n this.events.on('init-edit-mode', this.onInitEditMode.bind(this));\n this.events.on('init-panel-actions', this.onInitPanelActions.bind(this));\n this.events.on('anomaly-type-alert-change', () => {\n this.$scope.$digest()\n });\n this.events.on('anomaly-type-status-change', async (anomalyType: AnomalyType) => {\n if(anomalyType === undefined) {\n throw new Error('anomalyType is undefined');\n }\n if(anomalyType.status === 'ready') {\n await this.anomalyController.fetchSegments(anomalyType, +this.range.from, +this.range.to);\n }\n this.render(this.seriesList);\n this.$scope.$digest();\n });\n appEvents.on('ds-request-response', data => {\n let requestConfig = data.config;\n this.datasourceRequest = {\n url: requestConfig.url,\n type: requestConfig.inspect.type,\n method: requestConfig.method,\n data: requestConfig.data,\n params: requestConfig.params\n };\n });\n\n this.anomalyController.fetchAnomalyTypesStatuses();\n\n }\n\n get backendURL(): string {\n if(this.templateSrv.index[BACKEND_VARIABLE_NAME] === undefined) {\n return undefined;\n }\n return this.templateSrv.index[BACKEND_VARIABLE_NAME].current.value;\n }\n\n async runBackendConnectivityCheck() {\n if(this.backendURL === '' || this.backendURL === undefined) {\n this.alertSrv.set(\n `Dashboard variable $${BACKEND_VARIABLE_NAME} is missing`, \n `Please set $${BACKEND_VARIABLE_NAME}`, \n 'warning', 4000\n );\n return;\n }\n\n var as = new AnomalyService(this.backendURL, this.backendSrv);\n var isOK = await as.isBackendOk();\n if(!isOK) {\n this.alertSrv.set(\n 'Can`t connect to Hastic server', `Hastic server: \"${this.backendURL}\"`, 'warning', 4000\n );\n } \n }\n\n link(scope, elem, attrs, ctrl) {\n var $graphElem = $(elem[0]).find('#graphPanel');\n var $legendElem = $(elem[0]).find('#graphLegend');\n this._graphRenderer = new GraphRenderer(\n $graphElem, this.timeSrv, this.popoverSrv, this.contextSrv,this.$scope\n );\n this._graphLegend = new GraphLegend($legendElem, this.popoverSrv, this.$scope);\n }\n\n onInitEditMode() {\n var partialPath = this.panelPath + 'partials';\n this.addEditorTab('Analytics', `${partialPath}/tab_analytics.html`, 2);\n this.addEditorTab('Axes', axesEditorComponent, 3);\n this.addEditorTab('Legend', `${partialPath}/tab_legend.html`, 4);\n this.addEditorTab('Display', `${partialPath}/tab_display.html`, 5);\n\n if (config.alertingEnabled) {\n this.addEditorTab('Alert', alertTab, 6);\n }\n\n this.subTabIndex = 0;\n }\n\n onInitPanelActions(actions) {\n actions.push({ text: 'Export CSV', click: 'ctrl.exportCsv()' });\n actions.push({ text: 'Toggle legend', click: 'ctrl.toggleLegend()' });\n }\n\n issueQueries(datasource) {\n this.annotationsPromise = this.annotationsSrv.getAnnotations({\n dashboard: this.dashboard,\n panel: this.panel,\n range: this.range,\n });\n return super.issueQueries(datasource);\n }\n\n zoomOut(evt) {\n this.publishAppEvent('zoom-out', 2);\n }\n\n onDataSnapshotLoad(snapshotData) {\n this.annotationsPromise = this.annotationsSrv.getAnnotations({\n dashboard: this.dashboard,\n panel: this.panel,\n range: this.range,\n });\n this.onDataReceived(snapshotData);\n }\n\n onDataError(err) {\n this.seriesList = [];\n this.annotations = [];\n this.render([]);\n }\n\n async onDataReceived(dataList) {\n\n this.dataList = dataList;\n this.seriesList = this.processor.getSeriesList({\n dataList: dataList,\n range: this.range,\n });\n\n //this.onPredictionReceived(this.seriesList);\n\n this.dataWarning = null;\n const hasSomePoint = this.seriesList.some(s => s.datapoints.length > 0);\n\n if (!hasSomePoint) {\n this.dataWarning = {\n title: 'No data points',\n tip: 'No datapoints returned from data query',\n };\n } else {\n for (let series of this.seriesList) {\n if (series.isOutsideRange) {\n this.dataWarning = {\n title: 'Data points outside time range',\n tip: 'Can be caused by timezone mismatch or missing time filter in query',\n };\n break;\n }\n }\n }\n\n var loadTasks = [\n this.annotationsPromise,\n this.anomalyController.fetchAnomalyTypesSegments(+this.range.from, +this.range.to)\n ];\n\n var results = await Promise.all(loadTasks);\n this.loading = false;\n this.alertState = results[0].alertState;\n this.annotations = results[0].annotations;\n this.render(this.seriesList);\n\n }\n\n onPredictionReceived(spanList) {\n var predictions = [];\n for (var span of spanList) {\n var predictionLow = {\n target: '',\n color: '',\n datapoints: []\n };\n var predictionHigh = {\n target: '',\n color: '',\n datapoints: []\n };\n\n for (var datapoint of span.datapoints) {\n predictionHigh.datapoints.push([datapoint[0] + 2, datapoint[1]]);\n predictionLow.datapoints.push([datapoint[0] - 2, datapoint[1]]);\n }\n\n predictionHigh.target = `${span.label} high`;\n predictionLow.target = `${span.label} low`;\n predictionHigh.color = span.color;\n predictionLow.color = span.color;\n predictions.push(predictionHigh, predictionLow);\n }\n var predictionSeries = this.processor.getSeriesList({\n dataList: predictions,\n range: this.range\n });\n for (var serie of predictionSeries) {\n serie.prediction = true;\n this.seriesList.push(serie);\n }\n }\n\n onRender(data) {\n if (!this.seriesList) {\n return;\n }\n\n for (let series of this.seriesList) {\n if (series.prediction) {\n var overrideItem = _.find(\n this.panel.seriesOverrides,\n el => el.alias === series.alias\n )\n if (overrideItem !== undefined) {\n this.addSeriesOverride({\n alias: series.alias,\n linewidth: 0,\n legend: false,\n // if pointradius === 0 -> point still shows, that's why pointradius === -1\n pointradius: -1,\n fill: 3\n });\n }\n }\n series.applySeriesOverrides(this.panel.seriesOverrides);\n\n if (series.unit) {\n this.panel.yaxes[series.yaxis - 1].format = series.unit;\n }\n }\n\n if(!this.anomalyController.graphLocked) {\n this._graphLegend.render();\n this._graphRenderer.render(data);\n }\n }\n\n changeSeriesColor(series, color) {\n series.color = color;\n this.panel.aliasColors[series.alias] = series.color;\n this.render();\n }\n\n toggleSeries(serie, event) {\n if (event.ctrlKey || event.metaKey || event.shiftKey) {\n if (this.hiddenSeries[serie.alias]) {\n delete this.hiddenSeries[serie.alias];\n } else {\n this.hiddenSeries[serie.alias] = true;\n }\n } else {\n this.toggleSeriesExclusiveMode(serie);\n }\n this.render();\n }\n\n toggleSeriesExclusiveMode(serie) {\n var hidden = this.hiddenSeries;\n\n if (hidden[serie.alias]) {\n delete hidden[serie.alias];\n }\n\n // check if every other series is hidden\n var alreadyExclusive = _.every(this.seriesList, value => {\n if (value.alias === serie.alias) {\n return true;\n }\n\n return hidden[value.alias];\n });\n\n if (alreadyExclusive) {\n // remove all hidden series\n _.each(this.seriesList, value => {\n delete this.hiddenSeries[value.alias];\n });\n } else {\n // hide all but this serie\n _.each(this.seriesList, value => {\n if (value.alias === serie.alias) {\n return;\n }\n\n this.hiddenSeries[value.alias] = true;\n });\n }\n }\n\n toggleAxis(info) {\n var override = _.find(this.panel.seriesOverrides, { alias: info.alias });\n if (!override) {\n override = { alias: info.alias };\n this.panel.seriesOverrides.push(override);\n }\n info.yaxis = override.yaxis = info.yaxis === 2 ? 1 : 2;\n this.render();\n }\n\n addSeriesOverride(override) {\n this.panel.seriesOverrides.push(override || {});\n }\n\n removeSeriesOverride(override) {\n this.panel.seriesOverrides = _.without(this.panel.seriesOverrides, override);\n this.render();\n }\n\n toggleLegend() {\n this.panel.legend.show = !this.panel.legend.show;\n this.refresh();\n }\n\n legendValuesOptionChanged() {\n var legend = this.panel.legend;\n legend.values = legend.min || legend.max || legend.avg || legend.current || legend.total;\n this.render();\n }\n\n exportCsv() {\n var scope = this.$scope.$new(true);\n scope.seriesList = this.seriesList;\n this.publishAppEvent('show-modal', {\n templateHtml: '',\n scope,\n modalClass: 'modal--narrow',\n });\n }\n\n getAnnotationsByTag(tag) {\n var res = [];\n for (var annotation of this.annotations) {\n if (annotation.tags.indexOf(tag) >= 0) {\n res.push(annotation);\n }\n }\n return res;\n }\n\n get annotationTags() {\n var res = [];\n for (var annotation of this.annotations) {\n for (var tag of annotation.tags) {\n if (res.indexOf(tag) < 0) {\n res.push(tag);\n }\n }\n }\n return res;\n }\n\n get panelPath() {\n if (this._panelPath === undefined) {\n this._panelPath = '/public/plugins/' + this.pluginId + '/';\n }\n return this._panelPath;\n }\n\n createNewAnomalyType() {\n this.anomalyController.createAnomalyType();\n }\n\n async saveAnomalyType() {\n this.refresh();\n await this.anomalyController.saveNewAnomalyType(\n new MetricExpanded(this.panel.datasource, this.panel.targets),\n this.datasourceRequest,\n this.panel.id\n );\n this.$scope.$digest();\n this.render(this.seriesList);\n }\n\n onAnomalyColorChange(key: AnomalyKey, value) {\n this.anomalyController.onAnomalyColorChange(key, value);\n this.render();\n }\n\n onAnomalyRemove(key) {\n this.anomalyController.removeAnomalyType(key as string);\n this.render();\n }\n\n onAnomalyCancelLabeling(key) {\n this.$scope.$root.appEvent('confirm-modal', {\n title: 'Clear anomaly labeling',\n text2: 'Your changes will be lost.',\n yesText: 'Clear',\n icon: 'fa-warning',\n altActionText: 'Save',\n onAltAction: () => {\n this.onToggleAnomalyTypeLabelingMode(key);\n },\n onConfirm: () => {\n this.anomalyController.undoLabeling();\n this.render();\n },\n });\n }\n\n async onToggleAnomalyTypeLabelingMode(key) {\n await this.anomalyController.toggleAnomalyTypeLabelingMode(key as AnomalyKey);\n this.$scope.$digest();\n this.render();\n }\n\n onDKey() {\n if(!this.anomalyController.labelingMode) {\n return;\n }\n this.anomalyController.toggleDeleteMode();\n }\n\n onAnomalyAlertChange(anomalyType: AnomalyType) {\n this.anomalyController.toggleAnomalyTypeAlertEnabled(anomalyType);\n }\n\n onAnomalyToggleVisibility(key: AnomalyKey) {\n this.anomalyController.toggleAnomalyVisibility(key);\n this.render();\n }\n}\n\nexport { GraphCtrl, GraphCtrl as PanelCtrl };\n","import _ from 'lodash';\nimport angular from 'angular';\n\nexport class SeriesOverridesCtrl {\n /** @ngInject */\n constructor($scope, $element, popoverSrv) {\n $scope.overrideMenu = [];\n $scope.currentOverrides = [];\n $scope.override = $scope.override || {};\n\n $scope.addOverrideOption = function(name, propertyName, values) {\n var option = {\n text: name,\n propertyName: propertyName,\n index: $scope.overrideMenu.lenght,\n values: values,\n submenu: _.map(values, function(value) {\n return { text: String(value), value: value };\n }),\n };\n\n $scope.overrideMenu.push(option);\n };\n\n $scope.setOverride = function(item, subItem) {\n // handle color overrides\n if (item.propertyName === 'color') {\n $scope.openColorSelector($scope.override['color']);\n return;\n }\n\n $scope.override[item.propertyName] = subItem.value;\n\n // automatically disable lines for this series and the fill bellow to series\n // can be removed by the user if they still want lines\n if (item.propertyName === 'fillBelowTo') {\n $scope.override['lines'] = false;\n $scope.ctrl.addSeriesOverride({ alias: subItem.value, lines: false });\n }\n\n $scope.updateCurrentOverrides();\n $scope.ctrl.render();\n };\n\n $scope.colorSelected = function(color) {\n $scope.override['color'] = color;\n $scope.updateCurrentOverrides();\n $scope.ctrl.render();\n };\n\n $scope.openColorSelector = function(color) {\n var fakeSeries = { color: color };\n popoverSrv.show({\n element: $element.find('.dropdown')[0],\n position: 'top center',\n openOn: 'click',\n template: '',\n model: {\n autoClose: true,\n colorSelected: $scope.colorSelected,\n series: fakeSeries,\n },\n onClose: function() {\n $scope.ctrl.render();\n },\n });\n };\n\n $scope.removeOverride = function(option) {\n delete $scope.override[option.propertyName];\n $scope.updateCurrentOverrides();\n $scope.ctrl.refresh();\n };\n\n $scope.getSeriesNames = function() {\n return _.map($scope.ctrl.seriesList, function(series: any) {\n return series.alias;\n });\n };\n\n $scope.updateCurrentOverrides = function() {\n $scope.currentOverrides = [];\n _.each($scope.overrideMenu, function(option) {\n var value = $scope.override[option.propertyName];\n if (_.isUndefined(value)) {\n return;\n }\n $scope.currentOverrides.push({\n name: option.text,\n propertyName: option.propertyName,\n value: String(value),\n });\n });\n };\n\n $scope.addOverrideOption('Bars', 'bars', [true, false]);\n $scope.addOverrideOption('Lines', 'lines', [true, false]);\n $scope.addOverrideOption('Line fill', 'fill', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);\n $scope.addOverrideOption('Line width', 'linewidth', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);\n $scope.addOverrideOption('Null point mode', 'nullPointMode', ['connected', 'null', 'null as zero']);\n $scope.addOverrideOption('Fill below to', 'fillBelowTo', $scope.getSeriesNames());\n $scope.addOverrideOption('Staircase line', 'steppedLine', [true, false]);\n $scope.addOverrideOption('Dashes', 'dashes', [true, false]);\n $scope.addOverrideOption('Dash Length', 'dashLength', [\n 1,\n 2,\n 3,\n 4,\n 5,\n 6,\n 7,\n 8,\n 9,\n 10,\n 11,\n 12,\n 13,\n 14,\n 15,\n 16,\n 17,\n 18,\n 19,\n 20,\n ]);\n $scope.addOverrideOption('Dash Space', 'spaceLength', [\n 1,\n 2,\n 3,\n 4,\n 5,\n 6,\n 7,\n 8,\n 9,\n 10,\n 11,\n 12,\n 13,\n 14,\n 15,\n 16,\n 17,\n 18,\n 19,\n 20,\n ]);\n $scope.addOverrideOption('Points', 'points', [true, false]);\n $scope.addOverrideOption('Points Radius', 'pointradius', [1, 2, 3, 4, 5]);\n $scope.addOverrideOption('Stack', 'stack', [true, false, 'A', 'B', 'C', 'D']);\n $scope.addOverrideOption('Color', 'color', ['change']);\n $scope.addOverrideOption('Y-axis', 'yaxis', [1, 2]);\n $scope.addOverrideOption('Z-index', 'zindex', [-3, -2, -1, 0, 1, 2, 3]);\n $scope.addOverrideOption('Transform', 'transform', ['negative-Y']);\n $scope.addOverrideOption('Legend', 'legend', [true, false]);\n $scope.updateCurrentOverrides();\n }\n}\n\nangular.module('grafana.controllers').controller('SeriesOverridesCtrl', SeriesOverridesCtrl);\n","import { Segment, SegmentKey } from '../model/segment';\nimport { MetricExpanded } from '../model/metric';\nimport { DatasourceRequest } from '../model/datasource';\nimport { SegmentsSet } from '../model/segment_set';\nimport { AnomalyKey, AnomalyType, AnomalySegment } from '../model/anomaly';\n\nimport { BackendSrv } from 'grafana/app/core/services/backend_srv';\n\n\n\nexport class AnomalyService {\n constructor(private _backendURL: string, private _backendSrv: BackendSrv) {\n }\n\n async postNewAnomalyType(metric: MetricExpanded, datasourceRequest: DatasourceRequest, newAnomalyType: AnomalyType, panelId: number) {\n return this._backendSrv.post(\n this._backendURL + '/anomalies', \n {\n name: newAnomalyType.name,\n metric: metric.toJSON(),\n panelUrl: window.location.origin + window.location.pathname + `?panelId=${panelId}&fullscreen`,\n datasource: datasourceRequest,\n pattern: newAnomalyType.pattern\n }\n )\n };\n\n async isBackendOk(): Promise {\n try {\n var data = await this._backendSrv.get(this._backendURL);\n return true;\n } catch(e) {\n return false;\n }\n }\n \n async updateSegments(\n key: AnomalyKey, addedSegments: SegmentsSet, removedSegments: SegmentsSet\n ): Promise {\n\n const getJSONs = (segs: SegmentsSet) => segs.getSegments().map(segment => ({\n \"start\": segment.from,\n \"finish\": segment.to\n }));\n\n var payload = {\n name: key,\n added_segments: getJSONs(addedSegments),\n removed_segments: removedSegments.getSegments().map(s => s.key)\n }\n\n var data = await this._backendSrv.patch(this._backendURL + '/segments', payload);\n if(data.added_ids === undefined) {\n throw new Error('Server didn`t send added_ids');\n }\n\n return data.added_ids as SegmentKey[];\n }\n\n async getSegments(key: AnomalyKey, from?: number, to?: number): Promise {\n var payload: any = { anomaly_id: key };\n if(from !== undefined) {\n payload['from'] = from;\n }\n if(to !== undefined) {\n payload['to'] = to;\n }\n var data = await this._backendSrv.get(\n this._backendURL + '/segments',\n payload\n );\n if(data.segments === undefined) {\n throw new Error('Server didn`t return segments array');\n }\n var segments = data.segments as { id: number, start: number, finish: number, labeled: boolean }[];\n return segments.map(s => new AnomalySegment(s.labeled, s.id, s.start, s.finish));\n }\n\n async * getAnomalyTypeStatusGenerator(key: AnomalyKey, duration: number) {\n let statusCheck = async () => {\n var data = await this._backendSrv.get(\n this._backendURL + '/anomalies/status', { name: key }\n );\n return data;\n }\n\n let timeout = async () => new Promise(\n resolve => setTimeout(resolve, duration)\n );\n\n while(true) {\n yield await statusCheck();\n await timeout();\n }\n \n }\n\n async getAlertEnabled(key: AnomalyKey): Promise {\n var data = await this._backendSrv.get(\n this._backendURL + '/alerts', { anomaly_id: key }\n );\n return data.enable as boolean;\n\n }\n\n async setAlertEnabled(key: AnomalyKey, value: boolean): Promise {\n return this._backendSrv.post(\n this._backendURL + '/alerts', { anomaly_id: key, enable: value }\n );\n }\n\n}\n","var template = `\n\n`;\n\nexport default template;\n","import './vendor/flot/jquery.flot';\nimport * as $ from 'jquery';\nimport _ from 'lodash';\n\nexport class ThresholdManager {\n plot: any;\n placeholder: any;\n height: any;\n thresholds: any;\n needsCleanup: boolean;\n hasSecondYAxis: any;\n\n constructor(private panelCtrl) {}\n\n getHandleHtml(handleIndex, model, valueStr) {\n var stateClass = model.colorMode;\n if (model.colorMode === 'custom') {\n stateClass = 'critical';\n }\n\n return `\n \n
\n
\n
\n \n ${valueStr}\n
\n
`;\n }\n\n initDragging(evt) {\n var handleElem = $(evt.currentTarget).parents('.alert-handle-wrapper');\n var handleIndex = $(evt.currentTarget).data('handleIndex');\n\n var lastY = null;\n var posTop;\n var plot = this.plot;\n var panelCtrl = this.panelCtrl;\n var model = this.thresholds[handleIndex];\n\n function dragging(evt) {\n if (lastY === null) {\n lastY = evt.clientY;\n } else {\n var diff = evt.clientY - lastY;\n posTop = posTop + diff;\n lastY = evt.clientY;\n handleElem.css({ top: posTop + diff });\n }\n }\n\n function stopped() {\n // calculate graph level\n var graphValue = plot.c2p({ left: 0, top: posTop }).y;\n graphValue = parseInt(graphValue.toFixed(0));\n model.value = graphValue;\n\n handleElem.off('mousemove', dragging);\n handleElem.off('mouseup', dragging);\n handleElem.off('mouseleave', dragging);\n\n // trigger digest and render\n panelCtrl.$scope.$apply(function() {\n panelCtrl.render();\n panelCtrl.events.emit('threshold-changed', {\n threshold: model,\n handleIndex: handleIndex,\n });\n });\n }\n\n lastY = null;\n posTop = handleElem.position().top;\n\n handleElem.on('mousemove', dragging);\n handleElem.on('mouseup', stopped);\n handleElem.on('mouseleave', stopped);\n }\n\n cleanUp() {\n this.placeholder.find('.alert-handle-wrapper').remove();\n this.needsCleanup = false;\n }\n\n renderHandle(handleIndex, defaultHandleTopPos) {\n var model = this.thresholds[handleIndex];\n var value = model.value;\n var valueStr = value;\n var handleTopPos = 0;\n\n // handle no value\n if (!_.isNumber(value)) {\n valueStr = '';\n handleTopPos = defaultHandleTopPos;\n } else {\n var valueCanvasPos = this.plot.p2c({ x: 0, y: value });\n handleTopPos = Math.round(Math.min(Math.max(valueCanvasPos.top, 0), this.height) - 6);\n }\n\n var handleElem = $(this.getHandleHtml(handleIndex, model, valueStr));\n this.placeholder.append(handleElem);\n\n handleElem.toggleClass('alert-handle-wrapper--no-value', valueStr === '');\n handleElem.css({ top: handleTopPos });\n }\n\n shouldDrawHandles() {\n return !this.hasSecondYAxis && this.panelCtrl.editingThresholds && this.panelCtrl.panel.thresholds.length > 0;\n }\n\n prepare(elem, data) {\n this.hasSecondYAxis = false;\n for (var i = 0; i < data.length; i++) {\n if (data[i].yaxis > 1) {\n this.hasSecondYAxis = true;\n break;\n }\n }\n\n if (this.shouldDrawHandles()) {\n var thresholdMargin = this.panelCtrl.panel.thresholds.length > 1 ? '220px' : '110px';\n elem.css('margin-right', thresholdMargin);\n } else if (this.needsCleanup) {\n elem.css('margin-right', '0');\n }\n }\n\n draw(plot) {\n this.thresholds = this.panelCtrl.panel.thresholds;\n this.plot = plot;\n this.placeholder = plot.getPlaceholder();\n\n if (this.needsCleanup) {\n this.cleanUp();\n }\n\n if (!this.shouldDrawHandles()) {\n return;\n }\n\n this.height = plot.height();\n\n if (this.thresholds.length > 0) {\n this.renderHandle(0, 10);\n }\n if (this.thresholds.length > 1) {\n this.renderHandle(1, this.height - 30);\n }\n\n this.placeholder.off('mousedown', '.alert-handle');\n this.placeholder.on('mousedown', '.alert-handle', this.initDragging.bind(this));\n this.needsCleanup = true;\n }\n\n addFlotOptions(options, panel) {\n if (!panel.thresholds || panel.thresholds.length === 0) {\n return;\n }\n\n var gtLimit = Infinity;\n var ltLimit = -Infinity;\n var i, threshold, other;\n\n for (i = 0; i < panel.thresholds.length; i++) {\n threshold = panel.thresholds[i];\n if (!_.isNumber(threshold.value)) {\n continue;\n }\n\n var limit;\n switch (threshold.op) {\n case 'gt': {\n limit = gtLimit;\n // if next threshold is less then op and greater value, then use that as limit\n if (panel.thresholds.length > i + 1) {\n other = panel.thresholds[i + 1];\n if (other.value > threshold.value) {\n limit = other.value;\n ltLimit = limit;\n }\n }\n break;\n }\n case 'lt': {\n limit = ltLimit;\n // if next threshold is less then op and greater value, then use that as limit\n if (panel.thresholds.length > i + 1) {\n other = panel.thresholds[i + 1];\n if (other.value < threshold.value) {\n limit = other.value;\n gtLimit = limit;\n }\n }\n break;\n }\n }\n\n var fillColor, lineColor;\n switch (threshold.colorMode) {\n case 'critical': {\n fillColor = 'rgba(234, 112, 112, 0.12)';\n lineColor = 'rgba(237, 46, 24, 0.60)';\n break;\n }\n case 'warning': {\n fillColor = 'rgba(235, 138, 14, 0.12)';\n lineColor = 'rgba(247, 149, 32, 0.60)';\n break;\n }\n case 'ok': {\n fillColor = 'rgba(11, 237, 50, 0.090)';\n lineColor = 'rgba(6,163,69, 0.60)';\n break;\n }\n case 'custom': {\n fillColor = threshold.fillColor;\n lineColor = threshold.lineColor;\n break;\n }\n }\n\n // fill\n if (threshold.fill) {\n options.grid.markings.push({\n yaxis: { from: threshold.value, to: limit },\n color: fillColor,\n });\n }\n if (threshold.line) {\n options.grid.markings.push({\n yaxis: { from: threshold.value, to: threshold.value },\n color: lineColor,\n });\n }\n }\n }\n}\n","\nimport coreModule from 'grafana/app/core/core_module';\n\nexport class ThresholdFormCtrl {\n panelCtrl: any;\n panel: any;\n disabled: boolean;\n\n /** @ngInject */\n constructor($scope) {\n this.panel = this.panelCtrl.panel;\n\n if (this.panel.alert) {\n this.disabled = true;\n }\n\n var unbindDestroy = $scope.$on('$destroy', () => {\n this.panelCtrl.editingThresholds = false;\n this.panelCtrl.render();\n unbindDestroy();\n });\n\n this.panelCtrl.editingThresholds = true;\n }\n\n addThreshold() {\n this.panel.thresholds.push({\n value: undefined,\n colorMode: 'critical',\n op: 'gt',\n fill: true,\n line: true,\n });\n this.panelCtrl.render();\n }\n\n removeThreshold(index) {\n this.panel.thresholds.splice(index, 1);\n this.panelCtrl.render();\n }\n\n render() {\n this.panelCtrl.render();\n }\n\n onFillColorChange(index) {\n return newColor => {\n this.panel.thresholds[index].fillColor = newColor;\n this.render();\n };\n }\n\n onLineColorChange(index) {\n return newColor => {\n this.panel.thresholds[index].lineColor = newColor;\n this.render();\n };\n }\n}\n\nvar template = `\n\n`;\n\ncoreModule.directive('hasticGraphThresholdForm', function() {\n return {\n restrict: 'E',\n template: template,\n controller: ThresholdFormCtrl,\n bindToController: true,\n controllerAs: 'ctrl',\n scope: {\n panelCtrl: '=',\n },\n };\n});\n","/* Flot plugin for showing crosshairs when the mouse hovers over the plot.\n\nCopyright (c) 2007-2014 IOLA and Ole Laursen.\nLicensed under the MIT license.\n\nThe plugin supports these options:\n\n\tcrosshair: {\n\t\tmode: null or \"x\" or \"y\" or \"xy\"\n\t\tcolor: color\n\t\tlineWidth: number\n\t}\n\nSet the mode to one of \"x\", \"y\" or \"xy\". The \"x\" mode enables a vertical\ncrosshair that lets you trace the values on the x axis, \"y\" enables a\nhorizontal crosshair and \"xy\" enables them both. \"color\" is the color of the\ncrosshair (default is \"rgba(170, 0, 0, 0.80)\"), \"lineWidth\" is the width of\nthe drawn lines (default is 1).\n\nThe plugin also adds four public methods:\n\n - setCrosshair( pos )\n\n Set the position of the crosshair. Note that this is cleared if the user\n moves the mouse. \"pos\" is in coordinates of the plot and should be on the\n form { x: xpos, y: ypos } (you can use x2/x3/... if you're using multiple\n axes), which is coincidentally the same format as what you get from a\n \"plothover\" event. If \"pos\" is null, the crosshair is cleared.\n\n - clearCrosshair()\n\n Clear the crosshair.\n\n - lockCrosshair(pos)\n\n Cause the crosshair to lock to the current location, no longer updating if\n the user moves the mouse. Optionally supply a position (passed on to\n setCrosshair()) to move it to.\n\n Example usage:\n\n\tvar myFlot = $.plot( $(\"#graph\"), ..., { crosshair: { mode: \"x\" } } };\n\t$(\"#graph\").bind( \"plothover\", function ( evt, position, item ) {\n\t\tif ( item ) {\n\t\t\t// Lock the crosshair to the data point being hovered\n\t\t\tmyFlot.lockCrosshair({\n\t\t\t\tx: item.datapoint[ 0 ],\n\t\t\t\ty: item.datapoint[ 1 ]\n\t\t\t});\n\t\t} else {\n\t\t\t// Return normal crosshair operation\n\t\t\tmyFlot.unlockCrosshair();\n\t\t}\n\t});\n\n - unlockCrosshair()\n\n Free the crosshair to move again after locking it.\n*/\n\n(function ($) {\n var options = {\n crosshair: {\n mode: null, // one of null, \"x\", \"y\" or \"xy\",\n color: \"rgba(170, 0, 0, 0.80)\",\n lineWidth: 1\n }\n };\n \n function init(plot) {\n // position of crosshair in pixels\n var crosshair = { x: -1, y: -1, locked: false };\n\n plot.setCrosshair = function setCrosshair(pos) {\n if (!pos)\n crosshair.x = -1;\n else {\n var o = plot.p2c(pos);\n crosshair.x = Math.max(0, Math.min(o.left, plot.width()));\n crosshair.y = Math.max(0, Math.min(o.top, plot.height()));\n }\n \n plot.triggerRedrawOverlay();\n };\n \n plot.clearCrosshair = plot.setCrosshair; // passes null for pos\n \n plot.lockCrosshair = function lockCrosshair(pos) {\n if (pos)\n plot.setCrosshair(pos);\n crosshair.locked = true;\n };\n\n plot.unlockCrosshair = function unlockCrosshair() {\n crosshair.locked = false;\n };\n\n function onMouseOut(e) {\n if (crosshair.locked)\n return;\n\n if (crosshair.x != -1) {\n crosshair.x = -1;\n plot.triggerRedrawOverlay();\n }\n }\n\n function onMouseMove(e) {\n if (crosshair.locked)\n return;\n \n if (plot.getSelection && plot.getSelection()) {\n crosshair.x = -1; // hide the crosshair while selecting\n return;\n }\n \n var offset = plot.offset();\n crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width()));\n crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height()));\n plot.triggerRedrawOverlay();\n }\n \n plot.hooks.bindEvents.push(function (plot, eventHolder) {\n if (!plot.getOptions().crosshair.mode)\n return;\n\n eventHolder.mouseout(onMouseOut);\n eventHolder.mousemove(onMouseMove);\n });\n\n plot.hooks.drawOverlay.push(function (plot, ctx) {\n var c = plot.getOptions().crosshair;\n if (!c.mode)\n return;\n\n var plotOffset = plot.getPlotOffset();\n \n ctx.save();\n ctx.translate(plotOffset.left, plotOffset.top);\n\n if (crosshair.x != -1) {\n var adj = plot.getOptions().crosshair.lineWidth % 2 ? 0.5 : 0;\n\n ctx.strokeStyle = c.color;\n ctx.lineWidth = c.lineWidth;\n ctx.lineJoin = \"round\";\n\n ctx.beginPath();\n if (c.mode.indexOf(\"x\") != -1) {\n var drawX = Math.floor(crosshair.x) + adj;\n ctx.moveTo(drawX, 0);\n ctx.lineTo(drawX, plot.height());\n }\n if (c.mode.indexOf(\"y\") != -1) {\n var drawY = Math.floor(crosshair.y) + adj;\n ctx.moveTo(0, drawY);\n ctx.lineTo(plot.width(), drawY);\n }\n ctx.stroke();\n }\n ctx.restore();\n });\n\n plot.hooks.shutdown.push(function (plot, eventHolder) {\n eventHolder.unbind(\"mouseout\", onMouseOut);\n eventHolder.unbind(\"mousemove\", onMouseMove);\n });\n }\n \n $.plot.plugins.push({\n init: init,\n options: options,\n name: 'crosshair',\n version: '1.0'\n });\n})(jQuery);\n","/*\n * jQuery.flot.dashes\n *\n * options = {\n * series: {\n * dashes: {\n *\n * // show\n * // default: false\n * // Whether to show dashes for the series.\n * show: ,\n *\n * // lineWidth\n * // default: 2\n * // The width of the dashed line in pixels.\n * lineWidth: ,\n *\n * // dashLength\n * // default: 10\n * // Controls the length of the individual dashes and the amount of\n * // space between them.\n * // If this is a number, the dashes and spaces will have that length.\n * // If this is an array, it is read as [ dashLength, spaceLength ]\n * dashLength: or \n * }\n * }\n * }\n */\n(function($){\n\n function init(plot) {\n\n plot.hooks.processDatapoints.push(function(plot, series, datapoints) {\n\n if (!series.dashes.show) return;\n\n plot.hooks.draw.push(function(plot, ctx) {\n\n var plotOffset = plot.getPlotOffset(),\n axisx = series.xaxis,\n axisy = series.yaxis;\n\n function plotDashes(xoffset, yoffset) {\n\n var points = datapoints.points,\n ps = datapoints.pointsize,\n prevx = null,\n prevy = null,\n dashRemainder = 0,\n dashOn = true,\n dashOnLength,\n dashOffLength;\n\n if (series.dashes.dashLength[0]) {\n dashOnLength = series.dashes.dashLength[0];\n if (series.dashes.dashLength[1]) {\n dashOffLength = series.dashes.dashLength[1];\n } else {\n dashOffLength = dashOnLength;\n }\n } else {\n dashOffLength = dashOnLength = series.dashes.dashLength;\n }\n\n ctx.beginPath();\n\n for (var i = ps; i < points.length; i += ps) {\n\n var x1 = points[i - ps],\n y1 = points[i - ps + 1],\n x2 = points[i],\n y2 = points[i + 1];\n\n if (x1 == null || x2 == null) continue;\n\n // clip with ymin\n if (y1 <= y2 && y1 < axisy.min) {\n if (y2 < axisy.min) continue; // line segment is outside\n // compute new intersection point\n x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;\n y1 = axisy.min;\n } else if (y2 <= y1 && y2 < axisy.min) {\n if (y1 < axisy.min) continue;\n x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;\n y2 = axisy.min;\n }\n\n // clip with ymax\n if (y1 >= y2 && y1 > axisy.max) {\n if (y2 > axisy.max) continue;\n x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;\n y1 = axisy.max;\n } else if (y2 >= y1 && y2 > axisy.max) {\n if (y1 > axisy.max) continue;\n x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;\n y2 = axisy.max;\n }\n\n // clip with xmin\n if (x1 <= x2 && x1 < axisx.min) {\n if (x2 < axisx.min) continue;\n y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;\n x1 = axisx.min;\n } else if (x2 <= x1 && x2 < axisx.min) {\n if (x1 < axisx.min) continue;\n y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;\n x2 = axisx.min;\n }\n\n // clip with xmax\n if (x1 >= x2 && x1 > axisx.max) {\n if (x2 > axisx.max) continue;\n y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;\n x1 = axisx.max;\n } else if (x2 >= x1 && x2 > axisx.max) {\n if (x1 > axisx.max) continue;\n y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;\n x2 = axisx.max;\n }\n\n if (x1 != prevx || y1 != prevy) {\n ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);\n }\n\n var ax1 = axisx.p2c(x1) + xoffset,\n ay1 = axisy.p2c(y1) + yoffset,\n ax2 = axisx.p2c(x2) + xoffset,\n ay2 = axisy.p2c(y2) + yoffset,\n dashOffset;\n\n function lineSegmentOffset(segmentLength) {\n\n var c = Math.sqrt(Math.pow(ax2 - ax1, 2) + Math.pow(ay2 - ay1, 2));\n\n if (c <= segmentLength) {\n return {\n deltaX: ax2 - ax1,\n deltaY: ay2 - ay1,\n distance: c,\n remainder: segmentLength - c\n }\n } else {\n var xsign = ax2 > ax1 ? 1 : -1,\n ysign = ay2 > ay1 ? 1 : -1;\n return {\n deltaX: xsign * Math.sqrt(Math.pow(segmentLength, 2) / (1 + Math.pow((ay2 - ay1)/(ax2 - ax1), 2))),\n deltaY: ysign * Math.sqrt(Math.pow(segmentLength, 2) - Math.pow(segmentLength, 2) / (1 + Math.pow((ay2 - ay1)/(ax2 - ax1), 2))),\n distance: segmentLength,\n remainder: 0\n };\n }\n }\n //-end lineSegmentOffset\n\n do {\n\n dashOffset = lineSegmentOffset(\n dashRemainder > 0 ? dashRemainder :\n dashOn ? dashOnLength : dashOffLength);\n\n if (dashOffset.deltaX != 0 || dashOffset.deltaY != 0) {\n if (dashOn) {\n ctx.lineTo(ax1 + dashOffset.deltaX, ay1 + dashOffset.deltaY);\n } else {\n ctx.moveTo(ax1 + dashOffset.deltaX, ay1 + dashOffset.deltaY);\n }\n }\n\n dashOn = !dashOn;\n dashRemainder = dashOffset.remainder;\n ax1 += dashOffset.deltaX;\n ay1 += dashOffset.deltaY;\n\n } while (dashOffset.distance > 0);\n\n prevx = x2;\n prevy = y2;\n }\n\n ctx.stroke();\n }\n //-end plotDashes\n\n ctx.save();\n ctx.translate(plotOffset.left, plotOffset.top);\n ctx.lineJoin = 'round';\n\n var lw = series.dashes.lineWidth,\n sw = series.shadowSize;\n\n // FIXME: consider another form of shadow when filling is turned on\n if (lw > 0 && sw > 0) {\n // draw shadow as a thick and thin line with transparency\n ctx.lineWidth = sw;\n ctx.strokeStyle = \"rgba(0,0,0,0.1)\";\n // position shadow at angle from the mid of line\n var angle = Math.PI/18;\n plotDashes(Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2));\n ctx.lineWidth = sw/2;\n plotDashes(Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4));\n }\n\n ctx.lineWidth = lw;\n ctx.strokeStyle = series.color;\n\n if (lw > 0) {\n plotDashes(0, 0);\n }\n\n ctx.restore();\n\n });\n //-end draw hook\n\n });\n //-end processDatapoints hook\n\n }\n //-end init\n\n $.plot.plugins.push({\n init: init,\n options: {\n series: {\n dashes: {\n show: false,\n lineWidth: 2,\n dashLength: 10\n }\n }\n },\n name: 'dashes',\n version: '0.1'\n });\n\n})(jQuery)\n","define([\n 'jquery',\n 'lodash',\n 'angular',\n 'tether-drop',\n],\nfunction ($, _, angular, Drop) {\n 'use strict';\n\n function createAnnotationToolip(element, event, plot) {\n var injector = angular.element(document).injector();\n var content = document.createElement('div');\n content.innerHTML = '';\n\n injector.invoke([\"$compile\", \"$rootScope\", function($compile, $rootScope) {\n var eventManager = plot.getOptions().events.manager;\n var tmpScope = $rootScope.$new(true);\n tmpScope.event = event;\n tmpScope.onEdit = function() {\n eventManager.editEvent(event);\n };\n\n $compile(content)(tmpScope);\n tmpScope.$digest();\n tmpScope.$destroy();\n\n var drop = new Drop({\n target: element[0],\n content: content,\n position: \"bottom center\",\n classes: 'drop-popover drop-popover--annotation',\n openOn: 'hover',\n hoverCloseDelay: 200,\n tetherOptions: {\n constraints: [{to: 'window', pin: true, attachment: \"both\"}]\n }\n });\n\n drop.open();\n\n drop.on('close', function() {\n setTimeout(function() {\n drop.destroy();\n });\n });\n }]);\n }\n\n var markerElementToAttachTo = null;\n\n function createEditPopover(element, event, plot) {\n var eventManager = plot.getOptions().events.manager;\n if (eventManager.editorOpen) {\n // update marker element to attach to (needed in case of legend on the right\n // when there is a double render pass and the inital marker element is removed)\n markerElementToAttachTo = element;\n return;\n }\n\n // mark as openend\n eventManager.editorOpened();\n // set marker elment to attache to\n markerElementToAttachTo = element;\n\n // wait for element to be attached and positioned\n setTimeout(function() {\n\n var injector = angular.element(document).injector();\n var content = document.createElement('div');\n content.innerHTML = '';\n\n injector.invoke([\"$compile\", \"$rootScope\", function($compile, $rootScope) {\n var scope = $rootScope.$new(true);\n var drop;\n\n scope.event = event;\n scope.panelCtrl = eventManager.panelCtrl;\n scope.close = function() {\n drop.close();\n };\n\n $compile(content)(scope);\n scope.$digest();\n\n drop = new Drop({\n target: markerElementToAttachTo[0],\n content: content,\n position: \"bottom center\",\n classes: 'drop-popover drop-popover--form',\n openOn: 'click',\n tetherOptions: {\n constraints: [{to: 'window', pin: true, attachment: \"both\"}]\n }\n });\n\n drop.open();\n eventManager.editorOpened();\n\n drop.on('close', function() {\n // need timeout here in order call drop.destroy\n setTimeout(function() {\n eventManager.editorClosed();\n scope.$destroy();\n drop.destroy();\n });\n });\n }]);\n\n }, 100);\n }\n\n /*\n * jquery.flot.events\n *\n * description: Flot plugin for adding events/markers to the plot\n * version: 0.2.5\n * authors:\n * Alexander Wunschik \n * Joel Oughton \n * Nicolas Joseph \n *\n * website: https://github.com/mojoaxel/flot-events\n *\n * released under MIT License and GPLv2+\n */\n\n /**\n * A class that allows for the drawing an remove of some object\n */\n var DrawableEvent = function(object, drawFunc, clearFunc, moveFunc, left, top, width, height) {\n var _object = object;\n var\t_drawFunc = drawFunc;\n var\t_clearFunc = clearFunc;\n var\t_moveFunc = moveFunc;\n var\t_position = { left: left, top: top };\n var\t_width = width;\n var\t_height = height;\n\n this.width = function() { return _width; };\n this.height = function() { return _height; };\n this.position = function() { return _position; };\n this.draw = function() { _drawFunc(_object); };\n this.clear = function() { _clearFunc(_object); };\n this.getObject = function() { return _object; };\n this.moveTo = function(position) {\n _position = position;\n _moveFunc(_object, _position);\n };\n };\n\n /**\n * Event class that stores options (eventType, min, max, title, description) and the object to draw.\n */\n var VisualEvent = function(options, drawableEvent) {\n var _parent;\n var _options = options;\n var _drawableEvent = drawableEvent;\n var _hidden = false;\n\n this.visual = function() { return _drawableEvent; };\n this.getOptions = function() { return _options; };\n this.getParent = function() { return _parent; };\n this.isHidden = function() { return _hidden; };\n this.hide = function() { _hidden = true; };\n this.unhide = function() { _hidden = false; };\n };\n\n /**\n * A Class that handles the event-markers inside the given plot\n */\n var EventMarkers = function(plot) {\n var _events = [];\n\n this._types = [];\n this._plot = plot;\n this.eventsEnabled = false;\n\n this.getEvents = function() {\n return _events;\n };\n\n this.setTypes = function(types) {\n return this._types = types;\n };\n\n /**\n * create internal objects for the given events\n */\n this.setupEvents = function(events) {\n var that = this;\n var parts = _.partition(events, 'isRegion');\n var regions = parts[0];\n events = parts[1];\n\n $.each(events, function(index, event) {\n var ve = new VisualEvent(event, that._buildDiv(event));\n _events.push(ve);\n });\n\n $.each(regions, function (index, event) {\n var vre = new VisualEvent(event, that._buildRegDiv(event));\n _events.push(vre);\n });\n\n _events.sort(function(a, b) {\n var ao = a.getOptions(), bo = b.getOptions();\n if (ao.min > bo.min) { return 1; }\n if (ao.min < bo.min) { return -1; }\n return 0;\n });\n };\n\n /**\n * draw the events to the plot\n */\n this.drawEvents = function() {\n var that = this;\n // var o = this._plot.getPlotOffset();\n\n $.each(_events, function(index, event) {\n // check event is inside the graph range\n if (that._insidePlot(event.getOptions().min) && !event.isHidden()) {\n event.visual().draw();\n } else {\n event.visual().getObject().hide();\n }\n });\n };\n\n /**\n * update the position of the event-markers (e.g. after scrolling or zooming)\n */\n this.updateEvents = function() {\n var that = this;\n var o = this._plot.getPlotOffset(), left, top;\n var xaxis = this._plot.getXAxes()[this._plot.getOptions().events.xaxis - 1];\n\n $.each(_events, function(index, event) {\n top = o.top + that._plot.height() - event.visual().height();\n left = xaxis.p2c(event.getOptions().min) + o.left - event.visual().width() / 2;\n event.visual().moveTo({ top: top, left: left });\n });\n };\n\n /**\n * remove all events from the plot\n */\n this._clearEvents = function() {\n $.each(_events, function(index, val) {\n val.visual().clear();\n });\n _events = [];\n };\n\n /**\n * create a DOM element for the given event\n */\n this._buildDiv = function(event) {\n var that = this;\n\n var container = this._plot.getPlaceholder();\n var o = this._plot.getPlotOffset();\n var axes = this._plot.getAxes();\n var xaxis = this._plot.getXAxes()[this._plot.getOptions().events.xaxis - 1];\n var yaxis, top, left, color, markerSize, markerShow, lineStyle, lineWidth;\n var markerTooltip;\n\n // determine the y axis used\n if (axes.yaxis && axes.yaxis.used) { yaxis = axes.yaxis; }\n if (axes.yaxis2 && axes.yaxis2.used) { yaxis = axes.yaxis2; }\n\n // map the eventType to a types object\n var eventTypeId = event.eventType;\n\n if (this._types === null || !this._types[eventTypeId] || !this._types[eventTypeId].color) {\n color = '#666';\n } else {\n color = this._types[eventTypeId].color;\n }\n\n if (this._types === null || !this._types[eventTypeId] || !this._types[eventTypeId].markerSize) {\n markerSize = 8; //default marker size\n } else {\n markerSize = this._types[eventTypeId].markerSize;\n }\n\n if (this._types === null || !this._types[eventTypeId] || this._types[eventTypeId].markerShow === undefined) {\n markerShow = true;\n } else {\n markerShow = this._types[eventTypeId].markerShow;\n }\n\n if (this._types === null || !this._types[eventTypeId] || this._types[eventTypeId].markerTooltip === undefined) {\n markerTooltip = true;\n } else {\n markerTooltip = this._types[eventTypeId].markerTooltip;\n }\n\n if (this._types == null || !this._types[eventTypeId] || !this._types[eventTypeId].lineStyle) {\n lineStyle = 'dashed'; //default line style\n } else {\n lineStyle = this._types[eventTypeId].lineStyle.toLowerCase();\n }\n\n if (this._types == null || !this._types[eventTypeId] || this._types[eventTypeId].lineWidth === undefined) {\n lineWidth = 1; //default line width\n } else {\n lineWidth = this._types[eventTypeId].lineWidth;\n }\n\n var topOffset = xaxis.options.eventSectionHeight || 0;\n topOffset = topOffset / 3;\n\n top = o.top + this._plot.height() + topOffset;\n left = xaxis.p2c(event.min) + o.left;\n\n var line = $('').css({\n \"position\": \"absolute\",\n \"opacity\": 0.8,\n \"left\": left + 'px',\n \"top\": 8,\n \"width\": lineWidth + \"px\",\n \"height\": this._plot.height() + topOffset * 0.8,\n \"border-left-width\": lineWidth + \"px\",\n \"border-left-style\": lineStyle,\n \"border-left-color\": color,\n \"color\": color\n })\n .appendTo(container);\n\n if (markerShow) {\n var marker = $('').css({\n \"position\": \"absolute\",\n \"left\": (-markerSize - Math.round(lineWidth / 2)) + \"px\",\n \"font-size\": 0,\n \"line-height\": 0,\n \"width\": 0,\n \"height\": 0,\n \"border-left\": markerSize+\"px solid transparent\",\n \"border-right\": markerSize+\"px solid transparent\"\n });\n\n marker.appendTo(line);\n\n if (this._types[eventTypeId] && this._types[eventTypeId].position && this._types[eventTypeId].position.toUpperCase() === 'BOTTOM') {\n marker.css({\n \"top\": top-markerSize-8 +\"px\",\n \"border-top\": \"none\",\n \"border-bottom\": markerSize+\"px solid \" + color\n });\n } else {\n marker.css({\n \"top\": \"0px\",\n \"border-top\": markerSize+\"px solid \" + color,\n \"border-bottom\": \"none\"\n });\n }\n\n marker.data({\n \"event\": event\n });\n\n var mouseenter = function() {\n createAnnotationToolip(marker, $(this).data(\"event\"), that._plot);\n };\n\n if (event.editModel) {\n createEditPopover(marker, event.editModel, that._plot);\n }\n\n var mouseleave = function() {\n that._plot.clearSelection();\n };\n\n if (markerTooltip) {\n marker.css({ \"cursor\": \"help\" });\n marker.hover(mouseenter, mouseleave);\n }\n }\n\n var drawableEvent = new DrawableEvent(\n line,\n function drawFunc(obj) { obj.show(); },\n function(obj) { obj.remove(); },\n function(obj, position) {\n obj.css({\n top: position.top,\n left: position.left\n });\n },\n left,\n top,\n line.width(),\n line.height()\n );\n\n return drawableEvent;\n };\n\n /**\n * create a DOM element for the given region\n */\n this._buildRegDiv = function (event) {\n var that = this;\n\n var container = this._plot.getPlaceholder();\n var o = this._plot.getPlotOffset();\n var axes = this._plot.getAxes();\n var xaxis = this._plot.getXAxes()[this._plot.getOptions().events.xaxis - 1];\n var yaxis, top, left, lineWidth, regionWidth, lineStyle, color, markerTooltip;\n\n // determine the y axis used\n if (axes.yaxis && axes.yaxis.used) { yaxis = axes.yaxis; }\n if (axes.yaxis2 && axes.yaxis2.used) { yaxis = axes.yaxis2; }\n\n // map the eventType to a types object\n var eventTypeId = event.eventType;\n\n if (this._types === null || !this._types[eventTypeId] || !this._types[eventTypeId].color) {\n color = '#666';\n } else {\n color = this._types[eventTypeId].color;\n }\n\n if (this._types === null || !this._types[eventTypeId] || this._types[eventTypeId].markerTooltip === undefined) {\n markerTooltip = true;\n } else {\n markerTooltip = this._types[eventTypeId].markerTooltip;\n }\n\n if (this._types == null || !this._types[eventTypeId] || this._types[eventTypeId].lineWidth === undefined) {\n lineWidth = 1; //default line width\n } else {\n lineWidth = this._types[eventTypeId].lineWidth;\n }\n\n if (this._types == null || !this._types[eventTypeId] || !this._types[eventTypeId].lineStyle) {\n lineStyle = 'dashed'; //default line style\n } else {\n lineStyle = this._types[eventTypeId].lineStyle.toLowerCase();\n }\n\n var topOffset = 2;\n top = o.top + this._plot.height() + topOffset;\n\n var timeFrom = Math.min(event.min, event.timeEnd);\n var timeTo = Math.max(event.min, event.timeEnd);\n left = xaxis.p2c(timeFrom) + o.left;\n var right = xaxis.p2c(timeTo) + o.left;\n regionWidth = right - left;\n\n _.each([left, right], function(position) {\n var line = $('').css({\n \"position\": \"absolute\",\n \"opacity\": 0.8,\n \"left\": position + 'px',\n \"top\": 8,\n \"width\": lineWidth + \"px\",\n \"height\": that._plot.height() + topOffset,\n \"border-left-width\": lineWidth + \"px\",\n \"border-left-style\": lineStyle,\n \"border-left-color\": color,\n \"color\": color\n });\n line.appendTo(container);\n });\n\n var region = $('').css({\n \"position\": \"absolute\",\n \"opacity\": 0.5,\n \"left\": left + 'px',\n \"top\": top,\n \"width\": Math.round(regionWidth + lineWidth) + \"px\",\n \"height\": \"0.5rem\",\n \"border-left-color\": color,\n \"color\": color,\n \"background-color\": color\n });\n region.appendTo(container);\n\n region.data({\n \"event\": event\n });\n\n var mouseenter = function () {\n createAnnotationToolip(region, $(this).data(\"event\"), that._plot);\n };\n\n if (event.editModel) {\n createEditPopover(region, event.editModel, that._plot);\n }\n\n var mouseleave = function () {\n that._plot.clearSelection();\n };\n\n if (markerTooltip) {\n region.css({ \"cursor\": \"help\" });\n region.hover(mouseenter, mouseleave);\n }\n\n var drawableEvent = new DrawableEvent(\n region,\n function drawFunc(obj) { obj.show(); },\n function (obj) { obj.remove(); },\n function (obj, position) {\n obj.css({\n top: position.top,\n left: position.left\n });\n },\n left,\n top,\n region.width(),\n region.height()\n );\n\n return drawableEvent;\n };\n\n /**\n * check if the event is inside visible range\n */\n this._insidePlot = function(x) {\n var xaxis = this._plot.getXAxes()[this._plot.getOptions().events.xaxis - 1];\n var xc = xaxis.p2c(x);\n return xc > 0 && xc < xaxis.p2c(xaxis.max);\n };\n };\n\n /**\n * initialize the plugin for the given plot\n */\n function init(plot) {\n /*jshint validthis:true */\n var that = this;\n var eventMarkers = new EventMarkers(plot);\n\n plot.getEvents = function() {\n return eventMarkers._events;\n };\n\n plot.hideEvents = function() {\n $.each(eventMarkers._events, function(index, event) {\n event.visual().getObject().hide();\n });\n };\n\n plot.showEvents = function() {\n plot.hideEvents();\n $.each(eventMarkers._events, function(index, event) {\n event.hide();\n });\n\n that.eventMarkers.drawEvents();\n };\n\n // change events on an existing plot\n plot.setEvents = function(events) {\n if (eventMarkers.eventsEnabled) {\n eventMarkers.setupEvents(events);\n }\n };\n\n plot.hooks.processOptions.push(function(plot, options) {\n // enable the plugin\n if (options.events.data != null) {\n eventMarkers.eventsEnabled = true;\n }\n });\n\n plot.hooks.draw.push(function(plot) {\n var options = plot.getOptions();\n\n if (eventMarkers.eventsEnabled) {\n // check for first run\n if (eventMarkers.getEvents().length < 1) {\n eventMarkers.setTypes(options.events.types);\n eventMarkers.setupEvents(options.events.data);\n } else {\n eventMarkers.updateEvents();\n }\n }\n\n eventMarkers.drawEvents();\n });\n }\n\n var defaultOptions = {\n events: {\n data: null,\n types: null,\n xaxis: 1,\n position: 'BOTTOM'\n }\n };\n\n $.plot.plugins.push({\n init: init,\n options: defaultOptions,\n name: \"events\",\n version: \"0.2.5\"\n });\n});\n","(function($) {\n \"use strict\";\n\n var options = {\n series: {\n fillBelowTo: null\n }\n };\n\n function init(plot) {\n function findBelowSeries( series, allseries ) {\n\n var i;\n\n for ( i = 0; i < allseries.length; ++i ) {\n if ( allseries[ i ].id === series.fillBelowTo ) {\n return allseries[ i ];\n }\n }\n\n return null;\n }\n\n /* top and bottom doesn't actually matter for this, we're just using it to help make this easier to think about */\n /* this is a vector cross product operation */\n function segmentIntersection(top_left_x, top_left_y, top_right_x, top_right_y, bottom_left_x, bottom_left_y, bottom_right_x, bottom_right_y) {\n var top_delta_x, top_delta_y, bottom_delta_x, bottom_delta_y,\n s, t;\n\n top_delta_x = top_right_x - top_left_x;\n top_delta_y = top_right_y - top_left_y;\n bottom_delta_x = bottom_right_x - bottom_left_x;\n bottom_delta_y = bottom_right_y - bottom_left_y;\n\n s = (\n (-top_delta_y * (top_left_x - bottom_left_x)) + (top_delta_x * (top_left_y - bottom_left_y))\n ) / (\n -bottom_delta_x * top_delta_y + top_delta_x * bottom_delta_y\n );\n\n t = (\n (bottom_delta_x * (top_left_y - bottom_left_y)) - (bottom_delta_y * (top_left_x - bottom_left_x))\n ) / (\n -bottom_delta_x * top_delta_y + top_delta_x * bottom_delta_y\n );\n\n // Collision detected\n if (s >= 0 && s <= 1 && t >= 0 && t <= 1) {\n return [\n top_left_x + (t * top_delta_x), // X\n top_left_y + (t * top_delta_y) // Y\n ];\n }\n\n // No collision\n return null;\n }\n\n function plotDifferenceArea(plot, ctx, series) {\n if ( series.fillBelowTo === null ) {\n return;\n }\n\n var otherseries,\n\n ps,\n points,\n\n otherps,\n otherpoints,\n\n plotOffset,\n fillStyle;\n\n function openPolygon(x, y) {\n ctx.beginPath();\n ctx.moveTo(\n series.xaxis.p2c(x) + plotOffset.left,\n series.yaxis.p2c(y) + plotOffset.top\n );\n\n }\n\n function closePolygon() {\n ctx.closePath();\n ctx.fill();\n }\n\n function validateInput() {\n if (points.length/ps !== otherpoints.length/otherps) {\n console.error(\"Refusing to graph inconsistent number of points\");\n return false;\n }\n\n var i;\n for (i = 0; i < (points.length / ps); i++) {\n if (\n points[i * ps] !== null &&\n otherpoints[i * otherps] !== null &&\n points[i * ps] !== otherpoints[i * otherps]\n ) {\n console.error(\"Refusing to graph points without matching value\");\n return false;\n }\n }\n\n return true;\n }\n\n function findNextStart(start_i, end_i) {\n console.assert(end_i > start_i, \"expects the end index to be greater than the start index\");\n\n var start = (\n start_i === 0 ||\n points[start_i - 1] === null ||\n otherpoints[start_i - 1] === null\n ),\n equal = false,\n i,\n intersect;\n\n for (i = start_i; i < end_i; i++) {\n // Take note of null points\n if (\n points[(i * ps) + 1] === null ||\n otherpoints[(i * ps) + 1] === null\n ) {\n equal = false;\n start = true;\n }\n\n // Take note of equal points\n else if (points[(i * ps) + 1] === otherpoints[(i * otherps) + 1]) {\n equal = true;\n start = false;\n }\n\n\n else if (points[(i * ps) + 1] > otherpoints[(i * otherps) + 1]) {\n // If we begin above the desired point\n if (start) {\n openPolygon(points[i * ps], points[(i * ps) + 1]);\n }\n\n // If an equal point preceeds this, start the polygon at that equal point\n else if (equal) {\n openPolygon(points[(i - 1) * ps], points[((i - 1) * ps) + 1]);\n }\n\n // Otherwise, find the intersection point, and start it there\n else {\n intersect = intersectionPoint(i);\n openPolygon(intersect[0], intersect[1]);\n }\n\n topTraversal(i, end_i);\n return;\n }\n\n // If we go below equal, equal at any preceeding point is irrelevant\n else {\n start = false;\n equal = false;\n }\n }\n }\n\n function intersectionPoint(right_i) {\n console.assert(right_i > 0, \"expects the second point in the series line segment\");\n\n var i, intersect;\n\n for (i = 1; i < (otherpoints.length/otherps); i++) {\n intersect = segmentIntersection(\n points[(right_i - 1) * ps], points[((right_i - 1) * ps) + 1],\n points[right_i * ps], points[(right_i * ps) + 1],\n\n otherpoints[(i - 1) * otherps], otherpoints[((i - 1) * otherps) + 1],\n otherpoints[i * otherps], otherpoints[(i * otherps) + 1]\n );\n\n if (intersect !== null) {\n return intersect;\n }\n }\n\n console.error(\"intersectionPoint() should only be called when an intersection happens\");\n }\n\n function bottomTraversal(start_i, end_i) {\n console.assert(start_i >= end_i, \"the start should be the rightmost point, and the end should be the leftmost (excluding the equal or intersecting point)\");\n\n var i;\n\n for (i = start_i; i >= end_i; i--) {\n ctx.lineTo(\n otherseries.xaxis.p2c(otherpoints[i * otherps]) + plotOffset.left,\n otherseries.yaxis.p2c(otherpoints[(i * otherps) + 1]) + plotOffset.top\n );\n }\n\n closePolygon();\n }\n\n function topTraversal(start_i, end_i) {\n console.assert(start_i <= end_i, \"the start should be the rightmost point, and the end should be the leftmost (excluding the equal or intersecting point)\");\n\n var i,\n intersect;\n\n for (i = start_i; i < end_i; i++) {\n if (points[(i * ps) + 1] === null && i > start_i) {\n bottomTraversal(i - 1, start_i);\n findNextStart(i, end_i);\n return;\n }\n\n else if (points[(i * ps) + 1] === otherpoints[(i * otherps) + 1]) {\n bottomTraversal(i, start_i);\n findNextStart(i, end_i);\n return;\n }\n\n else if (points[(i * ps) + 1] < otherpoints[(i * otherps) + 1]) {\n intersect = intersectionPoint(i);\n ctx.lineTo(\n series.xaxis.p2c(intersect[0]) + plotOffset.left,\n series.yaxis.p2c(intersect[1]) + plotOffset.top\n );\n bottomTraversal(i, start_i);\n findNextStart(i, end_i);\n return;\n\n }\n\n else {\n ctx.lineTo(\n series.xaxis.p2c(points[i * ps]) + plotOffset.left,\n series.yaxis.p2c(points[(i * ps) + 1]) + plotOffset.top\n );\n }\n }\n\n bottomTraversal(end_i, start_i);\n }\n\n\n // Begin processing\n\n otherseries = findBelowSeries( series, plot.getData() );\n\n if ( !otherseries ) {\n return;\n }\n\n ps = series.datapoints.pointsize;\n points = series.datapoints.points;\n otherps = otherseries.datapoints.pointsize;\n otherpoints = otherseries.datapoints.points;\n plotOffset = plot.getPlotOffset();\n\n if (!validateInput()) {\n return;\n }\n\n\n // Flot's getFillStyle() should probably be exposed somewhere\n fillStyle = $.color.parse(series.color);\n fillStyle.a = 0.4;\n fillStyle.normalize();\n ctx.fillStyle = fillStyle.toString();\n\n\n // Begin recursive bi-directional traversal\n findNextStart(0, points.length/ps);\n }\n\n plot.hooks.drawSeries.push(plotDifferenceArea);\n }\n\n $.plot.plugins.push({\n init: init,\n options: options,\n name: \"fillbelow\",\n version: \"0.1.0\"\n });\n\n})(jQuery);\n","/* Javascript plotting library for jQuery, version 0.8.3.\n\nCopyright (c) 2007-2014 IOLA and Ole Laursen.\nLicensed under the MIT license.\n\n*/\n\n// first an inline dependency, jquery.colorhelpers.js, we inline it here\n// for convenience\n\n/* Plugin for jQuery for working with colors.\n *\n * Version 1.1.\n *\n * Inspiration from jQuery color animation plugin by John Resig.\n *\n * Released under the MIT license by Ole Laursen, October 2009.\n *\n * Examples:\n *\n * $.color.parse(\"#fff\").scale('rgb', 0.25).add('a', -0.5).toString()\n * var c = $.color.extract($(\"#mydiv\"), 'background-color');\n * console.log(c.r, c.g, c.b, c.a);\n * $.color.make(100, 50, 25, 0.4).toString() // returns \"rgba(100,50,25,0.4)\"\n *\n * Note that .scale() and .add() return the same modified object\n * instead of making a new one.\n *\n * V. 1.1: Fix error handling so e.g. parsing an empty string does\n * produce a color rather than just crashing.\n */\n(function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i=1){return\"rgb(\"+[o.r,o.g,o.b].join(\",\")+\")\"}else{return\"rgba(\"+[o.r,o.g,o.b,o.a].join(\",\")+\")\"}};o.normalize=function(){function clamp(min,value,max){return valuemax?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=\"\"&&c!=\"transparent\")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),\"body\"));if(c==\"rgba(0, 0, 0, 0)\")c=\"transparent\";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\\(\\s*([0-9]{1,3})\\s*,\\s*([0-9]{1,3})\\s*,\\s*([0-9]{1,3})\\s*\\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/rgba\\(\\s*([0-9]{1,3})\\s*,\\s*([0-9]{1,3})\\s*,\\s*([0-9]{1,3})\\s*,\\s*([0-9]+(?:\\.[0-9]+)?)\\s*\\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\\(\\s*([0-9]+(?:\\.[0-9]+)?)\\%\\s*,\\s*([0-9]+(?:\\.[0-9]+)?)\\%\\s*,\\s*([0-9]+(?:\\.[0-9]+)?)\\%\\s*\\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/rgba\\(\\s*([0-9]+(?:\\.[0-9]+)?)\\%\\s*,\\s*([0-9]+(?:\\.[0-9]+)?)\\%\\s*,\\s*([0-9]+(?:\\.[0-9]+)?)\\%\\s*,\\s*([0-9]+(?:\\.[0-9]+)?)\\s*\\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name==\"transparent\")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);\n\n// the actual Flot code\n(function($) {\n\n\t// Cache the prototype hasOwnProperty for faster access\n\n\tvar hasOwnProperty = Object.prototype.hasOwnProperty;\n\n // A shim to provide 'detach' to jQuery versions prior to 1.4. Using a DOM\n // operation produces the same effect as detach, i.e. removing the element\n // without touching its jQuery data.\n\n // Do not merge this into Flot 0.9, since it requires jQuery 1.4.4+.\n\n if (!$.fn.detach) {\n $.fn.detach = function() {\n return this.each(function() {\n if (this.parentNode) {\n this.parentNode.removeChild( this );\n }\n });\n };\n }\n\n\t///////////////////////////////////////////////////////////////////////////\n\t// The Canvas object is a wrapper around an HTML5