Perf dashboard should automatically select ranges for A/B testing
[WebKit-https.git] / Websites / perf.webkit.org / public / v2 / interactive-chart.js
index 9165b5704d038f24f266262fafa8f58ff10a22d1..6eaeac3dca44cd76cf604f64faf8cc0ced62670e 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;
+        var foregroundClass = movingAverageIsVisible ? '' : ' foreground';
         this._areas.push(this._clippedContainer
             .append("path")
             .datum(this._currentTimeSeriesData)
@@ -141,18 +147,19 @@ 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 (this.get('interactive')) {
+        if (isInteractive) {
             this._currentItemLine = this._clippedContainer
                 .append("line")
                 .attr("class", "current-item");
@@ -190,14 +197,19 @@ App.InteractiveChartComponent = Ember.Component.extend({
     _updateDomain: function ()
     {
         var xDomain = this.get('domain');
+        if (!xDomain || !this._currentTimeSeriesData)
+            return null;
         var intrinsicXDomain = this._computeXAxisDomain(this._currentTimeSeriesData);
         if (!xDomain)
             xDomain = intrinsicXDomain;
-        var currentDomain = this._x.domain();
-        if (currentDomain && App.domainsAreEqual(currentDomain, xDomain))
-            return currentDomain;
-
         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 currentXDomain;
+
         this._x.domain(xDomain);
         this._y.domain(yDomain);
         return xDomain;
@@ -226,8 +238,8 @@ App.InteractiveChartComponent = Ember.Component.extend({
             margin.left += 3 * rem;
 
         this._margin = margin;
-        this._contentWidth = this._totalWidth - margin.left - margin.right;
-        this._contentHeight = this._totalHeight - margin.top - margin.bottom;
+        this._contentWidth = Math.max(0, this._totalWidth - margin.left - margin.right);
+        this._contentHeight = Math.max(0, this._totalHeight - margin.top - margin.bottom);
 
         this._svg.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
 
@@ -239,12 +251,15 @@ App.InteractiveChartComponent = Ember.Component.extend({
         this._y.range([this._contentHeight, 0]);
 
         if (this._xAxis) {
+            this._xAxis.ticks(Math.round(this._contentWidth / 4 / rem));
             this._xAxis.tickSize(-this._contentHeight);
             this._xAxisLabels.attr("transform", "translate(0," + this._contentHeight + ")");
         }
 
-        if (this._yAxis)
+        if (this._yAxis) {
+            this._yAxis.ticks(Math.round(this._contentHeight / 2 / rem));
             this._yAxis.tickSize(-this._contentWidth);
+        }
 
         if (this._currentItemLine) {
             this._currentItemLine
@@ -260,7 +275,7 @@ App.InteractiveChartComponent = Ember.Component.extend({
             .call(this._brush)
         .selectAll("rect")
             .attr("y", 1)
-            .attr("height", this._contentHeight - 2);
+            .attr("height", Math.max(0, this._contentHeight - 2));
         this._updateSelectionToolbar();
     },
     _relayoutDataAndAxes: function (selection)
@@ -324,29 +339,43 @@ App.InteractiveChartComponent = Ember.Component.extend({
     },
     _computeYAxisDomain: function (startTime, endTime)
     {
-        var range = this._minMaxForAllTimeSeries(startTime, endTime);
+        var shouldShowFullYAxis = this.get('showFullYAxis');
+        var range = this._minMaxForAllTimeSeries(startTime, endTime, !shouldShowFullYAxis);
         var min = range[0];
         var max = range[1];
+
+        var highlightedItems = this.get('highlightedItems');
+        if (highlightedItems) {
+            var data = this._currentTimeSeriesData
+                .filter(function (point) { return startTime <= point.time && point.time <= endTime && highlightedItems[point.measurement.id()]; })
+                .map(function (point) { return point.value });
+            min = Math.min(min, Statistics.min(data));
+            max = Math.max(max, Statistics.max(data));
+        }
+
         if (max < min)
             min = max = 0;
+        else if (shouldShowFullYAxis)
+            min = Math.min(min, 0);
         var diff = max - min;
         var margin = diff * 0.05;
 
         yExtent = [min - margin, max + margin];
-//        if (yMin !== undefined)
-//            yExtent[0] = parseInt(yMin);
         return yExtent;
     },
-    _minMaxForAllTimeSeries: function (startTime, endTime)
+    _minMaxForAllTimeSeries: function (startTime, endTime, ignoreOutliners)
     {
-        var currentRange = this._currentTimeSeries.minMaxForTimeRange(startTime, endTime);
-        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]),
-        ];
+        var seriesList = [this._currentTimeSeries, this._movingAverageTimeSeries, this._baselineTimeSeries, this._targetTimeSeries];
+        var min = Infinity;
+        var max = -Infinity;
+        for (var i = 0; i < seriesList.length; i++) {
+            if (seriesList[i]) {
+                var minMax = seriesList[i].minMaxForTimeRange(startTime, endTime, ignoreOutliners);
+                min = Math.min(min, minMax[0]);
+                max = Math.max(max, minMax[1]);
+            }
+        }
+        return [min, max];
     },
     _currentSelection: function ()
     {
@@ -356,12 +385,14 @@ App.InteractiveChartComponent = Ember.Component.extend({
     {
         var selection = this._currentSelection() || this.get('sharedSelection');
         var newXDomain = this._updateDomain();
+        if (!newXDomain)
+            return;
 
         if (selection && newXDomain && selection[0] <= newXDomain[0] && newXDomain[1] <= selection[1])
             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'));
@@ -493,9 +524,6 @@ App.InteractiveChartComponent = Ember.Component.extend({
             var yDiff = mY - point.y;
             return xDiff * xDiff + yDiff * yDiff / 16; // Favor horizontal affinity.
         };
-        distanceHeuristics = function (m) {
-            return Math.abs(xScale(m.time) - point.x);
-        }
 
         var newItemIndex;
         if (point && !this._currentSelection()) {
@@ -594,7 +622,7 @@ App.InteractiveChartComponent = Ember.Component.extend({
                 .attr("class", "highlight")
                 .attr("r", (this.get('chartPointRadius') || 1) * 1.8);
 
-        this._updateHighlightPositions();
+        this._domainChanged();
     }.observes('highlightedItems'),
     _rangesChanged: function ()
     {
@@ -625,6 +653,7 @@ App.InteractiveChartComponent = Ember.Component.extend({
                 linkRoute: linkRoute,
                 linkId: range.get('id'),
                 label: range.get('label'),
+                status: range.get('status'),
             });
         }));
 
@@ -736,7 +765,7 @@ App.InteractiveChartComponent = Ember.Component.extend({
     },
     _updateSelectionToolbar: function ()
     {
-        if (!this.get('interactive'))
+        if (!this.get('zoomable'))
             return;
 
         var selection = this._currentSelection();