Changeset 212946 in webkit
- Timestamp:
- Feb 23, 2017 10:57:04 PM (7 years ago)
- Location:
- trunk/Websites/perf.webkit.org
- Files:
-
- 7 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Websites/perf.webkit.org/ChangeLog
r212935 r212946 1 2017-02-23 Ryosuke Niwa <rniwa@webkit.org> 2 3 New sampling algorithm shows very few points when zoomed out 4 https://bugs.webkit.org/show_bug.cgi?id=168813 5 6 Reviewed by Saam Barati. 7 8 When a chart is zoomed out to a large time interval, the new sampling algorithm introduced in r212853 can 9 hide most of the data points because the difference between the preceding point's time and the succeeding 10 point's time of most points will be below the threshold we computed. 11 12 Instead, rank each data point based on the aforementioned time interval difference, and pick the first M data 13 points when M data points are to be shown. 14 15 This makes the new algorithm behave like our old algorithm while keeping it stable still. Note that this 16 algorithm still biases data points without a close neighboring point but this seems to work out in practice 17 because such a point tends to be an important sample anyway, and we don't have a lot of space between 18 data points since we aim to show about one point per pixel. 19 20 * browser-tests/index.html: 21 (CanvasTest.canvasContainsColor): Extracted from one of the test cases and generalized. Returns true when 22 the specified region of the canvas contains a specified color (alpha is optional). 23 * browser-tests/time-series-chart-tests.js: Added a test case for sampling. It checks that sampling happens 24 and that we always show some data point even when zoomed out to a large time interval. 25 (createChartWithSampleCluster): 26 27 * public/v3/components/interactive-time-series-chart.js: 28 (InteractiveTimeSeriesChart.prototype._sampleTimeSeries): 29 * public/v3/components/time-series-chart.js: 30 (TimeSeriesChart.prototype._ensureSampledTimeSeries): M, the number of data points we pick must be computed 31 based on the width of data points we're about to draw constrained by the canvas size. e.g. when the canvas 32 is only half filled, we shouldn't be showing two points per pixel in the filled region. 33 (TimeSeriesChart.prototype._sampleTimeSeries): Refined the algorithm. First, compute the time difference or 34 the rank for each N data points. Sort those ranks in descending order (in the order we prefer), and include 35 all data points above the M-th rank in the sample. 36 (TimeSeriesChart.prototype.computeTimeGrid): Revert the inadvertent change in r212935. 37 38 * public/v3/models/time-series.js: 39 (TimeSeriesView.prototype.filter): Fixed a bug that the indices passed onto the callback were shifted by the 40 starting index. 41 * unit-tests/time-series-tests.js: Added a test case to ensure callbacks are called with correct data points 42 and indices. 43 1 44 2017-02-23 Ryosuke Niwa <rniwa@webkit.org> 2 45 -
trunk/Websites/perf.webkit.org/browser-tests/index.html
r212923 r212946 164 164 165 165 canvasImageData(canvas) { return canvasImageData(canvas); }, 166 167 canvasContainsColor(canvas, color, rect = {}) 168 { 169 const content = canvas.getContext('2d').getImageData(rect.x || 0, rect.y || 0, rect.width || canvas.width, rect.height || canvas.height); 170 let found = false; 171 const data = content.data; 172 for (let startOfPixel = 0; startOfPixel < data.length; startOfPixel += 4) { 173 let r = data[startOfPixel]; 174 let g = data[startOfPixel + 1]; 175 let b = data[startOfPixel + 2]; 176 let a = data[startOfPixel + 3]; 177 if (r == color.r && g == color.g && b == color.b && (color.a == undefined || a == color.a)) 178 return true; 179 } 180 return false; 181 }, 182 166 183 expectCanvasesMatch(canvas1, canvas2) { return canvasRefTest(canvas1, canvas2, true); }, 167 184 expectCanvasesMismatch(canvas1, canvas2) { return canvasRefTest(canvas1, canvas2, false); }, -
trunk/Websites/perf.webkit.org/browser-tests/time-series-chart-tests.js
r212935 r212946 78 78 { 79 79 type: 'current', 80 lineStyle: options.lineStyle || '#666', 80 81 measurementSet: MeasurementSet.findSet(1, 1, 0), 81 82 interactive: options.interactive || false, 82 includeOutliers: options.includeOutliers || false 83 includeOutliers: options.includeOutliers || false, 84 sampleData: options.sampleData || false, 83 85 }], chartOptions); 84 86 const element = chart.element(); … … 542 544 expect(chart.content().querySelector('canvas')).to.be(null); 543 545 return waitForComponentsToRender(context).then(() => { 544 console.log('done')545 546 expect(chart.content().querySelector('canvas')).to.not.be(null); 546 547 }); … … 829 830 CanvasTest.expectCanvasesMismatch(canvasWithYAxis1, canvasWithYAxis2); 830 831 831 let content1 = CanvasTest.canvasImageData(canvasWithYAxis1); 832 let foundGridLine = false; 833 for (let y = 0; y < content1.height; y++) { 834 let endOfY = content1.width * 4 * y; 835 let r = content1.data[endOfY - 4]; 836 let g = content1.data[endOfY - 3]; 837 let b = content1.data[endOfY - 2]; 838 if (r == 204 && g == 204 && b == 204) { 839 foundGridLine = true; 840 break; 841 } 842 } 843 expect(foundGridLine).to.be(true); 832 expect(CanvasTest.canvasContainsColor(canvasWithYAxis1, {r: 204, g: 204, b: 204}, 833 {x: canvasWithYAxis1.width - 1, width: 1, y: 0, height: canvasWithYAxis1.height})).to.be(true); 834 }); 835 }); 836 }); 837 838 it('should render the sampled time series', () => { 839 const context = new BrowsingContext(); 840 return context.importScripts(scripts, 'ComponentBase', 'TimeSeriesChart', 'InteractiveTimeSeriesChart', 'MeasurementSet', 'MockRemoteAPI').then(() => { 841 const chartWithoutSampling = createChartWithSampleCluster(context, {}, {lineStyle: 'rgb(0, 128, 255)', width: '100px', height: '100px', sampleData: false}); 842 const chartWithSampling = createChartWithSampleCluster(context, {}, {lineStyle: 'rgb(0, 128, 255)', width: '100px', height: '100px', sampleData: true}); 843 844 chartWithoutSampling.setDomain(sampleCluster.startTime, sampleCluster.endTime); 845 chartWithoutSampling.fetchMeasurementSets(); 846 respondWithSampleCluster(context.symbols.MockRemoteAPI.requests[0]); 847 848 chartWithSampling.setDomain(sampleCluster.startTime, sampleCluster.endTime); 849 chartWithSampling.fetchMeasurementSets(); 850 851 let canvasWithSampling; 852 let canvasWithoutSampling; 853 return waitForComponentsToRender(context).then(() => { 854 canvasWithoutSampling = chartWithoutSampling.content().querySelector('canvas'); 855 canvasWithSampling = chartWithSampling.content().querySelector('canvas'); 856 857 CanvasTest.expectCanvasesMatch(canvasWithSampling, canvasWithoutSampling); 858 expect(CanvasTest.canvasContainsColor(canvasWithoutSampling, {r: 0, g: 128, b: 255})).to.be(true); 859 expect(CanvasTest.canvasContainsColor(canvasWithSampling, {r: 0, g: 128, b: 255})).to.be(true); 860 861 const diff = sampleCluster.endTime - sampleCluster.startTime; 862 chartWithoutSampling.setDomain(sampleCluster.startTime - 2 * diff, sampleCluster.endTime); 863 chartWithSampling.setDomain(sampleCluster.startTime - 2 * diff, sampleCluster.endTime); 864 865 CanvasTest.fillCanvasBeforeRedrawCheck(canvasWithoutSampling); 866 CanvasTest.fillCanvasBeforeRedrawCheck(canvasWithSampling); 867 return waitForComponentsToRender(context); 868 }).then(() => { 869 expect(CanvasTest.hasCanvasBeenRedrawn(canvasWithoutSampling)).to.be(true); 870 expect(CanvasTest.hasCanvasBeenRedrawn(canvasWithSampling)).to.be(true); 871 872 expect(CanvasTest.canvasContainsColor(canvasWithoutSampling, {r: 0, g: 128, b: 255})).to.be(true); 873 expect(CanvasTest.canvasContainsColor(canvasWithSampling, {r: 0, g: 128, b: 255})).to.be(true); 874 875 CanvasTest.expectCanvasesMismatch(canvasWithSampling, canvasWithoutSampling); 844 876 }); 845 877 }); -
trunk/Websites/perf.webkit.org/public/v3/components/interactive-time-series-chart.js
r212923 r212946 383 383 } 384 384 385 _sampleTimeSeries(data, m inimumTimeDiff, excludedPoints)385 _sampleTimeSeries(data, maximumNumberOfPoints, excludedPoints) 386 386 { 387 387 if (this._indicatorID) 388 388 excludedPoints.add(this._indicatorID); 389 return super._sampleTimeSeries(data, m inimumTimeDiff, excludedPoints);389 return super._sampleTimeSeries(data, maximumNumberOfPoints, excludedPoints); 390 390 } 391 391 -
trunk/Websites/perf.webkit.org/public/v3/components/time-series-chart.js
r212935 r212946 491 491 return null; 492 492 493 // A chart with X px width shouldn't have more than 2X / <radius-of-points> data points.494 const maximumNumberOfPoints = 2 * metrics.chartWidth / (source.pointRadius || 2);495 496 493 const pointAfterStart = timeSeries.findPointAfterTime(startTime); 497 494 const pointBeforeStart = (pointAfterStart ? timeSeries.previousPoint(pointAfterStart) : null) || timeSeries.firstPoint(); … … 505 502 return view; 506 503 507 return this._sampleTimeSeries(view, (endTime - startTime) / maximumNumberOfPoints, new Set); 504 // A chart with X px width shouldn't have more than 2X / <radius-of-points> data points. 505 const viewWidth = Math.min(metrics.chartWidth, metrics.timeToX(pointAfterEnd.time) - metrics.timeToX(pointBeforeStart.time)); 506 const maximumNumberOfPoints = 2 * viewWidth / (source.pointRadius || 2); 507 508 return this._sampleTimeSeries(view, maximumNumberOfPoints, new Set); 508 509 }); 509 510 … … 515 516 } 516 517 517 _sampleTimeSeries(view, minimumTimeDiff, excludedPoints) 518 { 519 if (view.length() < 2) 518 _sampleTimeSeries(view, maximumNumberOfPoints, excludedPoints) 519 { 520 521 if (view.length() < 2 || maximumNumberOfPoints >= view.length() || maximumNumberOfPoints < 1) 520 522 return view; 521 523 522 524 Instrumentation.startMeasuringTime('TimeSeriesChart', 'sampleTimeSeries'); 523 525 524 const sampledData = view.filter((point, i) => {525 if (excludedPoints.has(point.id))526 return true;526 let ranks = new Array(view.length()); 527 let i = 0; 528 for (let point of view) { 527 529 let previousPoint = view.previousPoint(point) || point; 528 530 let nextPoint = view.nextPoint(point) || point; 529 return nextPoint.time - previousPoint.time >= minimumTimeDiff; 531 ranks[i] = nextPoint.time - previousPoint.time; 532 i++; 533 } 534 535 const sortedRanks = ranks.slice(0).sort((a, b) => b - a); 536 const minimumRank = sortedRanks[Math.floor(maximumNumberOfPoints)]; 537 const sampledData = view.filter((point, i) => { 538 return excludedPoints.has(point.id) || ranks[i] >= minimumRank; 530 539 }); 531 540 … … 620 629 let previousDate = null; 621 630 let previousMonth = null; 622 while (currentTime <= max ) {631 while (currentTime <= max && result.length < maxLabels) { 623 632 const time = new Date(currentTime); 624 633 const month = time.getUTCMonth() + 1; -
trunk/Websites/perf.webkit.org/public/v3/models/time-series.js
r212853 r212946 168 168 filter(callback) 169 169 { 170 const data = this._data;171 170 const filteredData = []; 172 for (let i = this._startingIndex; i < this._afterEndingIndex; i++) { 173 if (callback(data[i], i)) 174 filteredData.push(data[i]); 171 let i = 0; 172 for (let point of this) { 173 if (callback(point, i)) 174 filteredData.push(point); 175 i++; 175 176 } 176 177 return new TimeSeriesView(this._timeSeries, 0, filteredData.length, filteredData); -
trunk/Websites/perf.webkit.org/unit-tests/time-series-tests.js
r212853 r212946 277 277 278 278 describe('filter', () => { 279 it('should call callback with an element in the view and its index', () => { 280 const timeSeries = new TimeSeries(); 281 addPointsToSeries(timeSeries, fivePoints); 282 const originalView = timeSeries.viewBetweenPoints(fivePoints[1], fivePoints[3]); 283 const points = []; 284 const indices = []; 285 const view = originalView.filter((point, index) => { 286 points.push(point); 287 indices.push(index); 288 }); 289 assert.deepEqual(points, [fivePoints[1], fivePoints[2], fivePoints[3]]); 290 assert.deepEqual(indices, [0, 1, 2]); 291 }); 292 279 293 it('should create a filtered view', () => { 280 294 const timeSeries = new TimeSeries();
Note: See TracChangeset
for help on using the changeset viewer.