The chart status on v3 UI sometimes show wrong revision ranges
[WebKit.git] / Websites / perf.webkit.org / public / v3 / components / chart-status-view.js
1
2 class ChartStatusView extends ComponentBase {
3
4     constructor(metric, chart)
5     {
6         super('chart-status');
7         this._metric = metric;
8         this._chart = chart;
9
10         this._usedSelection = null;
11         this._usedCurrentPoint = null;
12         this._usedPreviousPoint = null;
13
14         this._currentValue = null;
15         this._comparisonClass = null;
16         this._comparisonLabel = null;
17
18         this._renderedCurrentValue = null;
19         this._renderedComparisonClass = null;
20         this._renderedComparisonLabel = null;
21     }
22
23     render()
24     {
25         this.updateStatusIfNeeded();
26
27         if (this._renderedCurrentValue == this._currentValue
28             && this._renderedComparisonClass == this._comparisonClass
29             && this._renderedComparisonLabel == this._comparisonLabel)
30             return;
31
32         this._renderedCurrentValue = this._currentValue;
33         this._renderedComparisonClass = this._comparisonClass;
34         this._renderedComparisonLabel = this._comparisonLabel;
35
36         this.content().querySelector('.chart-status-current-value').textContent = this._currentValue || '';
37         var comparison = this.content().querySelector('.chart-status-comparison');
38         comparison.className = 'chart-status-comparison ' + (this._comparisonClass || '');
39         comparison.textContent = this._comparisonLabel;
40     }
41
42     updateStatusIfNeeded()
43     {
44         var currentPoint;
45         var previousPoint;
46
47         if (this._chart instanceof InteractiveTimeSeriesChart) {
48             var selection = this._chart.currentSelection();
49             if (selection && this._usedSelection == selection)
50                 return false;
51
52             if (selection) {
53                 var data = this._chart.sampledDataBetween('current', selection[0], selection[1]);
54                 if (!data)
55                     return false;
56                 this._usedSelection = selection;
57
58                 if (data && data.length > 1) {
59                     currentPoint = data[data.length - 1];
60                     previousPoint = data[0];
61                 }
62             } else  {
63                 currentPoint = this._chart.currentPoint();
64                 previousPoint = this._chart.currentPoint(-1);
65             }
66         } else {
67             var data = this._chart.sampledTimeSeriesData('current');
68             if (!data)
69                 return false;
70             if (data.length)
71                 currentPoint = data[data.length - 1];
72         }
73
74         if (currentPoint == this._usedCurrentPoint && previousPoint == this._usedPreviousPoint)
75             return false;
76
77         this._usedCurrentPoint = currentPoint;
78         this._usedPreviousPoint = previousPoint;
79
80         this.computeChartStatusLabels(currentPoint, previousPoint);
81
82         return true;
83     }
84
85     computeChartStatusLabels(currentPoint, previousPoint)
86     {
87         var status = currentPoint ? this._computeChartStatus(this._metric, this._chart, currentPoint, previousPoint) : null;
88         if (status) {
89             this._currentValue = status.currentValue;
90             if (previousPoint)
91                 this._currentValue += ` (${status.valueDelta} / ${status.relativeDelta})`;
92             this._comparisonClass = status.className;
93             this._comparisonLabel = status.label;
94         } else {
95             this._currentValue = null;
96             this._comparisonClass = null;
97             this._comparisonLabel = null;
98         }
99     }
100
101     _computeChartStatus(metric, chart, currentPoint, previousPoint)
102     {
103         var currentTimeSeriesData = chart.sampledTimeSeriesData('current');
104         var baselineTimeSeriesData = chart.sampledTimeSeriesData('baseline');
105         var targetTimeSeriesData = chart.sampledTimeSeriesData('target');
106         if (!currentTimeSeriesData)
107             return null;
108
109         var formatter = metric.makeFormatter(3);
110         var deltaFormatter = metric.makeFormatter(2, true);
111
112         if (!currentPoint)
113             currentPoint = currentTimeSeriesData[currentTimeSeriesData.length - 1];
114
115         var diffFromBaseline = this._relativeDifferenceToLaterPointInTimeSeries(currentPoint, baselineTimeSeriesData);
116         var diffFromTarget = this._relativeDifferenceToLaterPointInTimeSeries(currentPoint, targetTimeSeriesData);
117
118         var label = '';
119         var className = '';
120
121         function labelForDiff(diff, name, comparison)
122         {
123             return Math.abs(diff * 100).toFixed(1) + '% ' + comparison + ' ' + name;
124         }
125
126         var smallerIsBetter = metric.isSmallerBetter();
127         if (diffFromBaseline !== undefined && diffFromTarget !== undefined) {
128             if (diffFromBaseline > 0 == smallerIsBetter) {
129                 label = labelForDiff(diffFromBaseline, 'baseline', 'worse than');
130                 className = 'worse';
131             } else if (diffFromTarget < 0 == smallerIsBetter) {
132                 label = labelForDiff(diffFromBaseline, 'target', 'better than');
133                 className = 'better';
134             } else
135                 label = labelForDiff(diffFromTarget, 'target', 'until');
136         } else if (diffFromBaseline !== undefined) {
137             className = diffFromBaseline > 0 == smallerIsBetter ? 'worse' : 'better';
138             label = labelForDiff(diffFromBaseline, 'baseline', className + ' than');
139         } else if (diffFromTarget !== undefined) {
140             className = diffFromTarget < 0 == smallerIsBetter ? 'better' : 'worse';
141             label = labelForDiff(diffFromTarget, 'target', className + ' than');
142         }
143
144         var valueDelta = null;
145         var relativeDelta = null;
146         if (previousPoint) {
147             valueDelta = deltaFormatter(currentPoint.value - previousPoint.value);
148             var relativeDelta = (currentPoint.value - previousPoint.value) / previousPoint.value;
149             relativeDelta = (relativeDelta * 100).toFixed(0) + '%';
150         }
151         return {
152             className: className,
153             label: label,
154             currentValue: formatter(currentPoint.value),
155             valueDelta: valueDelta,
156             relativeDelta: relativeDelta,
157         };
158     }
159
160     _relativeDifferenceToLaterPointInTimeSeries(currentPoint, timeSeriesData)
161     {
162         if (!currentPoint || !timeSeriesData || !timeSeriesData.length)
163             return undefined;
164
165         // FIXME: We shouldn't be using the first data point to access the time series object.
166         var referencePoint = timeSeriesData[0].series.findPointAfterTime(currentPoint.time);
167         if (!referencePoint)
168             return undefined;
169
170         return (currentPoint.value - referencePoint.value) / referencePoint.value;
171     }
172
173
174     static htmlTemplate()
175     {
176         return `
177             <div>
178                 <span class="chart-status-current-value"></span>
179                 <span class="chart-status-comparison"></span>
180             </div>`;
181     }
182
183     static cssTemplate()
184     {
185         return `
186             .chart-status-current-value {
187                 padding-right: 0.5rem;
188             }
189
190             .chart-status-comparison.worse {
191                 color: #c33;
192             }
193
194             .chart-status-comparison.better {
195                 color: #33c;
196             }`;
197     }
198 }