f32bdec80f467dfa6322b3a0f3e563c932a8cabd
[WebKit-https.git] / Websites / perf.webkit.org / public / v3 / pages / chart-pane.js
1
2 function createTrendLineExecutableFromAveragingFunction(callback) {
3     return function (source, parameters) {
4         var timeSeries = source.measurementSet.fetchedTimeSeries(source.type, source.includeOutliers, source.extendToFuture);
5         var values = timeSeries.values();
6         if (!values.length)
7             return Promise.resolve(null);
8
9         var averageValues = callback.call(null, values, ...parameters);
10         if (!averageValues)
11             return Promise.resolve(null);
12
13         var interval = function () { return null; }
14         var result = new Array(averageValues.length);
15         for (var i = 0; i < averageValues.length; i++)
16             result[i] = {time: timeSeries.findPointByIndex(i).time, value: averageValues[i], interval: interval};
17
18         return Promise.resolve(result);
19     }
20 }
21
22 const ChartTrendLineTypes = [
23     {
24         id: 0,
25         label: 'None',
26     },
27     {
28         id: 5,
29         label: 'Segmentation',
30         execute: function (source, parameters) {
31             return source.measurementSet.fetchSegmentation('segmentTimeSeriesByMaximizingSchwarzCriterion', parameters,
32                 source.type, source.includeOutliers, source.extendToFuture).then(function (segmentation) {
33                 return segmentation;
34             });
35         },
36         parameterList: [
37             {label: "Segment count weight", value: 2.5, min: 0.01, max: 10, step: 0.01},
38             {label: "Grid size", value: 500, min: 100, max: 10000, step: 10}
39         ]
40     },
41     {
42         id: 6,
43         label: 'Segmentation with Welch\'s t-test change detection',
44         execute: async function (source, parameters) {
45             const segmentation =  await source.measurementSet.fetchSegmentation('segmentTimeSeriesByMaximizingSchwarzCriterion', parameters,
46                 source.type, source.includeOutliers, source.extendToFuture);
47             if (!segmentation)
48                 return segmentation;
49
50             const metric = Metric.findById(source.measurementSet.metricId());
51             const timeSeries = source.measurementSet.fetchedTimeSeries(source.type, source.includeOutliers, source.extendToFuture);
52             segmentation.analysisAnnotations = Statistics.findRangesForChangeDetectionsWithWelchsTTest(timeSeries.values(),
53                 segmentation, parameters[parameters.length - 1]).map((range) => {
54                 const startPoint = timeSeries.findPointByIndex(range.startIndex);
55                 const endPoint = timeSeries.findPointByIndex(range.endIndex);
56                 const summary = metric.labelForDifference(range.segmentationStartValue, range.segmentationEndValue, 'progression', 'regression');
57                 return {
58                     task: null,
59                     fillStyle: ChartStyles.annotationFillStyleForTask(null),
60                     startTime: startPoint.time,
61                     endTime: endPoint.time,
62                     label: `Potential ${summary.changeLabel}`,
63                 };
64             });
65             return segmentation;
66         },
67         parameterList: [
68             {label: "Segment count weight", value: 2.5, min: 0.01, max: 10, step: 0.01},
69             {label: "Grid size", value: 500, min: 100, max: 10000, step: 10},
70             {label: "t-test significance", value: 0.99, options: Statistics.supportedOneSideTTestProbabilities()},
71         ]
72     },
73     {
74         id: 1,
75         label: 'Simple Moving Average',
76         parameterList: [
77             {label: "Backward window size", value: 8, min: 2, step: 1},
78             {label: "Forward window size", value: 4, min: 0, step: 1}
79         ],
80         execute: createTrendLineExecutableFromAveragingFunction(Statistics.movingAverage.bind(Statistics))
81     },
82     {
83         id: 2,
84         label: 'Cumulative Moving Average',
85         execute: createTrendLineExecutableFromAveragingFunction(Statistics.cumulativeMovingAverage.bind(Statistics))
86     },
87     {
88         id: 3,
89         label: 'Exponential Moving Average',
90         parameterList: [
91             {label: "Smoothing factor", value: 0.01, min: 0.001, max: 0.9, step: 0.001},
92         ],
93         execute: createTrendLineExecutableFromAveragingFunction(Statistics.exponentialMovingAverage.bind(Statistics))
94     },
95 ];
96 ChartTrendLineTypes.DefaultType = ChartTrendLineTypes[1];
97
98
99 class ChartPane extends ChartPaneBase {
100     constructor(chartsPage, platformId, metricId)
101     {
102         super('chart-pane');
103
104         this._mainChartIndicatorWasLocked = false;
105         this._chartsPage = chartsPage;
106         this._lockedPopover = null;
107         this._trendLineType = null;
108         this._trendLineParameters = [];
109         this._trendLineVersion = 0;
110         this._renderedTrendLineOptions = false;
111
112         this.configure(platformId, metricId);
113     }
114
115     didConstructShadowTree()
116     {
117         this.part('close').listenToAction('activate', () => {
118             this._chartsPage.closePane(this);
119         })
120     }
121
122     serializeState()
123     {
124         var state = [this._platformId, this._metricId];
125         if (this._mainChart) {
126             var selection = this._mainChart.currentSelection();
127             const indicator = this._mainChart.currentIndicator();
128             if (selection)
129                 state[2] = selection;
130             else if (indicator && indicator.isLocked)
131                 state[2] = indicator.point.id;
132         }
133
134         var graphOptions = new Set;
135         if (!this.isSamplingEnabled())
136             graphOptions.add('noSampling');
137         if (this.isShowingOutliers())
138             graphOptions.add('showOutliers');
139
140         if (graphOptions.size)
141             state[3] = graphOptions;
142
143         if (this._trendLineType)
144             state[4] = [this._trendLineType.id].concat(this._trendLineParameters);
145
146         return state;
147     }
148
149     updateFromSerializedState(state, isOpen)
150     {
151         if (!this._mainChart)
152             return;
153
154         var selectionOrIndicatedPoint = state[2];
155         if (selectionOrIndicatedPoint instanceof Array)
156             this._mainChart.setSelection([parseFloat(selectionOrIndicatedPoint[0]), parseFloat(selectionOrIndicatedPoint[1])]);
157         else if (typeof(selectionOrIndicatedPoint) == 'number') {
158             this._mainChart.setIndicator(selectionOrIndicatedPoint, true);
159             this._mainChartIndicatorWasLocked = true;
160         } else
161             this._mainChart.setIndicator(null, false);
162
163         // FIXME: This forces sourceList to be set twice. First in configure inside the constructor then here.
164         // FIXME: Show full y-axis when graphOptions is true to be compatible with v2 UI.
165         var graphOptions = state[3];
166         if (graphOptions instanceof Set) {
167             this.setSamplingEnabled(!graphOptions.has('nosampling'));
168             this.setShowOutliers(graphOptions.has('showoutliers'));
169         }
170
171         var trendLineOptions = state[4];
172         if (!(trendLineOptions instanceof Array))
173             trendLineOptions = [];
174
175         var trendLineId = trendLineOptions[0];
176         var trendLineType = ChartTrendLineTypes.find(function (type) { return type.id == trendLineId; }) || ChartTrendLineTypes.DefaultType;
177
178         this._trendLineType = trendLineType;
179         this._trendLineParameters = (trendLineType.parameterList || []).map(function (parameter, index) {
180             var specifiedValue = parseFloat(trendLineOptions[index + 1]);
181             return !isNaN(specifiedValue) ? specifiedValue : parameter.value;
182         });
183         this._updateTrendLine();
184         this._renderedTrendLineOptions = false;
185
186         // FIXME: state[5] specifies envelope in v2 UI
187         // FIXME: state[6] specifies change detection algorithm in v2 UI
188     }
189
190     setOverviewSelection(selection)
191     {
192         if (this._overviewChart)
193             this._overviewChart.setSelection(selection);
194     }
195
196     _overviewSelectionDidChange(domain, didEndDrag)
197     {
198         super._overviewSelectionDidChange(domain, didEndDrag);
199         this._chartsPage.setMainDomainFromOverviewSelection(domain, this, didEndDrag);
200     }
201
202     _mainSelectionDidChange(selection, didEndDrag)
203     {
204         super._mainSelectionDidChange(selection, didEndDrag);
205         this._chartsPage.mainChartSelectionDidChange(this, didEndDrag);
206     }
207
208     _mainSelectionDidZoom(selection)
209     {
210         super._mainSelectionDidZoom(selection);
211         this._chartsPage.setMainDomainFromZoom(selection, this);
212     }
213
214     router() { return this._chartsPage.router(); }
215
216     openNewRepository(repository)
217     {
218         this.content().querySelector('.chart-pane').focus();
219         this._chartsPage.setOpenRepository(repository);
220     }
221
222     _indicatorDidChange(indicatorID, isLocked)
223     {
224         this._chartsPage.mainChartIndicatorDidChange(this, isLocked != this._mainChartIndicatorWasLocked);
225         this._mainChartIndicatorWasLocked = isLocked;
226         super._indicatorDidChange(indicatorID, isLocked);
227     }
228
229     _analyzeRange(startPoint, endPoint)
230     {
231         const router = this._chartsPage.router();
232         const newWindow = window.open(router.url('analysis/task/create', {inProgress: true}), '_blank');
233
234         const analyzePopover = this.content().querySelector('.chart-pane-analyze-popover');
235         const name = analyzePopover.querySelector('input').value;
236         AnalysisTask.create(name, startPoint.id, endPoint.id).then((data) => {
237             newWindow.location.href = router.url('analysis/task/' + data['taskId']);
238             this.fetchAnalysisTasks(true);
239         }, (error) => {
240             newWindow.location.href = router.url('analysis/task/create', {error: error});
241         });
242     }
243
244     _markAsOutlier(markAsOutlier, points)
245     {
246         var self = this;
247         return Promise.all(points.map(function (point) {
248             return PrivilegedAPI.sendRequest('update-run-status', {'run': point.id, 'markedOutlier': markAsOutlier});
249         })).then(function () {
250             self._mainChart.fetchMeasurementSets(true /* noCache */);
251         }, function (error) {
252             alert('Failed to update the outlier status: ' + error);
253         }).catch();
254     }
255
256     render()
257     {
258         if (this._platform && this._metric) {
259             var metric = this._metric;
260             var platform = this._platform;
261
262             this.renderReplace(this.content().querySelector('.chart-pane-title'),
263                 metric.fullName() + ' on ' + platform.name());
264         }
265
266         if (this._mainChartStatus)
267             this._renderActionToolbar();
268
269         super.render();
270     }
271
272     _renderActionToolbar()
273     {
274         var actions = [];
275         var platform = this._platform;
276         var metric = this._metric;
277
278         var element = ComponentBase.createElement;
279         var link = ComponentBase.createLink;
280         var self = this;
281
282         if (this._chartsPage.canBreakdown(platform, metric)) {
283             actions.push(element('li', link('Breakdown', function () {
284                 self._chartsPage.insertBreakdownPanesAfter(platform, metric, self);
285             })));
286         }
287
288         var platformPopover = this.content().querySelector('.chart-pane-alternative-platforms');
289         var alternativePlatforms = this._chartsPage.alternatePlatforms(platform, metric);
290         if (alternativePlatforms.length) {
291             this.renderReplace(platformPopover, Platform.sortByName(alternativePlatforms).map(function (platform) {
292                 return element('li', link(platform.label(), function () {
293                     self._chartsPage.insertPaneAfter(platform, metric, self);
294                 }));
295             }));
296
297             actions.push(this._makePopoverActionItem(platformPopover, 'Other Platforms', true));
298         } else
299             platformPopover.style.display = 'none';
300
301         var analyzePopover = this.content().querySelector('.chart-pane-analyze-popover');
302         const selectedPoints = this._mainChart.selectedPoints('current');
303         const hasSelectedPoints = selectedPoints && selectedPoints.length();
304         if (hasSelectedPoints) {
305             actions.push(this._makePopoverActionItem(analyzePopover, 'Analyze', false));
306             analyzePopover.onsubmit = this.createEventHandler(() => {
307                 this._analyzeRange(selectedPoints.firstPoint(), selectedPoints.lastPoint());
308             });
309         } else {
310             analyzePopover.style.display = 'none';
311             analyzePopover.onsubmit = this.createEventHandler(() => {});
312         }
313
314         var filteringOptions = this.content().querySelector('.chart-pane-filtering-options');
315         actions.push(this._makePopoverActionItem(filteringOptions, 'Filtering', true));
316
317         var trendLineOptions = this.content().querySelector('.chart-pane-trend-line-options');
318         actions.push(this._makePopoverActionItem(trendLineOptions, 'Trend lines', true));
319
320         this._renderFilteringPopover();
321         this._renderTrendLinePopover();
322
323         this._lockedPopover = null;
324         this.renderReplace(this.content().querySelector('.chart-pane-action-buttons'), actions);
325     }
326
327     _makePopoverActionItem(popover, label, shouldRespondToHover)
328     {
329         var self = this;
330         popover.anchor = ComponentBase.createLink(label, function () {
331             var makeVisible = self._lockedPopover != popover;
332             self._setPopoverVisibility(popover, makeVisible);
333             if (makeVisible)
334                 self._lockedPopover = popover;
335         });
336         if (shouldRespondToHover)
337             this._makePopoverOpenOnHover(popover);
338
339         return ComponentBase.createElement('li', {class: this._lockedPopover == popover ? 'selected' : ''}, popover.anchor);
340     }
341
342     _makePopoverOpenOnHover(popover)
343     {
344         var mouseIsInAnchor = false;
345         var mouseIsInPopover = false;
346
347         var self = this;
348         var closeIfNeeded = function () {
349             setTimeout(function () {
350                 if (self._lockedPopover != popover && !mouseIsInAnchor && !mouseIsInPopover)
351                     self._setPopoverVisibility(popover, false);
352             }, 0);
353         }
354
355         popover.anchor.onmouseenter = function () {
356             if (self._lockedPopover)
357                 return;
358             mouseIsInAnchor = true;
359             self._setPopoverVisibility(popover, true);
360         }
361         popover.anchor.onmouseleave = function () {
362             mouseIsInAnchor = false;
363             closeIfNeeded();         
364         }
365
366         popover.onmouseenter = function () {
367             mouseIsInPopover = true;
368         }
369         popover.onmouseleave = function () {
370             mouseIsInPopover = false;
371             closeIfNeeded();
372         }
373     }
374
375     _setPopoverVisibility(popover, visible)
376     {
377         var anchor = popover.anchor;
378         if (visible) {
379             var width = anchor.offsetParent.offsetWidth;
380             popover.style.top = anchor.offsetTop + anchor.offsetHeight + 'px';
381             popover.style.right = (width - anchor.offsetLeft - anchor.offsetWidth) + 'px';
382         }
383         popover.style.display = visible ? null : 'none';
384         anchor.parentNode.className = visible ? 'selected' : '';
385
386         if (this._lockedPopover && this._lockedPopover != popover && visible)
387             this._setPopoverVisibility(this._lockedPopover, false);
388
389         if (this._lockedPopover == popover && !visible)
390             this._lockedPopover = null;
391     }
392
393     _renderFilteringPopover()
394     {
395         var enableSampling = this.content().querySelector('.enable-sampling');
396         enableSampling.checked = this.isSamplingEnabled();
397         enableSampling.onchange = function () {
398             self.setSamplingEnabled(enableSampling.checked);
399             self._chartsPage.graphOptionsDidChange();
400         }
401
402         var showOutliers = this.content().querySelector('.show-outliers');
403         showOutliers.checked = this.isShowingOutliers();
404         showOutliers.onchange = function () {
405             self.setShowOutliers(showOutliers.checked);
406             self._chartsPage.graphOptionsDidChange();
407         }
408
409         var markAsOutlierButton = this.content().querySelector('.mark-as-outlier');
410         const indicator = this._mainChart.currentIndicator();
411         let firstSelectedPoint = indicator && indicator.isLocked ? indicator.point : null;
412         if (!firstSelectedPoint)
413             firstSelectedPoint = this._mainChart.firstSelectedPoint('current');
414         var alreayMarkedAsOutlier = firstSelectedPoint && firstSelectedPoint.markedOutlier;
415
416         var self = this;
417         markAsOutlierButton.textContent = (alreayMarkedAsOutlier ? 'Unmark' : 'Mark') + ' selected points as outlier';
418         markAsOutlierButton.onclick = function () {
419             var selectedPoints = [firstSelectedPoint];
420             if (self._mainChart.currentSelection('current'))
421                 selectedPoints = self._mainChart.selectedPoints('current');
422             self._markAsOutlier(!alreayMarkedAsOutlier, selectedPoints);
423         }
424         markAsOutlierButton.disabled = !firstSelectedPoint;
425     }
426
427     _renderTrendLinePopover()
428     {
429         var element = ComponentBase.createElement;
430         var link = ComponentBase.createLink;
431         var self = this;
432
433         const trendLineTypesContainer = this.content().querySelector('.trend-line-types');
434         if (!trendLineTypesContainer.querySelector('select')) {
435             this.renderReplace(trendLineTypesContainer, [
436                 element('select', {onchange: this._trendLineTypeDidChange.bind(this)},
437                     ChartTrendLineTypes.map((type) => { return element('option', {value: type.id}, type.label); }))
438             ]);
439         }
440         if (this._trendLineType)
441             trendLineTypesContainer.querySelector('select').value = this._trendLineType.id;
442
443         if (this._renderedTrendLineOptions)
444             return;
445         this._renderedTrendLineOptions = true;
446
447         if (this._trendLineParameters.length) {
448             var configuredParameters = this._trendLineParameters;
449             this.renderReplace(this.content().querySelector('.trend-line-parameter-list'), [
450                 element('h3', 'Parameters'),
451                 element('ul', this._trendLineType.parameterList.map(function (parameter, index) {
452                     if (parameter.options) {
453                         const select = element('select', parameter.options.map((option) =>
454                             element('option', {value: option, selected: option == parameter.value}, option)));
455                         select.onchange = self._trendLineParameterDidChange.bind(self);
456                         select.parameterIndex = index;
457                         return element('li', element('label', [parameter.label + ': ', select]));
458                     }
459
460                     var attributes = {type: 'number'};
461                     for (var name in parameter)
462                         attributes[name] = parameter[name];
463
464                     attributes.value = configuredParameters[index];
465                     const input = element('input', attributes);
466                     input.parameterIndex = index;
467                     input.oninput = self._trendLineParameterDidChange.bind(self);
468                     input.onchange = self._trendLineParameterDidChange.bind(self);
469                     return element('li', element('label', [parameter.label + ': ', input]));
470                 }))
471             ]);
472         } else
473             this.renderReplace(this.content().querySelector('.trend-line-parameter-list'), []);
474     }
475
476     _trendLineTypeDidChange(event)
477     {
478         var newType = ChartTrendLineTypes.find(function (type) { return type.id == event.target.value });
479         if (newType == this._trendLineType)
480             return;
481
482         this._trendLineType = newType;
483         this._trendLineParameters = this._defaultParametersForTrendLine(newType);
484         this._renderedTrendLineOptions = false;
485
486         this._updateTrendLine();
487         this._chartsPage.graphOptionsDidChange();
488         this.enqueueToRender();
489     }
490
491     _defaultParametersForTrendLine(type)
492     {
493         return type && type.parameterList ? type.parameterList.map(function (parameter) { return parameter.value; }) : [];
494     }
495
496     _trendLineParameterDidChange(event)
497     {
498         var input = event.target;
499         var index = input.parameterIndex;
500         var newValue = parseFloat(input.value);
501         if (this._trendLineParameters[index] == newValue)
502             return;
503         this._trendLineParameters[index] = newValue;
504         var self = this;
505         setTimeout(function () { // Some trend lines, e.g. sementations, are expensive.
506             if (self._trendLineParameters[index] != newValue)
507                 return;
508             self._updateTrendLine();
509             self._chartsPage.graphOptionsDidChange();
510         }, 500);
511     }
512
513     _didFetchData()
514     {
515         super._didFetchData();
516         this._updateTrendLine();
517     }
518
519     async _updateTrendLine()
520     {
521         if (!this._mainChart.sourceList())
522             return;
523
524         this._trendLineVersion++;
525         var currentTrendLineType = this._trendLineType || ChartTrendLineTypes.DefaultType;
526         var currentTrendLineParameters = this._trendLineParameters || this._defaultParametersForTrendLine(currentTrendLineType);
527         var currentTrendLineVersion = this._trendLineVersion;
528         var sourceList = this._mainChart.sourceList();
529
530         if (!currentTrendLineType.execute) {
531             this._mainChart.clearTrendLines();
532             this.enqueueToRender();
533         } else {
534             // Wait for all trendlines to be ready. Otherwise we might see FOC when the domain is expanded.
535             await Promise.all(sourceList.map(async (source, sourceIndex) => {
536                 const trendlineSeries = await currentTrendLineType.execute.call(null, source, currentTrendLineParameters);
537                 if (this._trendLineVersion == currentTrendLineVersion)
538                     this._mainChart.setTrendLine(sourceIndex, trendlineSeries);
539
540                 if (trendlineSeries && trendlineSeries.analysisAnnotations)
541                     this._detectedAnnotations = trendlineSeries.analysisAnnotations;
542                 else
543                     this._detectedAnnotations = null;
544             }));
545             this.enqueueToRender();
546         }
547     }
548
549     static paneHeaderTemplate()
550     {
551         return `
552             <header class="chart-pane-header">
553                 <h2 class="chart-pane-title">-</h2>
554                 <nav class="chart-pane-actions">
555                     <ul>
556                         <li><close-button id="close"></close-button></li>
557                     </ul>
558                     <ul class="chart-pane-action-buttons buttoned-toolbar"></ul>
559                     <ul class="chart-pane-alternative-platforms popover" style="display:none"></ul>
560                     <form class="chart-pane-analyze-popover popover" style="display:none">
561                         <input type="text" required>
562                         <button>Create</button>
563                     </form>
564                     <ul class="chart-pane-filtering-options popover" style="display:none">
565                         <li><label><input type="checkbox" class="enable-sampling">Sampling</label></li>
566                         <li><label><input type="checkbox" class="show-outliers">Show outliers</label></li>
567                         <li><button class="mark-as-outlier">Mark selected points as outlier</button></li>
568                     </ul>
569                     <ul class="chart-pane-trend-line-options popover" style="display:none">
570                         <div class="trend-line-types"></div>
571                         <div class="trend-line-parameter-list"></div>
572                     </ul>
573                 </nav>
574             </header>
575         `;
576     }
577
578     static cssTemplate()
579     {
580         return ChartPaneBase.cssTemplate() + `
581             .chart-pane {
582                 border: solid 1px #ccc;
583                 border-radius: 0.5rem;
584                 margin: 1rem;
585                 margin-bottom: 2rem;
586             }
587
588             .chart-pane-body {
589                 height: calc(100% - 2rem);
590             }
591
592             .chart-pane-header {
593                 position: relative;
594                 left: 0;
595                 top: 0;
596                 width: 100%;
597                 height: 2rem;
598                 line-height: 2rem;
599                 border-bottom: solid 1px #ccc;
600             }
601
602             .chart-pane-title {
603                 margin: 0 0.5rem;
604                 padding: 0;
605                 padding-left: 1.5rem;
606                 font-size: 1rem;
607                 font-weight: inherit;
608             }
609
610             .chart-pane-actions {
611                 position: absolute;
612                 display: flex;
613                 flex-direction: row;
614                 justify-content: space-between;
615                 align-items: center;
616                 width: 100%;
617                 height: 2rem;
618                 top: 0;
619                 padding: 0 0;
620             }
621
622             .chart-pane-actions ul {
623                 display: block;
624                 padding: 0;
625                 margin: 0 0.5rem;
626                 font-size: 1rem;
627                 line-height: 1rem;
628                 list-style: none;
629             }
630
631             .chart-pane-actions .chart-pane-action-buttons {
632                 font-size: 0.9rem;
633                 line-height: 0.9rem;
634             }
635
636             .chart-pane-actions .popover {
637                 position: absolute;
638                 top: 0;
639                 right: 0;
640                 border: solid 1px #ccc;
641                 border-radius: 0.2rem;
642                 z-index: 10;
643                 padding: 0.2rem 0;
644                 margin: 0;
645                 margin-top: -0.2rem;
646                 margin-right: -0.2rem;
647                 background: rgba(255, 255, 255, 0.95);
648             }
649
650             @supports ( -webkit-backdrop-filter: blur(0.5rem) ) {
651                 .chart-pane-actions .popover {
652                     background: rgba(255, 255, 255, 0.6);
653                     -webkit-backdrop-filter: blur(0.5rem);
654                 }
655             }
656
657             .chart-pane-actions .popover li {
658             }
659
660             .chart-pane-actions .popover li a {
661                 display: block;
662                 text-decoration: none;
663                 color: inherit;
664                 font-size: 0.9rem;
665                 padding: 0.2rem 0.5rem;
666             }
667
668             .chart-pane-actions .popover a:hover,
669             .chart-pane-actions .popover input:focus {
670                 background: rgba(204, 153, 51, 0.1);
671             }
672
673             .chart-pane-actions .chart-pane-analyze-popover {
674                 padding: 0.5rem;
675             }
676
677             .chart-pane-actions .popover label {
678                 font-size: 0.9rem;
679             }
680
681             .chart-pane-actions .popover.chart-pane-filtering-options {
682                 padding: 0.2rem;
683             }
684
685             .chart-pane-actions .popover.chart-pane-trend-line-options h3 {
686                 font-size: 0.9rem;
687                 line-height: 0.9rem;
688                 font-weight: inherit;
689                 margin: 0;
690                 padding: 0.2rem;
691                 border-bottom: solid 1px #ccc;
692             }
693
694             .chart-pane-actions .popover.chart-pane-trend-line-options select,
695             .chart-pane-actions .popover.chart-pane-trend-line-options label {
696                 margin: 0.2rem;
697             }
698
699             .chart-pane-actions .popover.chart-pane-trend-line-options label {
700                 font-size: 0.8rem;
701             }
702
703             .chart-pane-actions .popover.chart-pane-trend-line-options input {
704                 width: 2.5rem;
705             }
706
707             .chart-pane-actions .popover input[type=text] {
708                 font-size: 1rem;
709                 width: 15rem;
710                 outline: none;
711                 border: solid 1px #ccc;
712             }
713 `;
714     }
715 }