Changeset 179878 in webkit
- Timestamp:
- Feb 10, 2015 1:39:41 PM (9 years ago)
- Location:
- trunk/Websites/perf.webkit.org
- Files:
-
- 7 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Websites/perf.webkit.org/ChangeLog
r179766 r179878 1 2015-02-10 Ryosuke Niwa <rniwa@webkit.org> 2 3 New perf dashboard should have the ability to overlay moving average with an envelope 4 https://bugs.webkit.org/show_bug.cgi?id=141438 5 6 Reviewed by Andreas Kling. 7 8 This patch adds three kinds of moving average strategies and two kinds of enveloping strategies: 9 10 Simple Moving Average - The moving average x̄_i of x_i is computed as the arithmetic mean of values 11 from x_(i - n) though x_(i + m) where n is a non-negative integer and m is a positive integer. It takes 12 n, backward window size, and m, forward window size, as an argument. 13 14 Cumulative Moving Average - x̄_i is computed as the arithmetic mean of all values x_0 though x_i. 15 That is, x̄_1 = x_1 and x̄_i = ((i - 1) * M_(i - 1) + x_i) / i for all i > 1. 16 17 Exponential Moving Average - x̄_i is computed as the weighted average of x_i and x̄_(i - 1) with α as 18 an argument specifying x_i's weight. To be precise, x̄_1 = x_1 and x̄_i = α * x_i + (α - 1) x̄_(i-1). 19 20 21 Average Difference - The enveloping delta d is computed as the arithmetic mean of the difference 22 between each x_i and x̄_i. 23 24 Moving Average Standard Deviation - d is computed like the standard deviation except the deviation 25 for each term is measured from the moving average instead of the sample arithmetic mean. i.e. it uses 26 the average of (x_i - x̄_i)^2 as the "sample variance" instead of the conventional (x_i - x̄)^2 where 27 x̄ is the sample mean of all x_i's. This change was necessary since our time series is non-stationary. 28 29 30 Each strategy is cloned for an App.Pane instance so that its parameterList can be configured per pane. 31 The configuration of the currently chosen strategies is saved in the query string for convenience. 32 33 Also added the "stat pane" to choose a moving average strategy and a enveloping strategy in each pane. 34 35 * public/v2/app.css: Specify the fill color for all SVG groups in the pane toolbar icons. 36 37 * public/v2/app.js: 38 (App.Pane._fetch): Delegate the creation of 'chartData' to _computeChartData. 39 (App.Pane.updateStatisticsTools): Added. Clones moving average and enveloping strategies for this pane. 40 (App.Pane._cloneStrategy): Added. Clones a strategy for a new pane. 41 (App.Pane._configureStrategy): Added. Finds and configures a strategy from the configuration retrieved 42 from the query string via ChartsController. 43 (App.Pane._computeChartData): Added. Creates chartData from fetchedData. 44 (App.Pane._computeMovingAverage): Added. Computes the moving average and the envelope. 45 (App.Pane._executeStrategy): Added. 46 (App.Pane._updateStrategyConfigIfNeeded): Pushes the strategy configurations to the query string via 47 ChartsController. 48 (App.ChartsController._parsePaneList): Merged the query string arguments for the range and point 49 selections, and added two new arguments for the moving average and the enveloping configurations. 50 (App.ChartsController._serializePaneList): Ditto. 51 (App.ChartsController._scheduleQueryStringUpdate): Observes strategy configurations. 52 (App.PaneController.actions.toggleBugsPane): Hides the stat pane. 53 (App.PaneController.actions.toggleSearchPane): Hides the stat pane. 54 (App.PaneController.actions.toggleStatPane): Added. 55 56 * public/v2/chart-pane.css: Added CSS rules for the new stat pane. Also added .foreground class for the 57 current (as opposed to baseline and target) time series for when it's the most foreground graph without 58 moving average and its envelope overlapping on top of it. 59 60 * public/v2/index.html: Added the templates for the stat pane and the corresponding icon (Σ). 61 62 * public/v2/interactive-chart.js: 63 (App.InteractiveChartComponent.chartDataDidChange): Unset _totalWidth and _totalHeight to avoid exiting 64 early inside _updateDimensionsIfNeeded when chartData changes after the initial layout. 65 (App.InteractiveChartComponent.didInsertElement): Attach event listeners here instead of inside 66 _constructGraphIfPossible since that could be called multiple times on the same SVG element. 67 (App.InteractiveChartComponent._constructGraphIfPossible): Clear down the old SVG element we created 68 but don't bother removing individual paths and circles. Added the code to show the moving average time 69 series when there is one. Also add "foreground" class on SVG elements for the current time series when 70 we're not showing the moving average. chart-pane.css has been updated to "dim down" the current time 71 series when "foreground" is not set. 72 (App.InteractiveChartComponent._minMaxForAllTimeSeries): Take the moving average time series into 73 account when computing the y-axis range. 74 (App.InteractiveChartComponent._brushChanged): Removed 'selectionIsLocked' argument as it's useless. 75 76 * public/v2/js/statistics.js: 77 (Statistics.MovingAverageStrategies): Added. 78 (Statistics.EnvelopingStrategies): Added. 79 1 80 2015-02-06 Ryosuke Niwa <rniwa@webkit.org> 2 81 -
trunk/Websites/perf.webkit.org/public/v2/app.css
r179763 r179878 122 122 .icon-button g { 123 123 stroke: #ccc; 124 fill: #ccc; 124 125 } 125 126 .icon-button:hover g { 126 127 stroke: #666; 128 fill: #666; 127 129 } 128 130 .disabled .icon-button:hover g { 129 131 stroke: #ccc; 132 fill: #ccc; 130 133 } 131 134 -
trunk/Websites/perf.webkit.org/public/v2/app.js
r179766 r179878 352 352 self.set('platform', result.platform); 353 353 self.set('metric', result.metric); 354 self.set('chartData', App.createChartData(result)); 354 self.set('fetchedData', result); 355 self._computeChartData(); 355 356 }, function (result) { 356 357 if (!result || typeof(result) === "string") … … 432 433 return this.computeStatus(lastPoint, chartData.current.previousPoint(lastPoint)); 433 434 }.property('chartData'), 435 updateStatisticsTools: function () 436 { 437 var movingAverageStrategies = Statistics.MovingAverageStrategies.map(this._cloneStrategy.bind(this)); 438 this.set('movingAverageStrategies', [{label: 'None'}].concat(movingAverageStrategies)); 439 this.set('chosenMovingAverageStrategy', this._configureStrategy(movingAverageStrategies, this.get('movingAverageConfig'))); 440 441 var envelopingStrategies = Statistics.EnvelopingStrategies.map(this._cloneStrategy.bind(this)); 442 this.set('envelopingStrategies', [{label: 'None'}].concat(envelopingStrategies)); 443 this.set('chosenEnvelopingStrategy', this._configureStrategy(envelopingStrategies, this.get('envelopingConfig'))); 444 }.on('init'), 445 _cloneStrategy: function (strategy) 446 { 447 var parameterList = (strategy.parameterList || []).map(function (param) { return Ember.Object.create(param); }); 448 return Ember.Object.create({ 449 id: strategy.id, 450 label: strategy.label, 451 description: strategy.description, 452 parameterList: parameterList, 453 execute: strategy.execute, 454 }); 455 }, 456 _configureStrategy: function (strategies, config) 457 { 458 if (!config || !config[0]) 459 return null; 460 461 var id = config[0]; 462 var chosenStrategy = strategies.find(function (strategy) { return strategy.id == id }); 463 if (!chosenStrategy) 464 return null; 465 466 for (var i = 0; i < chosenStrategy.parameters.length; i++) 467 chosenStrategy.parameters[i] = parseFloat(config[i + 1]); 468 469 return chosenStrategy; 470 }, 471 _computeChartData: function () 472 { 473 if (!this.get('fetchedData')) 474 return; 475 476 var chartData = App.createChartData(this.get('fetchedData')); 477 chartData.movingAverage = this._computeMovingAverage(chartData); 478 479 this._updateStrategyConfigIfNeeded(this.get('chosenMovingAverageStrategy'), 'movingAverageConfig'); 480 this._updateStrategyConfigIfNeeded(this.get('chosenEnvelopingStrategy'), 'envelopingConfig'); 481 482 this.set('chartData', chartData); 483 }.observes('chosenMovingAverageStrategy', 'chosenMovingAverageStrategy.parameterList.@each.value', 484 'chosenEnvelopingStrategy', 'chosenEnvelopingStrategy.parameterList.@each.value'), 485 _computeMovingAverage: function (chartData) 486 { 487 var currentTimeSeriesData = chartData.current.series(); 488 var movingAverageStrategy = this.get('chosenMovingAverageStrategy'); 489 if (!movingAverageStrategy || !movingAverageStrategy.execute) 490 return null; 491 492 var movingAverageValues = this._executeStrategy(movingAverageStrategy, currentTimeSeriesData); 493 if (!movingAverageValues) 494 return null; 495 496 var envelopeDelta = null; 497 var envelopingStrategy = this.get('chosenEnvelopingStrategy'); 498 if (envelopingStrategy && envelopingStrategy.execute) 499 envelopeDelta = this._executeStrategy(envelopingStrategy, currentTimeSeriesData, [movingAverageValues]); 500 501 return new TimeSeries(currentTimeSeriesData.map(function (point, index) { 502 var value = movingAverageValues[index]; 503 return { 504 measurement: point.measurement, 505 time: point.time, 506 value: value, 507 interval: envelopeDelta !== null ? [value - envelopeDelta, value + envelopeDelta] : null, 508 } 509 })); 510 }, 511 _executeStrategy: function (strategy, currentTimeSeriesData, additionalArguments) 512 { 513 var parameters = (strategy.parameterList || []).map(function (param) { 514 var parsed = parseFloat(param.value); 515 return Math.min(param.max || Infinity, Math.max(param.min || -Infinity, isNaN(parsed) ? 0 : parsed)); 516 }); 517 parameters.push(currentTimeSeriesData.map(function (point) { return point.value })); 518 return strategy.execute.apply(window, parameters.concat(additionalArguments)); 519 }, 520 _updateStrategyConfigIfNeeded: function (strategy, configName) 521 { 522 var config = null; 523 if (strategy && strategy.execute) 524 config = [strategy.id].concat((strategy.parameterList || []).map(function (param) { return param.value; })); 525 526 if (JSON.stringify(config) != JSON.stringify(this.get(configName))) 527 this.set(configName, config); 528 }, 434 529 }); 435 530 … … 553 648 return null; 554 649 555 // Don't re-create all panes.650 // FIXME: Don't re-create all panes. 556 651 var self = this; 557 652 return parsedPaneList.map(function (paneInfo) { 558 653 var timeRange = null; 559 if (paneInfo[3] && paneInfo[3] instanceof Array) { 560 var timeRange = paneInfo[3]; 654 var selectedItem = null; 655 if (paneInfo[2] instanceof Array) { 656 var timeRange = paneInfo[2]; 561 657 try { 562 658 timeRange = [new Date(timeRange[0]), new Date(timeRange[1])]; … … 564 660 console.log("Failed to parse the time range:", timeRange, error); 565 661 } 566 } 662 } else 663 selectedItem = paneInfo[2]; 664 567 665 return App.Pane.create({ 568 666 store: self.store, … … 570 668 platformId: paneInfo[0], 571 669 metricId: paneInfo[1], 572 selectedItem: paneInfo[2],670 selectedItem: selectedItem, 573 671 timeRange: timeRange, 574 timeRangeIsLocked: !!paneInfo[4], 672 movingAverageConfig: paneInfo[3], 673 envelopingConfig: paneInfo[4], 575 674 }); 576 675 }); … … 581 680 if (!panes.length) 582 681 return undefined; 682 var self = this; 583 683 return App.encodePrettifiedJSON(panes.map(function (pane) { 584 684 return [ 585 685 pane.get('platformId'), 586 686 pane.get('metricId'), 587 pane.get(' selectedItem'),588 pane.get(' timeRange') ? pane.get('timeRange').map(function (date) { return date.getTime() }) : null,589 !!pane.get('timeRangeIsLocked'),687 pane.get('timeRange') ? pane.get('timeRange').map(function (date) { return date.getTime() }) : pane.get('selectedItem'), 688 pane.get('movingAverageConfig'), 689 pane.get('envelopingConfig'), 590 690 ]; 591 691 })); … … 595 695 { 596 696 Ember.run.debounce(this, '_updateQueryString', 1000); 597 }.observes('sharedZoom', 'panes.@each.platform', 'panes.@each.metric', 'panes.@each.selectedItem', 598 'panes.@each. timeRange', 'panes.@each.timeRangeIsLocked'),697 }.observes('sharedZoom', 'panes.@each.platform', 'panes.@each.metric', 'panes.@each.selectedItem', 'panes.@each.timeRange', 698 'panes.@each.movingAverageConfig', 'panes.@each.envelopingConfig'), 599 699 600 700 _updateQueryString: function () … … 712 812 toggleBugsPane: function () 713 813 { 714 if (this.toggleProperty('showingAnalysisPane')) 814 if (this.toggleProperty('showingAnalysisPane')) { 715 815 this.set('showingSearchPane', false); 816 this.set('showingStatPane', false); 817 } 716 818 }, 717 819 createAnalysisTask: function () … … 744 846 if (!model.get('commitSearchRepository')) 745 847 model.set('commitSearchRepository', App.Manifest.repositoriesWithReportedCommits[0]); 746 if (this.toggleProperty('showingSearchPane')) 848 if (this.toggleProperty('showingSearchPane')) { 747 849 this.set('showingAnalysisPane', false); 850 this.set('showingStatPane', false); 851 } 748 852 }, 749 853 searchCommit: function () { 750 854 var model = this.get('model'); 751 855 model.searchCommit(model.get('commitSearchRepository'), model.get('commitSearchKeyword')); 856 }, 857 toggleStatPane: function () 858 { 859 if (this.toggleProperty('showingStatPane')) { 860 this.set('showingSearchPane', false); 861 this.set('showingAnalysisPane', false); 862 } 752 863 }, 753 864 zoomed: function (selection) … … 787 898 if (App.domainsAreEqual(newSelection, this.get('mainPlotDomain'))) 788 899 return; 789 this.set('mainPlotDomain', newSelection );900 this.set('mainPlotDomain', newSelection || this.get('overviewDomain')); 790 901 this.set('overviewSelection', newSelection); 791 902 }.observes('parentController.sharedZoom').on('init'), -
trunk/Websites/perf.webkit.org/public/v2/chart-pane.css
r179763 r179878 51 51 } 52 52 53 .chart-pane a.stat-button { 54 display: inline-block; 55 position: absolute; 56 right: 3.15rem; 57 top: 0.55rem; 58 } 59 53 60 .chart-pane a.bugs-button { 54 61 display: inline-block; … … 65 72 } 66 73 67 . search-pane, .analysis-pane {74 .popup-pane { 68 75 position: absolute; 69 76 top: 1.7rem; 70 77 border: 1px solid #bbb; 71 padding: 0; 78 font-size: 0.8rem; 79 padding: 0.2rem; 72 80 border-radius: 0.5rem; 73 81 display: table; … … 75 83 } 76 84 85 .popup-pane.hidden { 86 display: none; 87 } 88 89 .stat-pane { 90 right: 2.6rem; 91 padding: 0; 92 } 93 94 .stat-pane fieldset { 95 border: solid 1px #ccc; 96 border-radius: 0.5rem; 97 margin: 0.2rem; 98 padding: 0; 99 } 100 101 .stat-option { 102 margin: 0; 103 padding: 0; 104 font-size: 0.8rem; 105 } 106 107 .stat-option h1 { 108 font-size: inherit; 109 line-height: 0.8rem; 110 padding: 0.3rem 0.5rem; 111 margin: 0; 112 border-top: solid 1px #ccc; 113 border-bottom: solid 1px #ccc; 114 } 115 116 .stat-option:first-child h1 { 117 border-top: none; 118 } 119 120 .stat-option > * { 121 display: block; 122 margin: 0.1rem 0.5rem 0.1rem 1rem; 123 } 124 125 .stat-option input { 126 width: 4rem; 127 } 128 129 .stat-option p { 130 max-width: 15rem; 131 } 132 77 133 .analysis-pane { 78 134 right: 1.3rem; 79 135 } 80 136 81 .analysis-pane table{137 .analysis-pane > * { 82 138 margin: 0.2rem; 83 font-size: 0.8rem;84 }85 86 .analysis-pane th {87 font-weight: normal;88 139 } 89 140 90 141 .search-pane { 91 142 right: 0rem; 92 }93 94 .analysis-pane.hidden,95 .search-pane.hidden {96 display: none;97 143 } 98 144 … … 104 150 border-top-right-radius: 0.5rem; 105 151 border-bottom-right-radius: 0.5rem; 106 padding: 0. 5rem;107 font-size: 1rem;152 padding: 0.2rem; 153 font-size: 0.8rem; 108 154 margin: 0; 109 155 } … … 112 158 display: table-cell; 113 159 vertical-align: middle; 114 padding: 0 0.5rem;160 padding: 0; 115 161 } 116 162 … … 304 350 305 351 .chart .dot { 352 fill: #ccc; 353 stroke: none; 354 } 355 .chart .dot.foreground { 306 356 fill: #666; 307 stroke: none;308 357 } 309 358 … … 313 362 opacity: 0.8; 314 363 } 364 .chart path.area.foreground { 365 } 315 366 316 367 .chart path.current { 368 stroke: #ccc; 369 } 370 371 .chart path.current.foreground { 317 372 stroke: #999; 373 } 374 375 .chart path.movingAverage { 376 stroke: #363; 377 fill: none; 378 opacity: 0.8; 379 } 380 381 .chart path.envelope { 382 stroke: none; 383 fill: #6c6; 384 opacity: 0.4; 318 385 } 319 386 -
trunk/Websites/perf.webkit.org/public/v2/index.html
r179763 r179878 148 148 <h1 {{action "toggleDetails"}}>{{metric.fullName}} - {{ platform.name}}</h1> 149 149 <a href="#" title="Close" class="close-button" {{action "close"}}>{{partial "close-button"}}</a> 150 {{#if movingAverageStrategies}} 151 <a href="#" title="Statistical Tools" class="stat-button" {{action "toggleStatPane"}}>{{partial "stat-button"}}</a> 152 {{/if}} 150 153 {{#if App.Manifest.bugTrackers}} 151 154 <a href="#" title="Analysis" class="bugs-button" {{action "toggleBugsPane"}}> … … 174 177 selection=timeRange 175 178 selectedPoints=selectedPoints 176 selectionIsLocked=timeRangeIsLocked177 179 markedPoints=markedPoints 178 180 zoom="zoomed"}} … … 201 203 </div> 202 204 203 <form {{bind-attr class=":search-pane showingSearchPane::hidden"}}> 205 <div {{bind-attr class=":popup-pane :analysis-pane showingAnalysisPane::hidden"}}> 206 <label>Name: {{input type=text value=newAnalysisTaskName}}</label> 207 <button {{action "createAnalysisTask"}} {{bind-attr disabled=cannotAnalyze}}>Analyze</button> 208 </div> 209 210 <form {{bind-attr class=":popup-pane :search-pane showingSearchPane::hidden"}}> 204 211 <span class="repositories"> 205 212 {{view Ember.Select … … 212 219 </form> 213 220 214 <div {{bind-attr class=":analysis-pane showingAnalysisPane::hidden"}}> 215 <table> 216 <tbody> 217 <tr> 218 <th> 219 <label>Name: {{input type=text value=newAnalysisTaskName}}</label> 220 <button {{action "createAnalysisTask"}} {{bind-attr disabled=cannotAnalyze}}>Analyze</button> 221 </th> 222 </tr> 223 </tbody> 224 </table> 225 </div> 226 221 {{partial "stat-pane"}} 227 222 </section> 228 223 {{/each}} … … 357 352 </script> 358 353 354 <script type="text/x-handlebars" data-template-name="stat-button"> 355 <svg class="stat-button icon-button" viewBox="10 0 110 100"> 356 <g stroke="none" stroke-width="0" fill="black"> 357 <path id="upper-sigma" d="M 5 5 H 95 V 40 h -10 c -5 -20 -5 -20 -25 -20 H 35 L 60 50 l -20 0" /> 358 <use xlink:href="#upper-sigma" transform="translate(0, 100) scale(1, -1)" /> 359 </g> 360 </svg> 361 </script> 362 363 <script type="text/x-handlebars" data-template-name="stat-pane"> 364 <section {{bind-attr class=":popup-pane :stat-pane showingStatPane::hidden"}}> 365 <section class="stat-option"> 366 <h1>Moving average</h1> 367 <label>Type: {{view Ember.Select 368 content=movingAverageStrategies 369 optionValuePath='content' 370 optionLabelPath='content.label' 371 selection=chosenMovingAverageStrategy}}</label> 372 {{#each chosenMovingAverageStrategy.parameterList}} 373 <label>{{label}}: {{input type="number" value=value min=min max=max step=step}}</label> 374 {{/each}} 375 </section> 376 {{#if chosenMovingAverageStrategy.execute}} 377 <section class="stat-option"> 378 <h1>Envelope</h1> 379 <label>Type: {{view Ember.Select 380 content=envelopingStrategies 381 optionValuePath='content' 382 optionLabelPath='content.label' 383 selection=chosenEnvelopingStrategy}}</label> 384 {{#if chosenEnvelopingStrategy.description}} 385 <p class="description">{{chosenEnvelopingStrategy.description}}</p> 386 {{/if}} 387 {{#each chosenEnvelopingStrategy.parameterList}} 388 <label>{{label}}: <input type="number" {{bind-attr value=value min=min max=max step=step}}></label> 389 {{/each}} 390 </section> 391 {{/if}} 392 </section> 393 </script> 394 359 395 <script type="text/x-handlebars" data-template-name="analysis-button"> 360 396 <svg class="analysis-button icon-button" viewBox="0 0 100 100"> 361 <g stroke="black" stroke-width="15">397 <g stroke="black" fill="black" stroke-width="15"> 362 398 <circle cx="50" cy="50" r="40" fill="transparent"/> 363 399 <line x1="50" y1="25" x2="50" y2="55"/> 364 <circle cx="50" cy="67.5" r=" 2.5" fill="transparent"/>400 <circle cx="50" cy="67.5" r="10" stroke="none"/> 365 401 </g> 366 402 </svg> -
trunk/Websites/perf.webkit.org/public/v2/interactive-chart.js
r179763 r179878 19 19 return; 20 20 this._needsConstruction = true; 21 this._totalWidth = undefined; 22 this._totalHeight = undefined; 21 23 this._constructGraphIfPossible(chartData); 22 24 }.observes('chartData').on('init'), … … 26 28 if (chartData) 27 29 this._constructGraphIfPossible(chartData); 30 31 if (this.get('interactive')) { 32 var element = this.get('element'); 33 this._attachEventListener(element, "mousemove", this._mouseMoved.bind(this)); 34 this._attachEventListener(element, "mouseleave", this._mouseLeft.bind(this)); 35 this._attachEventListener(element, "mousedown", this._mouseDown.bind(this)); 36 this._attachEventListener($(element).parents("[tabindex]"), "keydown", this._keyPressed.bind(this)); 37 } 28 38 }, 29 39 willClearRender: function () … … 48 58 this._y = d3.scale.linear(); 49 59 50 // FIXME: Tear down the old SVG element. 60 if (this._svgElement) 61 this._svgElement.remove(); 51 62 this._svgElement = d3.select(element).append("svg") 52 63 .attr("width", "100%") … … 87 98 .y1(function(point) { return point.interval ? yScale(point.interval[1]) : null; }); 88 99 89 if (this._paths)90 this._paths.forEach(function (path) { path.remove(); });91 100 this._paths = []; 92 if (this._areas)93 this._areas.forEach(function (area) { area.remove(); });94 101 this._areas = []; 95 if (this._dots)96 this._dots.forEach(function (dot) { dots.remove(); });97 102 this._dots = []; 98 if (this._highlights)99 this._highlights.remove();100 103 this._highlights = null; 101 104 … … 104 107 this._baselineTimeSeries = chartData.baseline; 105 108 this._targetTimeSeries = chartData.target; 109 this._movingAverageTimeSeries = chartData.movingAverage; 106 110 107 111 this._yAxisUnit = chartData.unit; … … 120 124 } 121 125 126 var foregroundClass = this._movingAverageTimeSeries ? '' : ' foreground'; 122 127 this._areas.push(this._clippedContainer 123 128 .append("path") 124 129 .datum(this._currentTimeSeriesData) 125 .attr("class", "area" ));130 .attr("class", "area" + foregroundClass)); 126 131 127 132 this._paths.push(this._clippedContainer 128 133 .append("path") 129 134 .datum(this._currentTimeSeriesData) 130 .attr("class", "current" ));135 .attr("class", "current" + foregroundClass)); 131 136 132 137 this._dots.push(this._clippedContainer … … 134 139 .data(this._currentTimeSeriesData) 135 140 .enter().append("circle") 136 .attr("class", "dot" )141 .attr("class", "dot" + foregroundClass) 137 142 .attr("r", this.get('chartPointRadius') || 1)); 138 143 144 if (this._movingAverageTimeSeries) { 145 this._paths.push(this._clippedContainer 146 .append("path") 147 .datum(this._movingAverageTimeSeries.series()) 148 .attr("class", "movingAverage")); 149 this._areas.push(this._clippedContainer 150 .append("path") 151 .datum(this._movingAverageTimeSeries.series()) 152 .attr("class", "envelope")); 153 } 154 139 155 if (this.get('interactive')) { 140 this._attachEventListener(element, "mousemove", this._mouseMoved.bind(this));141 this._attachEventListener(element, "mouseleave", this._mouseLeft.bind(this));142 this._attachEventListener(element, "mousedown", this._mouseDown.bind(this));143 this._attachEventListener($(element).parents("[tabindex]"), "keydown", this._keyPressed.bind(this));144 145 156 this._currentItemLine = this._clippedContainer 146 157 .append("line") … … 332 343 var baselineRange = this._baselineTimeSeries ? this._baselineTimeSeries.minMaxForTimeRange(startTime, endTime) : [Number.MAX_VALUE, Number.MIN_VALUE]; 333 344 var targetRange = this._targetTimeSeries ? this._targetTimeSeries.minMaxForTimeRange(startTime, endTime) : [Number.MAX_VALUE, Number.MIN_VALUE]; 345 var movingAverageRange = this._movingAverageTimeSeries ? this._movingAverageTimeSeries.minMaxForTimeRange(startTime, endTime) : [Number.MAX_VALUE, Number.MIN_VALUE]; 334 346 return [ 335 Math.min(currentRange[0], baselineRange[0], targetRange[0] ),336 Math.max(currentRange[1], baselineRange[1], targetRange[1] ),347 Math.min(currentRange[0], baselineRange[0], targetRange[0], movingAverageRange[0]), 348 Math.max(currentRange[1], baselineRange[1], targetRange[1], movingAverageRange[1]), 337 349 ]; 338 350 }, … … 379 391 return; 380 392 381 this.set('selectionIsLocked', false);382 393 this._setCurrentSelection(undefined); 383 394 … … 392 403 } 393 404 394 this.set('selectionIsLocked', true);395 405 this._setCurrentSelection(this._brush.extent()); 396 406 }, -
trunk/Websites/perf.webkit.org/public/v2/js/statistics.js
r174477 r179878 100 100 }; 101 101 102 this.MovingAverageStrategies = [ 103 { 104 id: 1, 105 label: 'Simple Moving Average', 106 parameterList: [ 107 {label: "Backward window size", value: 5, min: 2, step: 1}, 108 {label: "Forward window size", value: 3, min: 0, step: 1} 109 ], 110 execute: function (backwardWindowSize, forwardWindowSize, values) { 111 var averages = new Array(values.length); 112 // We use naive O(n^2) algorithm for simplicy as well as to avoid accumulating round-off errors. 113 for (var i = 0; i < values.length; i++) { 114 var sum = 0; 115 var count = 0; 116 for (var j = i - backwardWindowSize; j < i + backwardWindowSize; j++) { 117 if (j >= 0 && j < values.length) { 118 sum += values[j]; 119 count++; 120 } 121 } 122 averages[i] = sum / count; 123 } 124 return averages; 125 }, 126 127 }, 128 { 129 id: 2, 130 label: 'Cumulative Moving Average', 131 execute: function (values) { 132 var averages = new Array(values.length); 133 var sum = 0; 134 for (var i = 0; i < values.length; i++) { 135 sum += values[i]; 136 averages[i] = sum / (i + 1); 137 } 138 return averages; 139 } 140 }, 141 { 142 id: 3, 143 label: 'Exponential Moving Average', 144 parameterList: [{label: "Smoothing factor", value: 0.1, min: 0.001, max: 0.9}], 145 execute: function (smoothingFactor, values) { 146 if (!values.length || typeof(smoothingFactor) !== "number") 147 return null; 148 149 var averages = new Array(values.length); 150 var movingAverage = 0; 151 averages[0] = values[0]; 152 for (var i = 1; i < values.length; i++) 153 averages[i] = smoothingFactor * values[i] + (1 - smoothingFactor) * averages[i - 1]; 154 return averages; 155 } 156 }, 157 ]; 158 159 this.EnvelopingStrategies = [ 160 { 161 id: 100, 162 label: 'Average Difference', 163 description: 'The average difference between consecutive values.', 164 execute: function (values, movingAverages) { 165 if (values.length < 1) 166 return NaN; 167 168 var diff = 0; 169 for (var i = 1; i < values.length; i++) 170 diff += Math.abs(values[i] - values[i - 1]); 171 172 return diff / values.length; 173 } 174 }, 175 { 176 id: 101, 177 label: 'Moving Average Standard Deviation', 178 description: 'The square root of the average deviation from the moving average with Bessel\'s correction.', 179 execute: function (values, movingAverages) { 180 if (values.length < 1) 181 return NaN; 182 183 var diffSquareSum = 0; 184 for (var i = 1; i < values.length; i++) { 185 var diff = (values[i] - movingAverages[i]); 186 diffSquareSum += diff * diff; 187 } 188 189 return Math.sqrt(diffSquareSum / (values.length - 1)); 190 } 191 }, 192 ]; 102 193 })(); 103 194
Note: See TracChangeset
for help on using the changeset viewer.