New perf dashboard shouldn't always show outliners
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 11 Feb 2015 02:45:43 +0000 (02:45 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 11 Feb 2015 02:45:43 +0000 (02:45 +0000)
https://bugs.webkit.org/show_bug.cgi?id=141445

Reviewed by Chris Dumez.

Use the simple moving average with an average difference envelope to compute the y-axis range to show
to avoid expanding it spuriously to show one off outlier.

* public/v2/app.js:
(App.Pane): Don't show the full y-axis range by default.
(App.Pane._computeChartData): Use the first strategies for the moving average and the enveloping if
one is not specified by the user but without showing them in the charts.
(App.Pane._computeMovingAverage): Takes moving average and enveloping strategies as arguments instead
of retrieving via chosenMovingAverageStrategy and chosenEnvelopingStrategy.

(App.ChartsController._parsePaneList): Added showFullYAxis as a query string argument to each pane.
(App.ChartsController._serializePaneList): Ditto.

* public/v2/chart-pane.css: Added a CSS rule for when y-axis is clickable.

* public/v2/index.html: Pass in showFullYAxis as an argument to the main interactive chart.

* public/v2/interactive-chart.js:
(App.InteractiveChartComponent._constructGraphIfPossible): Add an event listener on y-axis labels when
the chart is interactive so that toggle showFullYAxis. Also hide the moving average and/or the envelope
if they are not specified by the user (i.e. only used to adjust y-axis range).
(App.InteractiveChartComponent._updateDomain): Don't exit early if y-axis domains are different even if
x-axis domain remained the same. Without this change, the charts would never redraw.
(App.InteractiveChartComponent._minMaxForAllTimeSeries): Use the moving average instead of the current
time series to compute the y-axis range if showFullYAxis is false. When showFullYAxis is true, expand
y-axis all the way down to 0 or the minimum value in the current time series whichever is smaller.

* public/v2/js/statistics.js:
(Statistics.MovingAverageStrategies): Use a wider window in Simple Moving Average by default.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@179913 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Websites/perf.webkit.org/ChangeLog
Websites/perf.webkit.org/public/v2/app.js
Websites/perf.webkit.org/public/v2/chart-pane.css
Websites/perf.webkit.org/public/v2/index.html
Websites/perf.webkit.org/public/v2/interactive-chart.js
Websites/perf.webkit.org/public/v2/js/statistics.js

index 96b65d7..3990430 100644 (file)
@@ -1,5 +1,42 @@
 2015-02-10  Ryosuke Niwa  <rniwa@webkit.org>
 
+        New perf dashboard shouldn't always show outliners
+        https://bugs.webkit.org/show_bug.cgi?id=141445
+
+        Reviewed by Chris Dumez.
+
+        Use the simple moving average with an average difference envelope to compute the y-axis range to show
+        to avoid expanding it spuriously to show one off outlier.
+
+        * public/v2/app.js:
+        (App.Pane): Don't show the full y-axis range by default.
+        (App.Pane._computeChartData): Use the first strategies for the moving average and the enveloping if
+        one is not specified by the user but without showing them in the charts.
+        (App.Pane._computeMovingAverage): Takes moving average and enveloping strategies as arguments instead
+        of retrieving via chosenMovingAverageStrategy and chosenEnvelopingStrategy.
+
+        (App.ChartsController._parsePaneList): Added showFullYAxis as a query string argument to each pane.
+        (App.ChartsController._serializePaneList): Ditto.
+
+        * public/v2/chart-pane.css: Added a CSS rule for when y-axis is clickable.
+
+        * public/v2/index.html: Pass in showFullYAxis as an argument to the main interactive chart.
+
+        * public/v2/interactive-chart.js:
+        (App.InteractiveChartComponent._constructGraphIfPossible): Add an event listener on y-axis labels when
+        the chart is interactive so that toggle showFullYAxis. Also hide the moving average and/or the envelope
+        if they are not specified by the user (i.e. only used to adjust y-axis range).
+        (App.InteractiveChartComponent._updateDomain): Don't exit early if y-axis domains are different even if
+        x-axis domain remained the same. Without this change, the charts would never redraw.
+        (App.InteractiveChartComponent._minMaxForAllTimeSeries): Use the moving average instead of the current
+        time series to compute the y-axis range if showFullYAxis is false. When showFullYAxis is true, expand
+        y-axis all the way down to 0 or the minimum value in the current time series whichever is smaller.
+
+        * public/v2/js/statistics.js:
+        (Statistics.MovingAverageStrategies): Use a wider window in Simple Moving Average by default.
+
+2015-02-10  Ryosuke Niwa  <rniwa@webkit.org>
+
         Unreviewed build fix.
 
         * public/v2/app.js:
index d313aea..f80e5e4 100755 (executable)
@@ -297,6 +297,7 @@ App.Pane = Ember.Object.extend({
     metricId: null,
     metric: null,
     selectedItem: null,
+    showFullYAxis: false,
     searchCommit: function (repository, keyword) {
         var self = this;
         var repositoryId = repository.get('id');
@@ -476,30 +477,36 @@ App.Pane = Ember.Object.extend({
             return;
 
         var chartData = App.createChartData(this.get('fetchedData'));
-        chartData.movingAverage = this._computeMovingAverage(chartData);
 
-        this._updateStrategyConfigIfNeeded(this.get('chosenMovingAverageStrategy'), 'movingAverageConfig');
-        this._updateStrategyConfigIfNeeded(this.get('chosenEnvelopingStrategy'), 'envelopingConfig');
+        var movingAverageStrategy = this.get('chosenMovingAverageStrategy');
+        this._updateStrategyConfigIfNeeded(movingAverageStrategy, 'movingAverageConfig');
+
+        var envelopingStrategy = this.get('chosenEnvelopingStrategy');
+        this._updateStrategyConfigIfNeeded(envelopingStrategy, 'envelopingConfig');
+
+        if (!movingAverageStrategy || !movingAverageStrategy.execute) {
+            movingAverageStrategy = Statistics.MovingAverageStrategies[0];
+            chartData.hideMovingAverage = true;
+        }
+        if (!envelopingStrategy || !envelopingStrategy.execute) {
+            envelopingStrategy = Statistics.EnvelopingStrategies[0];
+            chartData.hideEnvelope = true;
+        }
+
+        chartData.movingAverage = this._computeMovingAverage(chartData, movingAverageStrategy, envelopingStrategy);
 
         this.set('chartData', chartData);
     }.observes('chosenMovingAverageStrategy', 'chosenMovingAverageStrategy.parameterList.@each.value',
         'chosenEnvelopingStrategy', 'chosenEnvelopingStrategy.parameterList.@each.value'),
-    _computeMovingAverage: function (chartData)
+    _computeMovingAverage: function (chartData, movingAverageStrategy, envelopingStrategy)
     {
         var currentTimeSeriesData = chartData.current.series();
-        var movingAverageStrategy = this.get('chosenMovingAverageStrategy');
-        if (!movingAverageStrategy || !movingAverageStrategy.execute)
-            return null;
-
         var movingAverageValues = this._executeStrategy(movingAverageStrategy, currentTimeSeriesData);
         if (!movingAverageValues)
             return null;
 
-        var envelopeDelta = null;
-        var envelopingStrategy = this.get('chosenEnvelopingStrategy');
-        if (envelopingStrategy && envelopingStrategy.execute)
-            envelopeDelta = this._executeStrategy(envelopingStrategy, currentTimeSeriesData, [movingAverageValues]);
-        
+        var envelopeDelta = this._executeStrategy(envelopingStrategy, currentTimeSeriesData, [movingAverageValues]);
+
         return new TimeSeries(currentTimeSeriesData.map(function (point, index) {
             var value = movingAverageValues[index];
             return {
@@ -671,8 +678,9 @@ App.ChartsController = Ember.Controller.extend({
                 metricId: paneInfo[1],
                 selectedItem: selectedItem,
                 timeRange: timeRange,
-                movingAverageConfig: paneInfo[3],
-                envelopingConfig: paneInfo[4],
+                showFullYAxis: paneInfo[3],
+                movingAverageConfig: paneInfo[4],
+                envelopingConfig: paneInfo[5],
             });
         });
     },
@@ -687,6 +695,7 @@ App.ChartsController = Ember.Controller.extend({
                 pane.get('platformId'),
                 pane.get('metricId'),
                 pane.get('timeRange') ? pane.get('timeRange').map(function (date) { return date.getTime() }) : pane.get('selectedItem'),
+                pane.get('showFullYAxis'),
                 pane.get('movingAverageConfig'),
                 pane.get('envelopingConfig'),
             ];
@@ -697,7 +706,7 @@ App.ChartsController = Ember.Controller.extend({
     {
         Ember.run.debounce(this, '_updateQueryString', 1000);
     }.observes('sharedZoom', 'panes.@each.platform', 'panes.@each.metric', 'panes.@each.selectedItem', 'panes.@each.timeRange',
-        'panes.@each.movingAverageConfig', 'panes.@each.envelopingConfig'),
+        'panes.@each.showFullYAxis', 'panes.@each.movingAverageConfig', 'panes.@each.envelopingConfig'),
 
     _updateQueryString: function ()
     {
index 04a9654..3f97063 100755 (executable)
     stroke: none;
 }
 
+.chart .axis.interactive text {
+    cursor: pointer;
+}
+
 .chart .rangeBar {
     display: block;
     background-color: #fc6;
index 394aa93..ced6008 100755 (executable)
                             selection=timeRange
                             selectedPoints=selectedPoints
                             markedPoints=markedPoints
+                            showFullYAxis=showFullYAxis
                             zoom="zoomed"}}
                     {{else}}
                         {{#if failure}}
index 9165b57..6887169 100644 (file)
@@ -76,10 +76,15 @@ App.InteractiveChartComponent = Ember.Component.extend({
                 .attr("class", "x axis");
         }
 
+        var isInteractive = this.get('interactive');
         if (this.get('showYAxis')) {
             this._yAxis = d3.svg.axis().scale(this._y).orient("left").ticks(6).tickFormat(chartData.formatter);
-            this._yAxisLabels = svg.append("g")
-                .attr("class", "y axis");
+            
+            this._yAxisLabels = svg.append('g').attr('class', 'y axis' + (isInteractive ? ' interactive' : ''));
+            if (isInteractive) {
+                var self = this;
+                this._yAxisLabels.on('click', function () { self.toggleProperty('showFullYAxis'); });
+            }
         }
 
         this._clippedContainer = svg.append("g")
@@ -123,7 +128,8 @@ App.InteractiveChartComponent = Ember.Component.extend({
                 .attr("class", "target"));
         }
 
-        var foregroundClass = this._movingAverageTimeSeries ? '' : ' foreground';
+        var movingAverageIsVisible = this._movingAverageTimeSeries && !chartData.hideMovingAverage;
+        var foregroundClass = movingAverageIsVisible ? '' : ' foreground';
         this._areas.push(this._clippedContainer
             .append("path")
             .datum(this._currentTimeSeriesData)
@@ -141,18 +147,21 @@ App.InteractiveChartComponent = Ember.Component.extend({
                 .attr("class", "dot" + foregroundClass)
                 .attr("r", this.get('chartPointRadius') || 1));
 
-        if (this._movingAverageTimeSeries) {
+        if (movingAverageIsVisible) {
             this._paths.push(this._clippedContainer
                 .append("path")
                 .datum(this._movingAverageTimeSeries.series())
                 .attr("class", "movingAverage"));
-            this._areas.push(this._clippedContainer
-                .append("path")
-                .datum(this._movingAverageTimeSeries.series())
-                .attr("class", "envelope"));
+
+            if (!chartData.hideEnvelope) {
+                this._areas.push(this._clippedContainer
+                    .append("path")
+                    .datum(this._movingAverageTimeSeries.series())
+                    .attr("class", "envelope"));
+            }
         }
 
-        if (this.get('interactive')) {
+        if (isInteractive) {
             this._currentItemLine = this._clippedContainer
                 .append("line")
                 .attr("class", "current-item");
@@ -193,11 +202,14 @@ App.InteractiveChartComponent = Ember.Component.extend({
         var intrinsicXDomain = this._computeXAxisDomain(this._currentTimeSeriesData);
         if (!xDomain)
             xDomain = intrinsicXDomain;
-        var currentDomain = this._x.domain();
-        if (currentDomain && App.domainsAreEqual(currentDomain, xDomain))
+        var yDomain = this._computeYAxisDomain(xDomain[0], xDomain[1]);
+
+        var currentXDomain = this._x.domain();
+        var currentYDomain = this._y.domain();
+        if (currentXDomain && App.domainsAreEqual(currentXDomain, xDomain)
+            && currentYDomain && App.domainsAreEqual(currentYDomain, yDomain))
             return currentDomain;
 
-        var yDomain = this._computeYAxisDomain(xDomain[0], xDomain[1]);
         this._x.domain(xDomain);
         this._y.domain(yDomain);
         return xDomain;
@@ -339,13 +351,17 @@ App.InteractiveChartComponent = Ember.Component.extend({
     },
     _minMaxForAllTimeSeries: function (startTime, endTime)
     {
-        var currentRange = this._currentTimeSeries.minMaxForTimeRange(startTime, endTime);
+        var shouldShowFullYAxis = this.get('showFullYAxis');
+        var mainTimeSeries = this._movingAverageTimeSeries && !shouldShowFullYAxis ? this._movingAverageTimeSeries : this._currentTimeSeries;
+        var currentRange = mainTimeSeries.minMaxForTimeRange(startTime, endTime);
+        if (shouldShowFullYAxis)
+            currentRange[0] = Math.min(0, currentRange[0]);
+
         var baselineRange = this._baselineTimeSeries ? this._baselineTimeSeries.minMaxForTimeRange(startTime, endTime) : [Number.MAX_VALUE, Number.MIN_VALUE];
         var targetRange = this._targetTimeSeries ? this._targetTimeSeries.minMaxForTimeRange(startTime, endTime) : [Number.MAX_VALUE, Number.MIN_VALUE];
-        var movingAverageRange = this._movingAverageTimeSeries ? this._movingAverageTimeSeries.minMaxForTimeRange(startTime, endTime) : [Number.MAX_VALUE, Number.MIN_VALUE];
         return [
-            Math.min(currentRange[0], baselineRange[0], targetRange[0], movingAverageRange[0]),
-            Math.max(currentRange[1], baselineRange[1], targetRange[1], movingAverageRange[1]),
+            Math.min(currentRange[0], baselineRange[0], targetRange[0]),
+            Math.max(currentRange[1], baselineRange[1], targetRange[1]),
         ];
     },
     _currentSelection: function ()
@@ -361,7 +377,7 @@ App.InteractiveChartComponent = Ember.Component.extend({
             selection = null; // Otherwise the user has no way of clearing the selection.
 
         this._relayoutDataAndAxes(selection);
-    }.observes('domain'),
+    }.observes('domain', 'showFullYAxis'),
     _selectionChanged: function ()
     {
         this._updateSelection(this.get('selection'));
index 15fd9c8..10ffd3f 100755 (executable)
@@ -104,8 +104,8 @@ var Statistics = new (function () {
             id: 1,
             label: 'Simple Moving Average',
             parameterList: [
-                {label: "Backward window size", value: 5, min: 2, step: 1},
-                {label: "Forward window size", value: 3, min: 0, step: 1}
+                {label: "Backward window size", value: 8, min: 2, step: 1},
+                {label: "Forward window size", value: 4, min: 0, step: 1}
             ],
             execute: function (backwardWindowSize, forwardWindowSize, values) {
                 var averages = new Array(values.length);