Chart status should always be computed against prior values
[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
57                 if (data && data.length > 1) {
58                     this._usedSelection = selection;
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 baselinePoint = this._findLastPointPriorToTime(currentPoint, baselineTimeSeriesData);
116         var diffFromBaseline = baselinePoint !== undefined ? (currentPoint.value - baselinePoint.value) / baselinePoint.value : undefined;
117
118         var targetPoint = this._findLastPointPriorToTime(currentPoint, targetTimeSeriesData);
119         var diffFromTarget = targetPoint !== undefined ? (currentPoint.value - targetPoint.value) / targetPoint.value : undefined;
120
121         var label = '';
122         var className = '';
123
124         function labelForDiff(diff, referencePoint, name, comparison)
125         {
126             var relativeDiff = Math.abs(diff * 100).toFixed(1);
127             var referenceValue = referencePoint ? ` (${formatter(referencePoint.value)})` : '';
128             return `${relativeDiff}% ${comparison} ${name}${referenceValue}`;
129         }
130
131         var smallerIsBetter = metric.isSmallerBetter();
132         if (diffFromBaseline !== undefined && diffFromTarget !== undefined) {
133             if (diffFromBaseline > 0 == smallerIsBetter) {
134                 label = labelForDiff(diffFromBaseline, baselinePoint, 'baseline', 'worse than');
135                 className = 'worse';
136             } else if (diffFromTarget < 0 == smallerIsBetter) {
137                 label = labelForDiff(diffFromTarget, targetPoint, 'target', 'better than');
138                 className = 'better';
139             } else
140                 label = labelForDiff(diffFromTarget, targetPoint, 'target', 'until');
141         } else if (diffFromBaseline !== undefined) {
142             className = diffFromBaseline > 0 == smallerIsBetter ? 'worse' : 'better';
143             label = labelForDiff(diffFromBaseline, baselinePoint, 'baseline', className + ' than');
144         } else if (diffFromTarget !== undefined) {
145             className = diffFromTarget < 0 == smallerIsBetter ? 'better' : 'worse';
146             label = labelForDiff(diffFromTarget, targetPoint, 'target', className + ' than');
147         }
148
149         var valueDelta = null;
150         var relativeDelta = null;
151         if (previousPoint) {
152             valueDelta = deltaFormatter(currentPoint.value - previousPoint.value);
153             var relativeDelta = (currentPoint.value - previousPoint.value) / previousPoint.value;
154             relativeDelta = (relativeDelta * 100).toFixed(0) + '%';
155         }
156         return {
157             className: className,
158             label: label,
159             currentValue: formatter(currentPoint.value),
160             valueDelta: valueDelta,
161             relativeDelta: relativeDelta,
162         };
163     }
164
165     _findLastPointPriorToTime(currentPoint, timeSeriesData)
166     {
167         if (!currentPoint || !timeSeriesData || !timeSeriesData.length)
168             return undefined;
169
170         var i = 0;
171         while (i < timeSeriesData.length - 1 && timeSeriesData[i + 1].time < currentPoint.time)
172             i++;
173         return timeSeriesData[i];
174     }
175
176     static htmlTemplate()
177     {
178         return `
179             <div>
180                 <span class="chart-status-current-value"></span>
181                 <span class="chart-status-comparison"></span>
182             </div>`;
183     }
184
185     static cssTemplate()
186     {
187         return `
188             .chart-status-current-value {
189                 padding-right: 0.5rem;
190             }
191
192             .chart-status-comparison.worse {
193                 color: #c33;
194             }
195
196             .chart-status-comparison.better {
197                 color: #33c;
198             }`;
199     }
200 }