<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[179878] trunk/Websites/perf.webkit.org</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/179878">179878</a></dd>
<dt>Author</dt> <dd>rniwa@webkit.org</dd>
<dt>Date</dt> <dd>2015-02-10 13:39:41 -0800 (Tue, 10 Feb 2015)</dd>
</dl>

<h3>Log Message</h3>
<pre>New perf dashboard should have the ability to overlay moving average with an envelope
https://bugs.webkit.org/show_bug.cgi?id=141438

Reviewed by Andreas Kling.

This patch adds three kinds of moving average strategies and two kinds of enveloping strategies:

Simple Moving Average - The moving average x̄_i of x_i is computed as the arithmetic mean of values
from x_(i - n) though x_(i + m) where n is a non-negative integer and m is a positive integer. It takes
n, backward window size, and m, forward window size, as an argument.

Cumulative Moving Average - x̄_i is computed as the arithmetic mean of all values x_0 though x_i.
That is, x̄_1 = x_1 and x̄_i = ((i - 1) * M_(i - 1) + x_i) / i for all i &gt; 1.

Exponential Moving Average - x̄_i is computed as the weighted average of x_i and x̄_(i - 1) with α as
an argument specifying x_i's weight. To be precise, x̄_1 = x_1 and x̄_i = α * x_i + (α - 1) x̄_(i-1).


Average Difference - The enveloping delta d is computed as the arithmetic mean of the difference
between each x_i and x̄_i.

Moving Average Standard Deviation - d is computed like the standard deviation except the deviation
for each term is measured from the moving average instead of the sample arithmetic mean. i.e. it uses
the average of (x_i - x̄_i)^2 as the &quot;sample variance&quot; instead of the conventional (x_i - x̄)^2 where
x̄ is the sample mean of all x_i's. This change was necessary since our time series is non-stationary.


Each strategy is cloned for an App.Pane instance so that its parameterList can be configured per pane.
The configuration of the currently chosen strategies is saved in the query string for convenience.

Also added the &quot;stat pane&quot; to choose a moving average strategy and a enveloping strategy in each pane.

* public/v2/app.css: Specify the fill color for all SVG groups in the pane toolbar icons.

* public/v2/app.js:
(App.Pane._fetch): Delegate the creation of 'chartData' to _computeChartData.
(App.Pane.updateStatisticsTools): Added. Clones moving average and enveloping strategies for this pane.
(App.Pane._cloneStrategy): Added. Clones a strategy for a new pane.
(App.Pane._configureStrategy): Added. Finds and configures a strategy from the configuration retrieved
from the query string via ChartsController.
(App.Pane._computeChartData): Added. Creates chartData from fetchedData.
(App.Pane._computeMovingAverage): Added. Computes the moving average and the envelope.
(App.Pane._executeStrategy): Added.
(App.Pane._updateStrategyConfigIfNeeded): Pushes the strategy configurations to the query string via
ChartsController.
(App.ChartsController._parsePaneList): Merged the query string arguments for the range and point
selections, and added two new arguments for the moving average and the enveloping configurations.
(App.ChartsController._serializePaneList): Ditto.
(App.ChartsController._scheduleQueryStringUpdate): Observes strategy configurations.
(App.PaneController.actions.toggleBugsPane): Hides the stat pane.
(App.PaneController.actions.toggleSearchPane): Hides the stat pane.
(App.PaneController.actions.toggleStatPane): Added.

* public/v2/chart-pane.css: Added CSS rules for the new stat pane. Also added .foreground class for the
current (as opposed to baseline and target) time series for when it's the most foreground graph without
moving average and its envelope overlapping on top of it.

* public/v2/index.html: Added the templates for the stat pane and the corresponding icon (Σ).

* public/v2/interactive-chart.js:
(App.InteractiveChartComponent.chartDataDidChange): Unset _totalWidth and _totalHeight to avoid exiting
early inside _updateDimensionsIfNeeded when chartData changes after the initial layout.
(App.InteractiveChartComponent.didInsertElement): Attach event listeners here instead of inside
_constructGraphIfPossible since that could be called multiple times on the same SVG element.
(App.InteractiveChartComponent._constructGraphIfPossible): Clear down the old SVG element we created
but don't bother removing individual paths and circles. Added the code to show the moving average time
series when there is one. Also add &quot;foreground&quot; class on SVG elements for the current time series when
we're not showing the moving average. chart-pane.css has been updated to &quot;dim down&quot; the current time
series when &quot;foreground&quot; is not set.
(App.InteractiveChartComponent._minMaxForAllTimeSeries): Take the moving average time series into
account when computing the y-axis range.
(App.InteractiveChartComponent._brushChanged): Removed 'selectionIsLocked' argument as it's useless.

* public/v2/js/statistics.js:
(Statistics.MovingAverageStrategies): Added.
(Statistics.EnvelopingStrategies): Added.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkWebsitesperfwebkitorgChangeLog">trunk/Websites/perf.webkit.org/ChangeLog</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv2appcss">trunk/Websites/perf.webkit.org/public/v2/app.css</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv2appjs">trunk/Websites/perf.webkit.org/public/v2/app.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv2chartpanecss">trunk/Websites/perf.webkit.org/public/v2/chart-pane.css</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv2indexhtml">trunk/Websites/perf.webkit.org/public/v2/index.html</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv2interactivechartjs">trunk/Websites/perf.webkit.org/public/v2/interactive-chart.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv2jsstatisticsjs">trunk/Websites/perf.webkit.org/public/v2/js/statistics.js</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkWebsitesperfwebkitorgChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/ChangeLog (179877 => 179878)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/ChangeLog        2015-02-10 20:27:40 UTC (rev 179877)
+++ trunk/Websites/perf.webkit.org/ChangeLog        2015-02-10 21:39:41 UTC (rev 179878)
</span><span class="lines">@@ -1,3 +1,82 @@
</span><ins>+2015-02-10  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
+
+        New perf dashboard should have the ability to overlay moving average with an envelope
+        https://bugs.webkit.org/show_bug.cgi?id=141438
+
+        Reviewed by Andreas Kling.
+
+        This patch adds three kinds of moving average strategies and two kinds of enveloping strategies:
+
+        Simple Moving Average - The moving average x̄_i of x_i is computed as the arithmetic mean of values
+        from x_(i - n) though x_(i + m) where n is a non-negative integer and m is a positive integer. It takes
+        n, backward window size, and m, forward window size, as an argument.
+
+        Cumulative Moving Average - x̄_i is computed as the arithmetic mean of all values x_0 though x_i.
+        That is, x̄_1 = x_1 and x̄_i = ((i - 1) * M_(i - 1) + x_i) / i for all i &gt; 1.
+
+        Exponential Moving Average - x̄_i is computed as the weighted average of x_i and x̄_(i - 1) with α as
+        an argument specifying x_i's weight. To be precise, x̄_1 = x_1 and x̄_i = α * x_i + (α - 1) x̄_(i-1).
+
+
+        Average Difference - The enveloping delta d is computed as the arithmetic mean of the difference
+        between each x_i and x̄_i.
+
+        Moving Average Standard Deviation - d is computed like the standard deviation except the deviation
+        for each term is measured from the moving average instead of the sample arithmetic mean. i.e. it uses
+        the average of (x_i - x̄_i)^2 as the &quot;sample variance&quot; instead of the conventional (x_i - x̄)^2 where
+        x̄ is the sample mean of all x_i's. This change was necessary since our time series is non-stationary.
+
+
+        Each strategy is cloned for an App.Pane instance so that its parameterList can be configured per pane.
+        The configuration of the currently chosen strategies is saved in the query string for convenience.
+
+        Also added the &quot;stat pane&quot; to choose a moving average strategy and a enveloping strategy in each pane.
+
+        * public/v2/app.css: Specify the fill color for all SVG groups in the pane toolbar icons.
+
+        * public/v2/app.js:
+        (App.Pane._fetch): Delegate the creation of 'chartData' to _computeChartData.
+        (App.Pane.updateStatisticsTools): Added. Clones moving average and enveloping strategies for this pane.
+        (App.Pane._cloneStrategy): Added. Clones a strategy for a new pane.
+        (App.Pane._configureStrategy): Added. Finds and configures a strategy from the configuration retrieved
+        from the query string via ChartsController.
+        (App.Pane._computeChartData): Added. Creates chartData from fetchedData.
+        (App.Pane._computeMovingAverage): Added. Computes the moving average and the envelope.
+        (App.Pane._executeStrategy): Added.
+        (App.Pane._updateStrategyConfigIfNeeded): Pushes the strategy configurations to the query string via
+        ChartsController.
+        (App.ChartsController._parsePaneList): Merged the query string arguments for the range and point
+        selections, and added two new arguments for the moving average and the enveloping configurations.
+        (App.ChartsController._serializePaneList): Ditto.
+        (App.ChartsController._scheduleQueryStringUpdate): Observes strategy configurations.
+        (App.PaneController.actions.toggleBugsPane): Hides the stat pane.
+        (App.PaneController.actions.toggleSearchPane): Hides the stat pane.
+        (App.PaneController.actions.toggleStatPane): Added.
+
+        * public/v2/chart-pane.css: Added CSS rules for the new stat pane. Also added .foreground class for the
+        current (as opposed to baseline and target) time series for when it's the most foreground graph without
+        moving average and its envelope overlapping on top of it.
+
+        * public/v2/index.html: Added the templates for the stat pane and the corresponding icon (Σ).
+
+        * public/v2/interactive-chart.js:
+        (App.InteractiveChartComponent.chartDataDidChange): Unset _totalWidth and _totalHeight to avoid exiting
+        early inside _updateDimensionsIfNeeded when chartData changes after the initial layout.
+        (App.InteractiveChartComponent.didInsertElement): Attach event listeners here instead of inside
+        _constructGraphIfPossible since that could be called multiple times on the same SVG element.
+        (App.InteractiveChartComponent._constructGraphIfPossible): Clear down the old SVG element we created
+        but don't bother removing individual paths and circles. Added the code to show the moving average time
+        series when there is one. Also add &quot;foreground&quot; class on SVG elements for the current time series when
+        we're not showing the moving average. chart-pane.css has been updated to &quot;dim down&quot; the current time
+        series when &quot;foreground&quot; is not set.
+        (App.InteractiveChartComponent._minMaxForAllTimeSeries): Take the moving average time series into
+        account when computing the y-axis range.
+        (App.InteractiveChartComponent._brushChanged): Removed 'selectionIsLocked' argument as it's useless.
+
+        * public/v2/js/statistics.js:
+        (Statistics.MovingAverageStrategies): Added.
+        (Statistics.EnvelopingStrategies): Added.
+
</ins><span class="cx"> 2015-02-06  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
</span><span class="cx"> 
</span><span class="cx">         The delta value in the chart pane sometimes doens't show '+' for a positive delta
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv2appcss"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v2/app.css (179877 => 179878)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v2/app.css        2015-02-10 20:27:40 UTC (rev 179877)
+++ trunk/Websites/perf.webkit.org/public/v2/app.css        2015-02-10 21:39:41 UTC (rev 179878)
</span><span class="lines">@@ -121,12 +121,15 @@
</span><span class="cx"> }
</span><span class="cx"> .icon-button g {
</span><span class="cx">     stroke: #ccc;
</span><ins>+    fill: #ccc;
</ins><span class="cx"> }
</span><span class="cx"> .icon-button:hover g {
</span><span class="cx">     stroke: #666;
</span><ins>+    fill: #666;
</ins><span class="cx"> }
</span><span class="cx"> .disabled .icon-button:hover g {
</span><span class="cx">     stroke: #ccc;
</span><ins>+    fill: #ccc;
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv2appjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v2/app.js (179877 => 179878)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v2/app.js        2015-02-10 20:27:40 UTC (rev 179877)
+++ trunk/Websites/perf.webkit.org/public/v2/app.js        2015-02-10 21:39:41 UTC (rev 179878)
</span><span class="lines">@@ -351,7 +351,8 @@
</span><span class="cx">             App.Manifest.fetchRunsWithPlatformAndMetric(this.get('store'), platformId, metricId).then(function (result) {
</span><span class="cx">                 self.set('platform', result.platform);
</span><span class="cx">                 self.set('metric', result.metric);
</span><del>-                self.set('chartData', App.createChartData(result));
</del><ins>+                self.set('fetchedData', result);
+                self._computeChartData();
</ins><span class="cx">             }, function (result) {
</span><span class="cx">                 if (!result || typeof(result) === &quot;string&quot;)
</span><span class="cx">                     self.set('failure', 'Failed to fetch the JSON with an error: ' + result);
</span><span class="lines">@@ -431,6 +432,100 @@
</span><span class="cx"> 
</span><span class="cx">         return this.computeStatus(lastPoint, chartData.current.previousPoint(lastPoint));
</span><span class="cx">     }.property('chartData'),
</span><ins>+    updateStatisticsTools: function ()
+    {
+        var movingAverageStrategies = Statistics.MovingAverageStrategies.map(this._cloneStrategy.bind(this));
+        this.set('movingAverageStrategies', [{label: 'None'}].concat(movingAverageStrategies));
+        this.set('chosenMovingAverageStrategy', this._configureStrategy(movingAverageStrategies, this.get('movingAverageConfig')));
+
+        var envelopingStrategies = Statistics.EnvelopingStrategies.map(this._cloneStrategy.bind(this));
+        this.set('envelopingStrategies', [{label: 'None'}].concat(envelopingStrategies));
+        this.set('chosenEnvelopingStrategy', this._configureStrategy(envelopingStrategies, this.get('envelopingConfig')));
+    }.on('init'),
+    _cloneStrategy: function (strategy)
+    {
+        var parameterList = (strategy.parameterList || []).map(function (param) { return Ember.Object.create(param); });
+        return Ember.Object.create({
+            id: strategy.id,
+            label: strategy.label,
+            description: strategy.description,
+            parameterList: parameterList,
+            execute: strategy.execute,
+        });
+    },
+    _configureStrategy: function (strategies, config)
+    {
+        if (!config || !config[0])
+            return null;
+
+        var id = config[0];
+        var chosenStrategy = strategies.find(function (strategy) { return strategy.id == id });
+        if (!chosenStrategy)
+            return null;
+
+        for (var i = 0; i &lt; chosenStrategy.parameters.length; i++)
+            chosenStrategy.parameters[i] = parseFloat(config[i + 1]);
+
+        return chosenStrategy;
+    },
+    _computeChartData: function ()
+    {
+        if (!this.get('fetchedData'))
+            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');
+
+        this.set('chartData', chartData);
+    }.observes('chosenMovingAverageStrategy', 'chosenMovingAverageStrategy.parameterList.@each.value',
+        'chosenEnvelopingStrategy', 'chosenEnvelopingStrategy.parameterList.@each.value'),
+    _computeMovingAverage: function (chartData)
+    {
+        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 &amp;&amp; envelopingStrategy.execute)
+            envelopeDelta = this._executeStrategy(envelopingStrategy, currentTimeSeriesData, [movingAverageValues]);
+        
+        return new TimeSeries(currentTimeSeriesData.map(function (point, index) {
+            var value = movingAverageValues[index];
+            return {
+                measurement: point.measurement,
+                time: point.time,
+                value: value,
+                interval: envelopeDelta !== null ? [value - envelopeDelta, value + envelopeDelta] : null,
+            }
+        }));
+    },
+    _executeStrategy: function (strategy, currentTimeSeriesData, additionalArguments)
+    {
+        var parameters = (strategy.parameterList || []).map(function (param) {
+            var parsed = parseFloat(param.value);
+            return Math.min(param.max || Infinity, Math.max(param.min || -Infinity, isNaN(parsed) ? 0 : parsed));
+        });
+        parameters.push(currentTimeSeriesData.map(function (point) { return point.value }));
+        return strategy.execute.apply(window, parameters.concat(additionalArguments));
+    },
+    _updateStrategyConfigIfNeeded: function (strategy, configName)
+    {
+        var config = null;
+        if (strategy &amp;&amp; strategy.execute)
+            config = [strategy.id].concat((strategy.parameterList || []).map(function (param) { return param.value; }));
+
+        if (JSON.stringify(config) != JSON.stringify(this.get(configName)))
+            this.set(configName, config);
+    },
</ins><span class="cx"> });
</span><span class="cx"> 
</span><span class="cx"> App.createChartData = function (data)
</span><span class="lines">@@ -552,26 +647,30 @@
</span><span class="cx">         if (!parsedPaneList)
</span><span class="cx">             return null;
</span><span class="cx"> 
</span><del>-        // Don't re-create all panes.
</del><ins>+        // FIXME: Don't re-create all panes.
</ins><span class="cx">         var self = this;
</span><span class="cx">         return parsedPaneList.map(function (paneInfo) {
</span><span class="cx">             var timeRange = null;
</span><del>-            if (paneInfo[3] &amp;&amp; paneInfo[3] instanceof Array) {
-                var timeRange = paneInfo[3];
</del><ins>+            var selectedItem = null;
+            if (paneInfo[2] instanceof Array) {
+                var timeRange = paneInfo[2];
</ins><span class="cx">                 try {
</span><span class="cx">                     timeRange = [new Date(timeRange[0]), new Date(timeRange[1])];
</span><span class="cx">                 } catch (error) {
</span><span class="cx">                     console.log(&quot;Failed to parse the time range:&quot;, timeRange, error);
</span><span class="cx">                 }
</span><del>-            }
</del><ins>+            } else
+                selectedItem = paneInfo[2];
+
</ins><span class="cx">             return App.Pane.create({
</span><span class="cx">                 store: self.store,
</span><span class="cx">                 info: paneInfo,
</span><span class="cx">                 platformId: paneInfo[0],
</span><span class="cx">                 metricId: paneInfo[1],
</span><del>-                selectedItem: paneInfo[2],
</del><ins>+                selectedItem: selectedItem,
</ins><span class="cx">                 timeRange: timeRange,
</span><del>-                timeRangeIsLocked: !!paneInfo[4],
</del><ins>+                movingAverageConfig: paneInfo[3],
+                envelopingConfig: paneInfo[4],
</ins><span class="cx">             });
</span><span class="cx">         });
</span><span class="cx">     },
</span><span class="lines">@@ -580,13 +679,14 @@
</span><span class="cx">     {
</span><span class="cx">         if (!panes.length)
</span><span class="cx">             return undefined;
</span><ins>+        var self = this;
</ins><span class="cx">         return App.encodePrettifiedJSON(panes.map(function (pane) {
</span><span class="cx">             return [
</span><span class="cx">                 pane.get('platformId'),
</span><span class="cx">                 pane.get('metricId'),
</span><del>-                pane.get('selectedItem'),
-                pane.get('timeRange') ? pane.get('timeRange').map(function (date) { return date.getTime() }) : null,
-                !!pane.get('timeRangeIsLocked'),
</del><ins>+                pane.get('timeRange') ? pane.get('timeRange').map(function (date) { return date.getTime() }) : pane.get('selectedItem'),
+                pane.get('movingAverageConfig'),
+                pane.get('envelopingConfig'),
</ins><span class="cx">             ];
</span><span class="cx">         }));
</span><span class="cx">     },
</span><span class="lines">@@ -594,8 +694,8 @@
</span><span class="cx">     _scheduleQueryStringUpdate: function ()
</span><span class="cx">     {
</span><span class="cx">         Ember.run.debounce(this, '_updateQueryString', 1000);
</span><del>-    }.observes('sharedZoom', 'panes.@each.platform', 'panes.@each.metric', 'panes.@each.selectedItem',
-        'panes.@each.timeRange', 'panes.@each.timeRangeIsLocked'),
</del><ins>+    }.observes('sharedZoom', 'panes.@each.platform', 'panes.@each.metric', 'panes.@each.selectedItem', 'panes.@each.timeRange',
+        'panes.@each.movingAverageConfig', 'panes.@each.envelopingConfig'),
</ins><span class="cx"> 
</span><span class="cx">     _updateQueryString: function ()
</span><span class="cx">     {
</span><span class="lines">@@ -711,8 +811,10 @@
</span><span class="cx">         },
</span><span class="cx">         toggleBugsPane: function ()
</span><span class="cx">         {
</span><del>-            if (this.toggleProperty('showingAnalysisPane'))
</del><ins>+            if (this.toggleProperty('showingAnalysisPane')) {
</ins><span class="cx">                 this.set('showingSearchPane', false);
</span><ins>+                this.set('showingStatPane', false);
+            }
</ins><span class="cx">         },
</span><span class="cx">         createAnalysisTask: function ()
</span><span class="cx">         {
</span><span class="lines">@@ -743,13 +845,22 @@
</span><span class="cx">             var model = this.get('model');
</span><span class="cx">             if (!model.get('commitSearchRepository'))
</span><span class="cx">                 model.set('commitSearchRepository', App.Manifest.repositoriesWithReportedCommits[0]);
</span><del>-            if (this.toggleProperty('showingSearchPane'))
</del><ins>+            if (this.toggleProperty('showingSearchPane')) {
</ins><span class="cx">                 this.set('showingAnalysisPane', false);
</span><ins>+                this.set('showingStatPane', false);
+            }
</ins><span class="cx">         },
</span><span class="cx">         searchCommit: function () {
</span><span class="cx">             var model = this.get('model');
</span><span class="cx">             model.searchCommit(model.get('commitSearchRepository'), model.get('commitSearchKeyword'));                
</span><span class="cx">         },
</span><ins>+        toggleStatPane: function ()
+        {
+            if (this.toggleProperty('showingStatPane')) {
+                this.set('showingSearchPane', false);
+                this.set('showingAnalysisPane', false);
+            }
+        },
</ins><span class="cx">         zoomed: function (selection)
</span><span class="cx">         {
</span><span class="cx">             this.set('mainPlotDomain', selection ? selection : this.get('overviewDomain'));
</span><span class="lines">@@ -786,7 +897,7 @@
</span><span class="cx">         var newSelection = this.get('parentController').get('sharedZoom');
</span><span class="cx">         if (App.domainsAreEqual(newSelection, this.get('mainPlotDomain')))
</span><span class="cx">             return;
</span><del>-        this.set('mainPlotDomain', newSelection);
</del><ins>+        this.set('mainPlotDomain', newSelection || this.get('overviewDomain'));
</ins><span class="cx">         this.set('overviewSelection', newSelection);
</span><span class="cx">     }.observes('parentController.sharedZoom').on('init'),
</span><span class="cx">     _updateDetails: function ()
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv2chartpanecss"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v2/chart-pane.css (179877 => 179878)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v2/chart-pane.css        2015-02-10 20:27:40 UTC (rev 179877)
+++ trunk/Websites/perf.webkit.org/public/v2/chart-pane.css        2015-02-10 21:39:41 UTC (rev 179878)
</span><span class="lines">@@ -50,6 +50,13 @@
</span><span class="cx">     top: 0.55rem;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+.chart-pane a.stat-button {
+    display: inline-block;
+    position: absolute;
+    right: 3.15rem;
+    top: 0.55rem;
+}
+
</ins><span class="cx"> .chart-pane a.bugs-button {
</span><span class="cx">     display: inline-block;
</span><span class="cx">     position: absolute;
</span><span class="lines">@@ -64,38 +71,77 @@
</span><span class="cx">     top: 0.55rem;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-.search-pane, .analysis-pane {
</del><ins>+.popup-pane {
</ins><span class="cx">     position: absolute;
</span><span class="cx">     top: 1.7rem;
</span><span class="cx">     border: 1px solid #bbb;
</span><del>-    padding: 0;
</del><ins>+    font-size: 0.8rem;
+    padding: 0.2rem;
</ins><span class="cx">     border-radius: 0.5rem;
</span><span class="cx">     display: table;
</span><span class="cx">     background: white;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-.analysis-pane {
-    right: 1.3rem;
</del><ins>+.popup-pane.hidden {
+    display: none;
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-.analysis-pane table {
</del><ins>+.stat-pane {
+    right: 2.6rem;
+    padding: 0;
+}
+
+.stat-pane fieldset {
+    border: solid 1px #ccc;
+    border-radius: 0.5rem;
</ins><span class="cx">     margin: 0.2rem;
</span><ins>+    padding: 0;
+}
+
+.stat-option {
+    margin: 0;
+    padding: 0;
</ins><span class="cx">     font-size: 0.8rem;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-.analysis-pane th {
-    font-weight: normal;
</del><ins>+.stat-option h1 {
+    font-size: inherit;
+    line-height: 0.8rem;
+    padding: 0.3rem 0.5rem;
+    margin: 0;
+    border-top: solid 1px #ccc;
+    border-bottom: solid 1px #ccc;
</ins><span class="cx"> }
</span><span class="cx"> 
</span><ins>+.stat-option:first-child h1 {
+    border-top: none;
+}
+
+.stat-option &gt; * {
+    display: block;
+    margin: 0.1rem 0.5rem 0.1rem 1rem;
+}
+
+.stat-option input {
+    width: 4rem;
+}
+
+.stat-option p {
+    max-width: 15rem;
+}
+
+.analysis-pane {
+    right: 1.3rem;
+}
+
+.analysis-pane &gt; * {
+    margin: 0.2rem;
+}
+
</ins><span class="cx"> .search-pane {
</span><span class="cx">     right: 0rem;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-.analysis-pane.hidden,
-.search-pane.hidden {
-    display: none;
-}
-
</del><span class="cx"> .search-pane input {
</span><span class="cx">     display: table-cell;
</span><span class="cx">     vertical-align: middle;
</span><span class="lines">@@ -103,15 +149,15 @@
</span><span class="cx">     border: none;
</span><span class="cx">     border-top-right-radius: 0.5rem;
</span><span class="cx">     border-bottom-right-radius: 0.5rem;
</span><del>-    padding: 0.5rem;
-    font-size: 1rem;
</del><ins>+    padding: 0.2rem;
+    font-size: 0.8rem;
</ins><span class="cx">     margin: 0;
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> .search-pane .repositories {
</span><span class="cx">     display: table-cell;
</span><span class="cx">     vertical-align: middle;
</span><del>-    padding: 0 0.5rem;
</del><ins>+    padding: 0;
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> .search-pane input:focus {
</span><span class="lines">@@ -303,20 +349,41 @@
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> .chart .dot {
</span><del>-    fill: #666;
</del><ins>+    fill: #ccc;
</ins><span class="cx">     stroke: none;
</span><span class="cx"> }
</span><ins>+.chart .dot.foreground {
+    fill: #666;
+}
</ins><span class="cx"> 
</span><span class="cx"> .chart path.area {
</span><span class="cx">     stroke: none;
</span><span class="cx">     fill: #ccc;
</span><span class="cx">     opacity: 0.8;
</span><span class="cx"> }
</span><ins>+.chart path.area.foreground {
+}
</ins><span class="cx"> 
</span><span class="cx"> .chart path.current {
</span><ins>+    stroke: #ccc;
+}
+
+.chart path.current.foreground {
</ins><span class="cx">     stroke: #999;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+.chart path.movingAverage {
+    stroke: #363;
+    fill: none;
+    opacity: 0.8;
+}
+
+.chart path.envelope {
+    stroke: none;
+    fill: #6c6;
+    opacity: 0.4;
+}
+
</ins><span class="cx"> .chart path.baseline {
</span><span class="cx">     stroke: #f66;
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv2indexhtml"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v2/index.html (179877 => 179878)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v2/index.html        2015-02-10 20:27:40 UTC (rev 179877)
+++ trunk/Websites/perf.webkit.org/public/v2/index.html        2015-02-10 21:39:41 UTC (rev 179878)
</span><span class="lines">@@ -147,6 +147,9 @@
</span><span class="cx">                 &lt;header&gt;
</span><span class="cx">                     &lt;h1 {{action &quot;toggleDetails&quot;}}&gt;{{metric.fullName}} - {{ platform.name}}&lt;/h1&gt;
</span><span class="cx">                     &lt;a href=&quot;#&quot; title=&quot;Close&quot; class=&quot;close-button&quot; {{action &quot;close&quot;}}&gt;{{partial &quot;close-button&quot;}}&lt;/a&gt;
</span><ins>+                    {{#if movingAverageStrategies}}
+                        &lt;a href=&quot;#&quot; title=&quot;Statistical Tools&quot; class=&quot;stat-button&quot; {{action &quot;toggleStatPane&quot;}}&gt;{{partial &quot;stat-button&quot;}}&lt;/a&gt;
+                    {{/if}}
</ins><span class="cx">                     {{#if App.Manifest.bugTrackers}}
</span><span class="cx">                         &lt;a href=&quot;#&quot; title=&quot;Analysis&quot; class=&quot;bugs-button&quot; {{action &quot;toggleBugsPane&quot;}}&gt;
</span><span class="cx">                             {{partial &quot;analysis-button&quot;}}
</span><span class="lines">@@ -173,7 +176,6 @@
</span><span class="cx">                             rangeRoute=&quot;analysisTask&quot;
</span><span class="cx">                             selection=timeRange
</span><span class="cx">                             selectedPoints=selectedPoints
</span><del>-                            selectionIsLocked=timeRangeIsLocked
</del><span class="cx">                             markedPoints=markedPoints
</span><span class="cx">                             zoom=&quot;zoomed&quot;}}
</span><span class="cx">                     {{else}}
</span><span class="lines">@@ -200,7 +202,12 @@
</span><span class="cx">                     &lt;/div&gt;
</span><span class="cx">                 &lt;/div&gt;
</span><span class="cx"> 
</span><del>-                &lt;form {{bind-attr class=&quot;:search-pane showingSearchPane::hidden&quot;}}&gt;
</del><ins>+                &lt;div {{bind-attr class=&quot;:popup-pane :analysis-pane showingAnalysisPane::hidden&quot;}}&gt;
+                    &lt;label&gt;Name: {{input type=text value=newAnalysisTaskName}}&lt;/label&gt;
+                    &lt;button {{action &quot;createAnalysisTask&quot;}} {{bind-attr disabled=cannotAnalyze}}&gt;Analyze&lt;/button&gt;
+                &lt;/div&gt;
+
+                &lt;form {{bind-attr class=&quot;:popup-pane :search-pane showingSearchPane::hidden&quot;}}&gt;
</ins><span class="cx">                     &lt;span class=&quot;repositories&quot;&gt;
</span><span class="cx">                         {{view Ember.Select
</span><span class="cx">                             content=App.Manifest.repositoriesWithReportedCommits
</span><span class="lines">@@ -211,19 +218,7 @@
</span><span class="cx">                     {{input action=&quot;searchCommit&quot; placeholder=&quot;Name or email&quot; value=commitSearchKeyword}}
</span><span class="cx">                 &lt;/form&gt;
</span><span class="cx"> 
</span><del>-                &lt;div {{bind-attr class=&quot;:analysis-pane showingAnalysisPane::hidden&quot;}}&gt;
-                    &lt;table&gt;
-                        &lt;tbody&gt;
-                            &lt;tr&gt;
-                                &lt;th&gt;
-                                    &lt;label&gt;Name: {{input type=text value=newAnalysisTaskName}}&lt;/label&gt;
-                                    &lt;button {{action &quot;createAnalysisTask&quot;}} {{bind-attr disabled=cannotAnalyze}}&gt;Analyze&lt;/button&gt;
-                                &lt;/th&gt;
-                            &lt;/tr&gt;
-                        &lt;/tbody&gt;
-                    &lt;/table&gt;
-                &lt;/div&gt;
-
</del><ins>+                {{partial &quot;stat-pane&quot;}}
</ins><span class="cx">             &lt;/section&gt;
</span><span class="cx">         {{/each}}
</span><span class="cx">     &lt;/script&gt;
</span><span class="lines">@@ -356,12 +351,53 @@
</span><span class="cx">         &lt;/svg&gt;
</span><span class="cx">     &lt;/script&gt;
</span><span class="cx"> 
</span><ins>+    &lt;script type=&quot;text/x-handlebars&quot; data-template-name=&quot;stat-button&quot;&gt;
+        &lt;svg class=&quot;stat-button icon-button&quot; viewBox=&quot;10 0 110 100&quot;&gt;
+            &lt;g stroke=&quot;none&quot; stroke-width=&quot;0&quot; fill=&quot;black&quot;&gt;
+                &lt;path id=&quot;upper-sigma&quot; d=&quot;M 5 5 H 95 V 40 h -10 c -5 -20 -5 -20 -25 -20 H 35 L 60 50 l -20 0&quot; /&gt;
+                &lt;use xlink:href=&quot;#upper-sigma&quot; transform=&quot;translate(0, 100) scale(1, -1)&quot; /&gt;
+            &lt;/g&gt;
+        &lt;/svg&gt;
+    &lt;/script&gt;
+
+    &lt;script type=&quot;text/x-handlebars&quot; data-template-name=&quot;stat-pane&quot;&gt;
+        &lt;section {{bind-attr class=&quot;:popup-pane :stat-pane showingStatPane::hidden&quot;}}&gt;
+            &lt;section class=&quot;stat-option&quot;&gt;
+                &lt;h1&gt;Moving average&lt;/h1&gt;
+                &lt;label&gt;Type: {{view Ember.Select
+                    content=movingAverageStrategies
+                    optionValuePath='content'
+                    optionLabelPath='content.label'
+                    selection=chosenMovingAverageStrategy}}&lt;/label&gt;
+                {{#each chosenMovingAverageStrategy.parameterList}}
+                    &lt;label&gt;{{label}}: {{input type=&quot;number&quot; value=value min=min max=max step=step}}&lt;/label&gt;
+                {{/each}}
+            &lt;/section&gt;
+            {{#if chosenMovingAverageStrategy.execute}}
+                &lt;section class=&quot;stat-option&quot;&gt;
+                    &lt;h1&gt;Envelope&lt;/h1&gt;
+                    &lt;label&gt;Type: {{view Ember.Select
+                        content=envelopingStrategies
+                        optionValuePath='content'
+                        optionLabelPath='content.label'
+                        selection=chosenEnvelopingStrategy}}&lt;/label&gt;
+                    {{#if chosenEnvelopingStrategy.description}}
+                        &lt;p class=&quot;description&quot;&gt;{{chosenEnvelopingStrategy.description}}&lt;/p&gt;
+                    {{/if}}
+                    {{#each chosenEnvelopingStrategy.parameterList}}
+                        &lt;label&gt;{{label}}: &lt;input type=&quot;number&quot; {{bind-attr value=value min=min max=max step=step}}&gt;&lt;/label&gt;
+                    {{/each}}
+                &lt;/section&gt;
+            {{/if}}
+        &lt;/section&gt;
+    &lt;/script&gt;
+
</ins><span class="cx">     &lt;script type=&quot;text/x-handlebars&quot; data-template-name=&quot;analysis-button&quot;&gt;
</span><span class="cx">         &lt;svg class=&quot;analysis-button icon-button&quot; viewBox=&quot;0 0 100 100&quot;&gt;
</span><del>-            &lt;g stroke=&quot;black&quot; stroke-width=&quot;15&quot;&gt;
</del><ins>+            &lt;g stroke=&quot;black&quot; fill=&quot;black&quot; stroke-width=&quot;15&quot;&gt;
</ins><span class="cx">                 &lt;circle cx=&quot;50&quot; cy=&quot;50&quot; r=&quot;40&quot; fill=&quot;transparent&quot;/&gt;
</span><span class="cx">                 &lt;line x1=&quot;50&quot; y1=&quot;25&quot; x2=&quot;50&quot; y2=&quot;55&quot;/&gt;
</span><del>-                &lt;circle cx=&quot;50&quot; cy=&quot;67.5&quot; r=&quot;2.5&quot; fill=&quot;transparent&quot;/&gt;
</del><ins>+                &lt;circle cx=&quot;50&quot; cy=&quot;67.5&quot; r=&quot;10&quot; stroke=&quot;none&quot;/&gt;
</ins><span class="cx">             &lt;/g&gt;
</span><span class="cx">         &lt;/svg&gt;
</span><span class="cx">     &lt;/script&gt;
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv2interactivechartjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v2/interactive-chart.js (179877 => 179878)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v2/interactive-chart.js        2015-02-10 20:27:40 UTC (rev 179877)
+++ trunk/Websites/perf.webkit.org/public/v2/interactive-chart.js        2015-02-10 21:39:41 UTC (rev 179878)
</span><span class="lines">@@ -18,6 +18,8 @@
</span><span class="cx">         if (!chartData)
</span><span class="cx">             return;
</span><span class="cx">         this._needsConstruction = true;
</span><ins>+        this._totalWidth = undefined;
+        this._totalHeight = undefined;
</ins><span class="cx">         this._constructGraphIfPossible(chartData);
</span><span class="cx">     }.observes('chartData').on('init'),
</span><span class="cx">     didInsertElement: function ()
</span><span class="lines">@@ -25,6 +27,14 @@
</span><span class="cx">         var chartData = this.get('chartData');
</span><span class="cx">         if (chartData)
</span><span class="cx">             this._constructGraphIfPossible(chartData);
</span><ins>+
+        if (this.get('interactive')) {
+            var element = this.get('element');
+            this._attachEventListener(element, &quot;mousemove&quot;, this._mouseMoved.bind(this));
+            this._attachEventListener(element, &quot;mouseleave&quot;, this._mouseLeft.bind(this));
+            this._attachEventListener(element, &quot;mousedown&quot;, this._mouseDown.bind(this));
+            this._attachEventListener($(element).parents(&quot;[tabindex]&quot;), &quot;keydown&quot;, this._keyPressed.bind(this));
+        }
</ins><span class="cx">     },
</span><span class="cx">     willClearRender: function ()
</span><span class="cx">     {
</span><span class="lines">@@ -47,7 +57,8 @@
</span><span class="cx">         this._x = d3.time.scale();
</span><span class="cx">         this._y = d3.scale.linear();
</span><span class="cx"> 
</span><del>-        // FIXME: Tear down the old SVG element.
</del><ins>+        if (this._svgElement)
+            this._svgElement.remove();
</ins><span class="cx">         this._svgElement = d3.select(element).append(&quot;svg&quot;)
</span><span class="cx">                 .attr(&quot;width&quot;, &quot;100%&quot;)
</span><span class="cx">                 .attr(&quot;height&quot;, &quot;100%&quot;);
</span><span class="lines">@@ -86,23 +97,16 @@
</span><span class="cx">             .y0(function(point) { return point.interval ? yScale(point.interval[0]) : null; })
</span><span class="cx">             .y1(function(point) { return point.interval ? yScale(point.interval[1]) : null; });
</span><span class="cx"> 
</span><del>-        if (this._paths)
-            this._paths.forEach(function (path) { path.remove(); });
</del><span class="cx">         this._paths = [];
</span><del>-        if (this._areas)
-            this._areas.forEach(function (area) { area.remove(); });
</del><span class="cx">         this._areas = [];
</span><del>-        if (this._dots)
-            this._dots.forEach(function (dot) { dots.remove(); });
</del><span class="cx">         this._dots = [];
</span><del>-        if (this._highlights)
-            this._highlights.remove();
</del><span class="cx">         this._highlights = null;
</span><span class="cx"> 
</span><span class="cx">         this._currentTimeSeries = chartData.current;
</span><span class="cx">         this._currentTimeSeriesData = this._currentTimeSeries.series();
</span><span class="cx">         this._baselineTimeSeries = chartData.baseline;
</span><span class="cx">         this._targetTimeSeries = chartData.target;
</span><ins>+        this._movingAverageTimeSeries = chartData.movingAverage;
</ins><span class="cx"> 
</span><span class="cx">         this._yAxisUnit = chartData.unit;
</span><span class="cx"> 
</span><span class="lines">@@ -119,29 +123,36 @@
</span><span class="cx">                 .attr(&quot;class&quot;, &quot;target&quot;));
</span><span class="cx">         }
</span><span class="cx"> 
</span><ins>+        var foregroundClass = this._movingAverageTimeSeries ? '' : ' foreground';
</ins><span class="cx">         this._areas.push(this._clippedContainer
</span><span class="cx">             .append(&quot;path&quot;)
</span><span class="cx">             .datum(this._currentTimeSeriesData)
</span><del>-            .attr(&quot;class&quot;, &quot;area&quot;));
</del><ins>+            .attr(&quot;class&quot;, &quot;area&quot; + foregroundClass));
</ins><span class="cx"> 
</span><span class="cx">         this._paths.push(this._clippedContainer
</span><span class="cx">             .append(&quot;path&quot;)
</span><span class="cx">             .datum(this._currentTimeSeriesData)
</span><del>-            .attr(&quot;class&quot;, &quot;current&quot;));
</del><ins>+            .attr(&quot;class&quot;, &quot;current&quot; + foregroundClass));
</ins><span class="cx"> 
</span><span class="cx">         this._dots.push(this._clippedContainer
</span><span class="cx">             .selectAll(&quot;.dot&quot;)
</span><span class="cx">                 .data(this._currentTimeSeriesData)
</span><span class="cx">             .enter().append(&quot;circle&quot;)
</span><del>-                .attr(&quot;class&quot;, &quot;dot&quot;)
</del><ins>+                .attr(&quot;class&quot;, &quot;dot&quot; + foregroundClass)
</ins><span class="cx">                 .attr(&quot;r&quot;, this.get('chartPointRadius') || 1));
</span><span class="cx"> 
</span><ins>+        if (this._movingAverageTimeSeries) {
+            this._paths.push(this._clippedContainer
+                .append(&quot;path&quot;)
+                .datum(this._movingAverageTimeSeries.series())
+                .attr(&quot;class&quot;, &quot;movingAverage&quot;));
+            this._areas.push(this._clippedContainer
+                .append(&quot;path&quot;)
+                .datum(this._movingAverageTimeSeries.series())
+                .attr(&quot;class&quot;, &quot;envelope&quot;));
+        }
+
</ins><span class="cx">         if (this.get('interactive')) {
</span><del>-            this._attachEventListener(element, &quot;mousemove&quot;, this._mouseMoved.bind(this));
-            this._attachEventListener(element, &quot;mouseleave&quot;, this._mouseLeft.bind(this));
-            this._attachEventListener(element, &quot;mousedown&quot;, this._mouseDown.bind(this));
-            this._attachEventListener($(element).parents(&quot;[tabindex]&quot;), &quot;keydown&quot;, this._keyPressed.bind(this));
-
</del><span class="cx">             this._currentItemLine = this._clippedContainer
</span><span class="cx">                 .append(&quot;line&quot;)
</span><span class="cx">                 .attr(&quot;class&quot;, &quot;current-item&quot;);
</span><span class="lines">@@ -331,9 +342,10 @@
</span><span class="cx">         var currentRange = this._currentTimeSeries.minMaxForTimeRange(startTime, endTime);
</span><span class="cx">         var baselineRange = this._baselineTimeSeries ? this._baselineTimeSeries.minMaxForTimeRange(startTime, endTime) : [Number.MAX_VALUE, Number.MIN_VALUE];
</span><span class="cx">         var targetRange = this._targetTimeSeries ? this._targetTimeSeries.minMaxForTimeRange(startTime, endTime) : [Number.MAX_VALUE, Number.MIN_VALUE];
</span><ins>+        var movingAverageRange = this._movingAverageTimeSeries ? this._movingAverageTimeSeries.minMaxForTimeRange(startTime, endTime) : [Number.MAX_VALUE, Number.MIN_VALUE];
</ins><span class="cx">         return [
</span><del>-            Math.min(currentRange[0], baselineRange[0], targetRange[0]),
-            Math.max(currentRange[1], baselineRange[1], targetRange[1]),
</del><ins>+            Math.min(currentRange[0], baselineRange[0], targetRange[0], movingAverageRange[0]),
+            Math.max(currentRange[1], baselineRange[1], targetRange[1], movingAverageRange[1]),
</ins><span class="cx">         ];
</span><span class="cx">     },
</span><span class="cx">     _currentSelection: function ()
</span><span class="lines">@@ -378,7 +390,6 @@
</span><span class="cx">             if (!this._brushExtent)
</span><span class="cx">                 return;
</span><span class="cx"> 
</span><del>-            this.set('selectionIsLocked', false);
</del><span class="cx">             this._setCurrentSelection(undefined);
</span><span class="cx"> 
</span><span class="cx">             // Avoid locking the indicator in _mouseDown when the brush was cleared in the same mousedown event.
</span><span class="lines">@@ -391,7 +402,6 @@
</span><span class="cx">             return;
</span><span class="cx">         }
</span><span class="cx"> 
</span><del>-        this.set('selectionIsLocked', true);
</del><span class="cx">         this._setCurrentSelection(this._brush.extent());
</span><span class="cx">     },
</span><span class="cx">     _keyPressed: function (event)
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv2jsstatisticsjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v2/js/statistics.js (179877 => 179878)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v2/js/statistics.js        2015-02-10 20:27:40 UTC (rev 179877)
+++ trunk/Websites/perf.webkit.org/public/v2/js/statistics.js        2015-02-10 21:39:41 UTC (rev 179878)
</span><span class="lines">@@ -99,6 +99,97 @@
</span><span class="cx">             2.368026, 2.367566, 2.367115, 2.366674, 2.366243, 2.365821, 2.365407, 2.365002, 2.364606, 2.364217]
</span><span class="cx">     };
</span><span class="cx"> 
</span><ins>+    this.MovingAverageStrategies = [
+        {
+            id: 1,
+            label: 'Simple Moving Average',
+            parameterList: [
+                {label: &quot;Backward window size&quot;, value: 5, min: 2, step: 1},
+                {label: &quot;Forward window size&quot;, value: 3, min: 0, step: 1}
+            ],
+            execute: function (backwardWindowSize, forwardWindowSize, values) {
+                var averages = new Array(values.length);
+                // We use naive O(n^2) algorithm for simplicy as well as to avoid accumulating round-off errors.
+                for (var i = 0; i &lt; values.length; i++) {
+                    var sum = 0;
+                    var count = 0;
+                    for (var j = i - backwardWindowSize; j &lt; i + backwardWindowSize; j++) {
+                        if (j &gt;= 0 &amp;&amp; j &lt; values.length) {
+                            sum += values[j];
+                            count++;
+                        }
+                    }
+                    averages[i] = sum / count;
+                }
+                return averages;
+            },
+
+        },
+        {
+            id: 2,
+            label: 'Cumulative Moving Average',
+            execute: function (values) {
+                var averages = new Array(values.length);
+                var sum = 0;
+                for (var i = 0; i &lt; values.length; i++) {
+                    sum += values[i];
+                    averages[i] = sum / (i + 1);
+                }
+                return averages;
+            }
+        },
+        {
+            id: 3,
+            label: 'Exponential Moving Average',
+            parameterList: [{label: &quot;Smoothing factor&quot;, value: 0.1, min: 0.001, max: 0.9}],
+            execute: function (smoothingFactor, values) {
+                if (!values.length || typeof(smoothingFactor) !== &quot;number&quot;)
+                    return null;
+
+                var averages = new Array(values.length);
+                var movingAverage = 0;
+                averages[0] = values[0];
+                for (var i = 1; i &lt; values.length; i++)
+                    averages[i] = smoothingFactor * values[i] + (1 - smoothingFactor) * averages[i - 1];
+                return averages;
+            }
+        },
+    ];
+
+    this.EnvelopingStrategies = [
+        {
+            id: 100,
+            label: 'Average Difference',
+            description: 'The average difference between consecutive values.',
+            execute: function (values, movingAverages) {
+                if (values.length &lt; 1)
+                    return NaN;
+
+                var diff = 0;
+                for (var i = 1; i &lt; values.length; i++)
+                    diff += Math.abs(values[i] - values[i - 1]);
+
+                return diff / values.length;
+            }
+        },
+        {
+            id: 101,
+            label: 'Moving Average Standard Deviation',
+            description: 'The square root of the average deviation from the moving average with Bessel\'s correction.',
+            execute: function (values, movingAverages) {
+                if (values.length &lt; 1)
+                    return NaN;
+
+                var diffSquareSum = 0;
+                for (var i = 1; i &lt; values.length; i++) {
+                    var diff = (values[i] - movingAverages[i]);
+                    diffSquareSum += diff * diff;
+                }
+
+                return Math.sqrt(diffSquareSum / (values.length - 1));
+            }
+        },
+    ];
</ins><span class="cx"> })();
</span><span class="cx"> 
</span><span class="cx"> if (typeof module != 'undefined') {
</span></span></pre>
</div>
</div>

</body>
</html>