Changeset 212853 in webkit


Ignore:
Timestamp:
Feb 22, 2017 2:07:39 PM (7 years ago)
Author:
rniwa@webkit.org
Message:

Make sampling algorithm more stable and introduce an abstraction for sampled data
https://bugs.webkit.org/show_bug.cgi?id=168693

Reviewed by Chris Dumez.

Before this patch, TimeSeriesChart's resampling resulted in some points poping up and disappearing as
the width of a chart is changed. e.g. when resizing the browser window. The bug was by caused by
the sample for a given width not always including all points for a smaller width so as the width is
expanded, some point may be dropped.

Fixed this by using a much simpler algorithm of always picking a point when the time interval between
the preceding point and the succeeding point is larger than the minimum space we allow for a given width.

Also introduced a new abstraction around the sample data: TimeSeriesView. A TimeSeriesView provides
a similar API to TimeSeries for a subset of the time series filtered by a time range a custom function.
This paves a way to adding the ability to select baseline, etc... on the chart status view.

TimeSeriesView can be in two modes:
Mode 1. The view represents a contiguous subrange of TimeSeries - In this mode, this._data references

the underlying TimeSeries's _data directly, and we use _startingIndex to adjust index given to
find the relative index. Finding the next point or the previous point of a given point is done
via looking up the point's seriesIndex and doing a simple arithmetic. In general, an index is
converted to the absolute index in the underlying TimeSeries's _data array.

Mode 2. The view represents a filtered non-contiguous subset of TimeSeries - In this mode, this._data is

its own array. Finding the next point or the previous point of a given point requires finding
a sibling point in the underlying TimeSeries which is in this view. Since this may result in O(n)
traversal and a hash lookup, we lazily build a map of each point to its position in _data instead.

  • public/v3/components/chart-status-view.js:

(ChartStatusView.prototype.updateStatusIfNeeded): Call selectedPoints instead of sampledDataBetween for
clarity. This function now returns a TimeSeriesView instead of a raw array.

  • public/v3/components/interactive-time-series-chart.js:

(InteractiveTimeSeriesChart.prototype.currentPoint): Updated now that _sampledTimeSeriesData contains
an array of TimeSeriesView's. Note that diff is either 0, -1, or 1.
(InteractiveTimeSeriesChart.prototype.selectedPoints): Ditto. sampledDataBetween no longer exists since
we can simply call viewTimeRange on TimeSeriesView returned by sampledDataBetween.
(InteractiveTimeSeriesChart.prototype.firstSelectedPoint): Ditto.
(InteractiveTimeSeriesChart.prototype._sampleTimeSeries): Use add since excludedPoints is now a Set.

  • public/v3/components/time-series-chart.js:

(TimeSeriesChart.prototype.sampledDataBetween): Deleted.
(TimeSeriesChart.prototype.firstSampledPointBetweenTime): Deleted.
(TimeSeriesChart.prototype._ensureSampledTimeSeries): Modernized the code. Use the the time interval of
the chart divided by the number of allowed points as the time interval used in the new sampling algorithm.
(TimeSeriesChart.prototype._sampleTimeSeries): Rewritten. We also create TimeSeriesView here.
(TimeSeriesChart.prototype._sampleTimeSeries.findMedian): Deleted.
(TimeSeriesChart.prototype._updateCanvasSizeIfClientSizeChanged): Fixed a bug that the canvas size wasn't
set to the correct value on Chrome when a high DPI screen is used.

  • public/v3/models/time-series.js:

(TimeSeries.prototype.viewBetweenPoints): Renamed from dataBetweenPoints. Now returns a TimeSeriesView.
(TimeSeriesView): Added. This constructor is to be called by viewBetweenPoints, viewTimeRange, and filter.
(TimeSeriesView.prototype._buildPointIndexMap): Added. Used in mode (2).
(TimeSeriesView.prototype.length): Added.
(TimeSeriesView.prototype.firstPoint): Added.
(TimeSeriesView.prototype.lastPoint): Added.
(TimeSeriesView.prototype.nextPoint): Added. Note index is always a position in this._data. In mode (1),
this is the position of the point in the underlying TimeSeries' _data. In mode (2), this is the position
of the point in this._data which is dictinct from the underlying TimeSeries' _data.
(TimeSeriesView.prototype.previousPoint): Ditto.
(TimeSeriesView.prototype.findPointByIndex): Added. Finds the point using the positional index from the
beginning of this view. findPointByIndex(0) on one view may not be same as findPointByIndex(0) of another.
(TimeSeriesView.prototype.findById): Added. This is O(n).
(TimeSeriesView.prototype.values): Added. Returns the value of each point in this view.
(TimeSeriesView.prototype.filter): Added. Creates a new view with a subset of data points the predicate
function returned true.
(TimeSeriesView.prototype.viewTimeRange): Added. Creates a new view with a subset of data points for the
given time ragne. When the resultant view would include all points of this view, it simply returns itself
as an optimization.
(TimeSeriesView.prototype.firstPointInTimeRange): Added. Returns the first point in the view which lies
within the specified time range.
(TimeSeriesView.prototype.Symbol.iterator): Added. Iterates over each point in the view.

  • public/v3/pages/analysis-task-page.js:

(AnalysisTaskChartPane.prototype.selectedPoints): Use selectedPoints in lieu of getting selection and then
calling sampledDataBetween with that range.

  • public/v3/pages/summary-page.js:

(SummaryPageConfigurationGroup.set _medianForTimeRange): Modernized.

  • unit-tests/time-series-tests.js: Added tests for TimeSeries and TimeSeriesView. Already caught bugs!

(addPointsToSeries):

Location:
trunk/Websites/perf.webkit.org
Files:
1 added
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/Websites/perf.webkit.org/ChangeLog

    r212542 r212853  
     12017-02-21  Ryosuke Niwa  <rniwa@webkit.org>
     2
     3        Make sampling algorithm more stable and introduce an abstraction for sampled data
     4        https://bugs.webkit.org/show_bug.cgi?id=168693
     5
     6        Reviewed by Chris Dumez.
     7
     8        Before this patch, TimeSeriesChart's resampling resulted in some points poping up and disappearing as
     9        the width of a chart is changed. e.g. when resizing the browser window. The bug was by caused by
     10        the sample for a given width not always including all points for a smaller width so as the width is
     11        expanded, some point may be dropped.
     12
     13        Fixed this by using a much simpler algorithm of always picking a point when the time interval between
     14        the preceding point and the succeeding point is larger than the minimum space we allow for a given width.
     15
     16        Also introduced a new abstraction around the sample data: TimeSeriesView. A TimeSeriesView provides
     17        a similar API to TimeSeries for a subset of the time series filtered by a time range a custom function.
     18        This paves a way to adding the ability to select baseline, etc... on the chart status view.
     19
     20        TimeSeriesView can be in two modes:
     21        Mode 1. The view represents a contiguous subrange of TimeSeries - In this mode, this._data references
     22                the underlying TimeSeries's _data directly, and we use _startingIndex to adjust index given to
     23                find the relative index. Finding the next point or the previous point of a given point is done
     24                via looking up the point's seriesIndex and doing a simple arithmetic. In general, an index is
     25                converted to the absolute index in the underlying TimeSeries's _data array.
     26
     27        Mode 2. The view represents a filtered non-contiguous subset of TimeSeries -  In this mode, this._data is
     28                its own array. Finding the next point or the previous point of a given point requires finding
     29                a sibling point in the underlying TimeSeries which is in this view. Since this may result in O(n)
     30                traversal and a hash lookup, we lazily build a map of each point to its position in _data instead.
     31
     32        * public/v3/components/chart-status-view.js:
     33        (ChartStatusView.prototype.updateStatusIfNeeded): Call selectedPoints instead of sampledDataBetween for
     34        clarity. This function now returns a TimeSeriesView instead of a raw array.
     35
     36        * public/v3/components/interactive-time-series-chart.js:
     37        (InteractiveTimeSeriesChart.prototype.currentPoint): Updated now that _sampledTimeSeriesData contains
     38        an array of TimeSeriesView's. Note that diff is either 0, -1, or 1.
     39        (InteractiveTimeSeriesChart.prototype.selectedPoints): Ditto. sampledDataBetween no longer exists since
     40        we can simply call viewTimeRange on TimeSeriesView returned by sampledDataBetween.
     41        (InteractiveTimeSeriesChart.prototype.firstSelectedPoint): Ditto.
     42        (InteractiveTimeSeriesChart.prototype._sampleTimeSeries): Use add since excludedPoints is now a Set.
     43
     44        * public/v3/components/time-series-chart.js:
     45        (TimeSeriesChart.prototype.sampledDataBetween): Deleted.
     46        (TimeSeriesChart.prototype.firstSampledPointBetweenTime): Deleted.
     47        (TimeSeriesChart.prototype._ensureSampledTimeSeries): Modernized the code. Use the the time interval of
     48        the chart divided by the number of allowed points as the time interval used in the new sampling algorithm.
     49        (TimeSeriesChart.prototype._sampleTimeSeries): Rewritten. We also create TimeSeriesView here.
     50        (TimeSeriesChart.prototype._sampleTimeSeries.findMedian): Deleted.
     51        (TimeSeriesChart.prototype._updateCanvasSizeIfClientSizeChanged): Fixed a bug that the canvas size wasn't
     52        set to the correct value on Chrome when a high DPI screen is used.
     53
     54        * public/v3/models/time-series.js:
     55        (TimeSeries.prototype.viewBetweenPoints): Renamed from dataBetweenPoints. Now returns a TimeSeriesView.
     56        (TimeSeriesView): Added. This constructor is to be called by viewBetweenPoints, viewTimeRange, and filter.
     57        (TimeSeriesView.prototype._buildPointIndexMap): Added. Used in mode (2).
     58        (TimeSeriesView.prototype.length): Added.
     59        (TimeSeriesView.prototype.firstPoint): Added.
     60        (TimeSeriesView.prototype.lastPoint): Added.
     61        (TimeSeriesView.prototype.nextPoint): Added. Note index is always a position in this._data. In mode (1),
     62        this is the position of the point in the underlying TimeSeries' _data. In mode (2), this is the position
     63        of the point in this._data which is dictinct from the underlying TimeSeries' _data.
     64        (TimeSeriesView.prototype.previousPoint): Ditto.
     65        (TimeSeriesView.prototype.findPointByIndex): Added. Finds the point using the positional index from the
     66        beginning of this view. findPointByIndex(0) on one view may not be same as findPointByIndex(0) of another.
     67        (TimeSeriesView.prototype.findById): Added. This is O(n).
     68        (TimeSeriesView.prototype.values): Added. Returns the value of each point in this view.
     69        (TimeSeriesView.prototype.filter): Added. Creates a new view with a subset of data points the predicate
     70        function returned true.
     71        (TimeSeriesView.prototype.viewTimeRange): Added. Creates a new view with a subset of data points for the
     72        given time ragne. When the resultant view would include all points of this view, it simply returns itself
     73        as an optimization.
     74        (TimeSeriesView.prototype.firstPointInTimeRange): Added. Returns the first point in the view which lies
     75        within the specified time range.
     76        (TimeSeriesView.prototype.Symbol.iterator): Added. Iterates over each point in the view.
     77
     78        * public/v3/pages/analysis-task-page.js:
     79        (AnalysisTaskChartPane.prototype.selectedPoints): Use selectedPoints in lieu of getting selection and then
     80        calling sampledDataBetween with that range.
     81
     82        * public/v3/pages/summary-page.js:
     83        (SummaryPageConfigurationGroup.set _medianForTimeRange): Modernized.
     84
     85        * unit-tests/time-series-tests.js: Added tests for TimeSeries and TimeSeriesView. Already caught bugs!
     86        (addPointsToSeries):
     87
    1882017-02-17  Ryosuke Niwa  <rniwa@webkit.org>
    289
  • trunk/Websites/perf.webkit.org/public/v3/components/chart-status-view.js

    r200090 r212853  
    5151
    5252            if (selection) {
    53                 var data = this._chart.sampledDataBetween('current', selection[0], selection[1]);
    54                 if (!data)
     53                const view = this._chart.selectedPoints('current');
     54                if (!view)
    5555                    return false;
    5656
    57                 if (data && data.length > 1) {
     57                if (view && view.length() > 1) {
     58                    console.log(view.length(), view.firstPoint(), view.lastPoint())
    5859                    this._usedSelection = selection;
    59                     currentPoint = data[data.length - 1];
    60                     previousPoint = data[0];
     60                    currentPoint = view.lastPoint();
     61                    previousPoint = view.firstPoint();
    6162                }
    6263            } else  {
  • trunk/Websites/perf.webkit.org/public/v3/components/interactive-time-series-chart.js

    r210880 r212853  
    2424
    2525        if (!this._sampledTimeSeriesData) {
     26            // FIXME: Why are we not using diff in this code path?
    2627            this._ensureFetchedTimeSeries();
    2728            for (var series of this._fetchedTimeSeries) {
     
    3334        }
    3435
    35         for (var data of this._sampledTimeSeriesData) {
    36             if (!data)
     36        for (var view of this._sampledTimeSeriesData) {
     37            if (!view)
    3738                continue;
    38             var index = data.findIndex(function (point) { return point.id == id; });
    39             if (index < 0)
     39            let point = view.findById(id);
     40            if (!point)
    4041                continue;
    41             if (diff)
    42                 index += diff;
    43             return data[Math.min(Math.max(0, index), data.length)];
     42            if (!diff)
     43                return point;
     44            return (point && diff > 0 ? view.nextPoint(point) : view.previousPoint(point)) || point;
    4445        }
    4546        return null;
     
    5051    selectedPoints(type)
    5152    {
    52         var selection = this._selectionTimeRange;
    53         return selection ? this.sampledDataBetween(type, selection[0], selection[1]) : null;
     53        const selection = this._selectionTimeRange;
     54        const data = this.sampledTimeSeriesData(type);
     55        return selection && data ? data.viewTimeRange(selection[0], selection[1]) : null;
    5456    }
    5557
    5658    firstSelectedPoint(type)
    5759    {
    58         var selection = this._selectionTimeRange;
    59         return selection ? this.firstSampledPointBetweenTime(type, selection[0], selection[1]) : null;
     60        const selection = this._selectionTimeRange;
     61        const data = this.sampledTimeSeriesData(type);
     62        return selection && data ? data.firstPointInTimeRange(selection[0], selection[1]) : null;
    6063    }
    6164
     
    384387    }
    385388
    386     _sampleTimeSeries(data, maximumNumberOfPoints, excludedPoints)
     389    _sampleTimeSeries(data, minimumTimeDiff, excludedPoints)
    387390    {
    388391        if (this._indicatorID)
    389             excludedPoints.push(this._indicatorID);
     392            excludedPoints.add(this._indicatorID);
    390393        return super._sampleTimeSeries(data, maximumNumberOfPoints, excludedPoints);
    391394    }
  • trunk/Websites/perf.webkit.org/public/v3/components/time-series-chart.js

    r212542 r212853  
    132132    }
    133133
    134     sampledDataBetween(type, startTime, endTime)
    135     {
    136         var data = this.sampledTimeSeriesData(type);
    137         if (!data)
    138             return null;
    139         return data.filter(function (point) { return startTime <= point.time && point.time <= endTime; });
    140     }
    141 
    142     firstSampledPointBetweenTime(type, startTime, endTime)
    143     {
    144         var data = this.sampledTimeSeriesData(type);
    145         if (!data)
    146             return null;
    147         return data.find(function (point) { return startTime <= point.time && point.time <= endTime; });
    148     }
    149 
    150134    setAnnotations(annotations)
    151135    {
     
    498482        Instrumentation.startMeasuringTime('TimeSeriesChart', 'ensureSampledTimeSeries');
    499483
    500         var self = this;
    501         var startTime = this._startTime;
    502         var endTime = this._endTime;
    503         this._sampledTimeSeriesData = this._sourceList.map(function (source, sourceIndex) {
    504             var timeSeries = self._fetchedTimeSeries[sourceIndex];
     484        const startTime = this._startTime;
     485        const endTime = this._endTime;
     486        this._sampledTimeSeriesData = this._sourceList.map((source, sourceIndex) => {
     487            const timeSeries = this._fetchedTimeSeries[sourceIndex];
    505488            if (!timeSeries)
    506489                return null;
    507490
    508491            // A chart with X px width shouldn't have more than 2X / <radius-of-points> data points.
    509             var maximumNumberOfPoints = 2 * metrics.chartWidth / (source.pointRadius || 2);
    510 
    511             var pointAfterStart = timeSeries.findPointAfterTime(startTime);
    512             var pointBeforeStart = (pointAfterStart ? timeSeries.previousPoint(pointAfterStart) : null) || timeSeries.firstPoint();
    513             var pointAfterEnd = timeSeries.findPointAfterTime(endTime) || timeSeries.lastPoint();
     492            const maximumNumberOfPoints = 2 * metrics.chartWidth / (source.pointRadius || 2);
     493
     494            const pointAfterStart = timeSeries.findPointAfterTime(startTime);
     495            const pointBeforeStart = (pointAfterStart ? timeSeries.previousPoint(pointAfterStart) : null) || timeSeries.firstPoint();
     496            const pointAfterEnd = timeSeries.findPointAfterTime(endTime) || timeSeries.lastPoint();
    514497            if (!pointBeforeStart || !pointAfterEnd)
    515498                return null;
    516499
    517500            // FIXME: Move this to TimeSeries.prototype.
    518             var filteredData = timeSeries.dataBetweenPoints(pointBeforeStart, pointAfterEnd);
     501            const view = timeSeries.viewBetweenPoints(pointBeforeStart, pointAfterEnd);
    519502            if (!source.sampleData)
    520                 return filteredData;
    521 
    522             return self._sampleTimeSeries(filteredData, maximumNumberOfPoints, filteredData.slice(-1).map(function (point) { return point.id; }));
     503                return view;
     504
     505            return this._sampleTimeSeries(view, (endTime - startTime) / maximumNumberOfPoints, new Set);
    523506        });
    524507
     
    530513    }
    531514
    532     _sampleTimeSeries(data, maximumNumberOfPoints, excludedPoints)
    533     {
     515    _sampleTimeSeries(view, minimumTimeDiff, excludedPoints)
     516    {
     517        if (view.length() < 2)
     518            return view;
     519
    534520        Instrumentation.startMeasuringTime('TimeSeriesChart', 'sampleTimeSeries');
    535521
    536         // FIXME: Do this in O(n) using quickselect: https://en.wikipedia.org/wiki/Quickselect
    537         function findMedian(list, startIndex, indexAfterEnd)
    538         {
    539             var sortedList = list.slice(startIndex, indexAfterEnd).sort(function (a, b) { return a.value - b.value; });
    540             return sortedList[Math.floor(sortedList.length / 2)];
    541         }
    542 
    543         var samplingSize = Math.ceil(data.length / maximumNumberOfPoints);
    544 
    545         var totalTimeDiff = data[data.length - 1].time - data[0].time;
    546         var timePerSample = totalTimeDiff / maximumNumberOfPoints;
    547 
    548         var sampledData = [];
    549         var lastIndex = data.length - 1;
    550         var i = 0;
    551         while (i <= lastIndex) {
    552             var startPoint = data[i];
    553             var j;
    554             for (j = i; j <= lastIndex; j++) {
    555                 var endPoint = data[j];
    556                 if (excludedPoints.includes(endPoint.id)) {
    557                     j--;
    558                     break;
    559                 }
    560                 if (endPoint.time - startPoint.time >= timePerSample)
    561                     break;
    562             }
    563             if (i < j - 1) {
    564                 sampledData.push(findMedian(data, i, j));
    565                 i = j;
    566             } else {
    567                 sampledData.push(startPoint);
    568                 i++;
    569             }
    570         }
     522        const sampledData = view.filter((point, i) => {
     523            if (excludedPoints.has(point.id))
     524                return true;
     525            let previousPoint = view.previousPoint(point) || point;
     526            let nextPoint = view.nextPoint(point) || point;
     527            return nextPoint.time - previousPoint.time >= minimumTimeDiff;
     528        });
    571529
    572530        Instrumentation.endMeasuringTime('TimeSeriesChart', 'sampleTimeSeries');
    573531
    574         Instrumentation.reportMeasurement('TimeSeriesChart', 'samplingRatio', '%', sampledData.length / data.length * 100);
     532        Instrumentation.reportMeasurement('TimeSeriesChart', 'samplingRatio', '%', sampledData.length() / view.length() * 100);
    575533
    576534        return sampledData;
     
    625583        canvas.width = newWidth * scale;
    626584        canvas.height = newHeight * scale;
     585        canvas.style.width = newWidth + 'px';
     586        canvas.style.height = newHeight + 'px';
    627587        this._contextScaleX = scale;
    628588        this._contextScaleY = scale;
  • trunk/Websites/perf.webkit.org/public/v3/models/time-series.js

    r210880 r212853  
    7575    findPointAfterTime(time) { return this._data.find(function (point) { return point.time >= time; }); }
    7676
    77     dataBetweenPoints(firstPoint, lastPoint)
     77    viewBetweenPoints(firstPoint, lastPoint)
    7878    {
    7979        console.assert(firstPoint.series == this);
    8080        console.assert(lastPoint.series == this);
    81         return this._data.slice(firstPoint.seriesIndex, lastPoint.seriesIndex + 1);
    82     }
    83 
     81        return new TimeSeriesView(this, firstPoint.seriesIndex, lastPoint.seriesIndex + 1);
     82    }
    8483};
     84
     85class TimeSeriesView {
     86    constructor(timeSeries, startingIndex, afterEndingIndex, filteredData = null)
     87    {
     88        console.assert(timeSeries instanceof TimeSeries);
     89        console.assert(startingIndex <= afterEndingIndex);
     90        console.assert(afterEndingIndex <= timeSeries._data.length);
     91        this._timeSeries = timeSeries;
     92        this._data = filteredData || timeSeries._data;
     93        this._values = null;
     94        this._length = afterEndingIndex - startingIndex;
     95        this._startingIndex = startingIndex;
     96        this._afterEndingIndex = afterEndingIndex;
     97        this._pointIndexMap = null;
     98
     99        if (this._data != timeSeries._data) {
     100            this._findIndexForPoint = (point) => {
     101                if (this._pointIndexMap == null)
     102                    this._buildPointIndexMap();
     103                return this._pointIndexMap.get(point);
     104            }
     105        } else
     106            this._findIndexForPoint = (point) => { return point.seriesIndex; }
     107    }
     108
     109    _buildPointIndexMap()
     110    {
     111        this._pointIndexMap = new Map;
     112        const data = this._data;
     113        const length = data.length;
     114        for (let i = 0; i < length; i++)
     115            this._pointIndexMap.set(data[i], i);
     116    }
     117
     118    length() { return this._length; }
     119
     120    firstPoint() { return this._length ? this._data[this._startingIndex] : null; }
     121    lastPoint() { return this._length ? this._data[this._afterEndingIndex - 1] : null; }
     122
     123    nextPoint(point)
     124    {
     125        let index = this._findIndexForPoint(point);
     126        index++;
     127        if (index == this._afterEndingIndex)
     128            return null;
     129        return this._data[index];
     130    }
     131
     132    previousPoint(point)
     133    {
     134        const index = this._findIndexForPoint(point);
     135        if (index == this._startingIndex)
     136            return null;
     137        return this._data[index - 1];
     138    }
     139
     140    findPointByIndex(index)
     141    {
     142        index += this._startingIndex;
     143        if (index < 0 || index >= this._afterEndingIndex)
     144            return null;
     145        return this._data[index];
     146    }
     147
     148    findById(id)
     149    {
     150        for (let point of this) {
     151            if (point.id == id)
     152                return point;
     153        }
     154        return null;
     155    }
     156
     157    values()
     158    {
     159        if (this._values == null) {
     160            this._values = new Array(this._length);
     161            let i = 0;
     162            for (let point of this)
     163                this._values[i++] = point.value;
     164        }
     165        return this._values;
     166    }
     167
     168    filter(callback)
     169    {
     170        const data = this._data;
     171        const filteredData = [];
     172        for (let i = this._startingIndex; i < this._afterEndingIndex; i++) {
     173            if (callback(data[i], i))
     174                filteredData.push(data[i]);
     175        }
     176        return new TimeSeriesView(this._timeSeries, 0, filteredData.length, filteredData);
     177    }
     178
     179    viewTimeRange(startTime, endTime)
     180    {
     181        const data = this._data;
     182        let startingIndex = null;
     183        let endingIndex = null;
     184        for (let i = this._startingIndex; i < this._afterEndingIndex; i++) {
     185            if (startingIndex == null && data[i].time >= startTime)
     186                startingIndex = i;
     187            if (data[i].time <= endTime)
     188                endingIndex = i;
     189        }
     190        if (startingIndex == null || endingIndex == null)
     191            return new TimeSeriesView(this._timeSeries, 0, 0, data);
     192        return new TimeSeriesView(this._timeSeries, startingIndex, endingIndex + 1, data);
     193    }
     194
     195    firstPointInTimeRange(startTime, endTime)
     196    {
     197        console.assert(startTime <= endTime);
     198        for (let point of this) {
     199            if (point.time > endTime)
     200                return null;
     201            if (point.time >= startTime)
     202                return point;
     203        }
     204        return null;
     205    }
     206
     207    [Symbol.iterator]()
     208    {
     209        const data = this._data;
     210        const end = this._afterEndingIndex;
     211        let i = this._startingIndex;
     212        return {
     213            next() {
     214                return {value: data[i], done: i++ == end};
     215            }
     216        };
     217    }
     218}
    85219
    86220if (typeof module != 'undefined')
  • trunk/Websites/perf.webkit.org/public/v3/pages/analysis-task-page.js

    r211196 r212853  
    2525    selectedPoints()
    2626    {
    27         var selection = this._mainChart ? this._mainChart.currentSelection() : null;
    28         if (!selection)
    29             return null;
    30 
    31         return this._mainChart.sampledDataBetween('current', selection[0], selection[1]);
     27        return this._mainChart ? this._mainChart.selectedPoints('current') : null;
    3228    }
    3329}
  • trunk/Websites/perf.webkit.org/public/v3/pages/summary-page.js

    r210880 r212853  
    365365            return NaN;
    366366
    367         var startPoint = timeSeries.findPointAfterTime(timeRange[0]) || timeSeries.lastPoint();
    368         var afterEndPoint = timeSeries.findPointAfterTime(timeRange[1]) || timeSeries.lastPoint();
    369         var endPoint = timeSeries.previousPoint(afterEndPoint);
     367        const startPoint = timeSeries.findPointAfterTime(timeRange[0]) || timeSeries.lastPoint();
     368        const afterEndPoint = timeSeries.findPointAfterTime(timeRange[1]) || timeSeries.lastPoint();
     369        let endPoint = timeSeries.previousPoint(afterEndPoint);
    370370        if (!endPoint || startPoint == afterEndPoint)
    371371            endPoint = afterEndPoint;
    372372
    373         var points = timeSeries.dataBetweenPoints(startPoint, endPoint).map(function (point) { return point.value; });
    374         return Statistics.median(points);
     373        return Statistics.median(timeSeries.viewBetweenPoints(startPoint, endPoint).values());
    375374    }
    376375}
Note: See TracChangeset for help on using the changeset viewer.