From 15b1c824e3491a2ac1f0c472fa4357bd8cfdcc27 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Fri, 17 Apr 2015 11:35:24 -0700 Subject: [PATCH] [Plot] Begin separating out plot line handling Begin separating out plot line buffer from the rest of plot; managing this buffer separately will aid in merging realtime and hsitorical data, WTD-806. --- .../features/plot/src/elements/PlotLine.js | 102 +++++++++++++++++ .../plot/src/elements/PlotLineBuffer.js | 107 ++++++++++++++++++ .../plot/src/elements/PlotSeriesWindow.js | 45 ++++++++ .../features/plot/src/elements/PlotUpdater.js | 17 ++- .../plot/test/elements/PlotLineBufferSpec.js | 72 ++++++++++++ platform/features/plot/test/suite.json | 1 + 6 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 platform/features/plot/src/elements/PlotLine.js create mode 100644 platform/features/plot/src/elements/PlotLineBuffer.js create mode 100644 platform/features/plot/src/elements/PlotSeriesWindow.js create mode 100644 platform/features/plot/test/elements/PlotLineBufferSpec.js diff --git a/platform/features/plot/src/elements/PlotLine.js b/platform/features/plot/src/elements/PlotLine.js new file mode 100644 index 0000000000..3c2257c317 --- /dev/null +++ b/platform/features/plot/src/elements/PlotLine.js @@ -0,0 +1,102 @@ +/*global define,Float32Array*/ + +define( + ['./PlotSeriesWindow'], + function (PlotSeriesWindow) { + "use strict"; + + + function PlotLine(initialSize, maxPoints) { + var buffer, + length = 0, + timeWindow; + + // Binary search the buffer to find the index where + // a point with this timestamp should be inserted. + // After is a flag indicating whether it is preferred + // to insert after or before its nearest timestamp + function searchBuffer(timestamp, after) { + // Binary search for an appropriate insertion index. + function binSearch(min, max) { + var mid = Math.floor((min + max) / 2), + ts; + + if (max < min) { + return -1; + } + + ts = buffer[mid * 2]; + + // Check for an exact match... + if (ts === timestamp) { + // This is a case where we'll need to + // split up what we want to insert. + return mid + after ? -1 : 1; + } else { + // Found our index? + if (max === min) { + return max; + } + // Otherwise, search recursively + if (ts < timestamp) { + + } else { + + } + } + + } + + // Booleanize + after = !!after; + + return binSearch(0, length - 1); + } + + function insertSeriesWindow(seriesWindow) { + var startIndex = findStartIndex(), + endIndex = findEndIndex(); + + if (startIndex === endIndex) { + + } else { + // Split it up, and add the two halves + seriesWindow.split().forEach(insertSeriesWindow); + } + } + + function createWindow(series, domain, range) { + // TODO: Enforce time window, too! + return new PlotSeriesWindow( + series, + domain, + range, + 0, + series.getPointCount() + ); + } + + return { + addData: function (domainValue, rangeValue) { + // Should append to buffer + }, + addSeries: function (series, domain, range) { + // Should try to add via insertion if a + // clear insertion point is available; + // if not, should split and add each half. + // Insertion operation also needs to factor out + // redundant timestamps, for overlapping data + insertSeriesWindow(createWindow(series, domain, range)); + }, + setTimeWindow: function (start, end) { + timeWindow = [ start, end ]; + }, + clearTimeWindow: function () { + timeWindow = undefined; + } + }; + } + + return PlotLine; + } +); \ No newline at end of file diff --git a/platform/features/plot/src/elements/PlotLineBuffer.js b/platform/features/plot/src/elements/PlotLineBuffer.js new file mode 100644 index 0000000000..8a1f6a715f --- /dev/null +++ b/platform/features/plot/src/elements/PlotLineBuffer.js @@ -0,0 +1,107 @@ +/*global define,Float32Array*/ + +define( + [], + function () { + "use strict"; + + function PlotLineBuffer(domainOffset, initialSize, maxSize) { + var buffer = new Float32Array(initialSize * 2), + length = 0; + + // Binary search for an insertion index + function binSearch(value, min, max) { + var mid = Math.floor((min + max) / 2), + found = buffer[mid * 2]; + + // Collisions are not wanted + if (found === value) { + return -1; + } + + // Otherwise, if we're down to a single index, + // we've found our insertion point + if (min >= max) { + // Compare the found timestamp with the search + // value to decide if we'll insert after or before. + return min + ((found < value) ? 1 : 0); + } + + // Finally, do the recursive step + if (found < value) { + return binSearch(value, mid + 1, max); + } else { + return binSearch(value, min, mid - 1); + } + } + + // Increase the size of the buffer + function doubleBufferSize() { + var sz = Math.min(maxSize, buffer.length * 2), + canDouble = sz > buffer.length, + doubled = canDouble && new Float32Array(sz); + + if (canDouble) { + doubled.set(buffer); // Copy contents of original + buffer = doubled; + } + + return canDouble; + } + + return { + getBuffer: function () { + return buffer; + }, + insert: function (series, index) { + var sz = series.getPointCount(), + free = (buffer.length / 2) - length, + i; + + // Don't allow append after the end; that doesn't make sense + if (index > length) { + index = length; + } + + // Resize if necessary + if (sz > free) { + if (!doubleBufferSize()) { + // TODO: Figure out which data to discard + i = 0; + } + } + + // Insert data into the set + for (i = 0; i < series.getPointCount(); i += 1) { + buffer[(i + index) * 2] = + series.getDomainValue(i) - domainOffset; + buffer[(i + index) * 2 + 1] = + series.getRangeValue(i); + } + + // Increase the length + length += sz; + }, + /** + * Find an index for inserting data with this + * timestamp. The second argument indicates whether + * we are searching for insert-before or insert-after + * positions. + * Timestamps are meant to be unique, so if a collision + * occurs, this will return -1. + * @param {number} timestamp timestamp to insert + * @returns {number} the index for insertion (or -1) + */ + findInsertionIndex: function (timestamp) { + return binSearch( + timestamp - domainOffset, + 0, + length - 1 + ); + } + }; + } + + return PlotLineBuffer; + } +); \ No newline at end of file diff --git a/platform/features/plot/src/elements/PlotSeriesWindow.js b/platform/features/plot/src/elements/PlotSeriesWindow.js new file mode 100644 index 0000000000..614c09b525 --- /dev/null +++ b/platform/features/plot/src/elements/PlotSeriesWindow.js @@ -0,0 +1,45 @@ +/*global define*/ +define( + [], + function () { + "use strict"; + + /** + * Provides a window on a telemetry data series, to support + * insertion into a plot line. + */ + function PlotSeriesWindow(series, domain, range, start, end) { + return { + getPointCount: function () { + return end - start; + }, + getDomainValue: function (index) { + return series.getDomainValue(index - start, domain); + }, + getRangeValue: function (index) { + return series.getRangeValue(index - start, range); + }, + split: function () { + var mid = Math.floor((end + start) / 2); + return end > start ? + [ + new PlotSeriesWindow( + series, + domain, + range, + start, + mid + ), + new PlotSeriesWindow( + series, + domain, + range, + mid + 1, + end + ) + ] : []; + } + }; + } + } +); \ No newline at end of file diff --git a/platform/features/plot/src/elements/PlotUpdater.js b/platform/features/plot/src/elements/PlotUpdater.js index 0ca8684ad7..3b9be1d7a3 100644 --- a/platform/features/plot/src/elements/PlotUpdater.js +++ b/platform/features/plot/src/elements/PlotUpdater.js @@ -133,8 +133,23 @@ define( // Update historical data for this domain object function setHistorical(domainObject) { var id = domainObject.getId(), + // Buffer to expand buffer = buffers[id], - endIndex = realtimeIndex[id] || 0; + // Index where historical data ends (and realtime begins) + endIndex = realtimeIndex[id] || 0, + // Look up the data series + series = subscription.getSeries(domainObject), + // Get its length + seriesLength = series ? series.getPointCount() : 0, + // Get the current buffer length... + length = lengths[id] || 0, + // As well as the length of the realtime segment + realtimeLength = length - endIndex, + // Determine the new total length of the existing + // realtime + new historical segment. + totalLength = + Math.min(seriesLength + realtimeLength, maxPoints), + seriesFit = Math.max(0, totalLength - realtimeLength); // Make sure the buffer is big enough diff --git a/platform/features/plot/test/elements/PlotLineBufferSpec.js b/platform/features/plot/test/elements/PlotLineBufferSpec.js new file mode 100644 index 0000000000..7838d3bbd4 --- /dev/null +++ b/platform/features/plot/test/elements/PlotLineBufferSpec.js @@ -0,0 +1,72 @@ +/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/ + +/** + * MergeModelsSpec. Created by vwoeltje on 11/6/14. + */ +define( + ["../../src/elements/PlotLineBuffer"], + function (PlotLineBuffer) { + "use strict"; + + var TEST_INITIAL_SIZE = 10, + TEST_MAX_SIZE = 40, + TEST_DOMAIN_OFFSET = 42; + + describe("A plot line buffer", function () { + var mockSeries, + testDomainValues, + testRangeValues, + buffer; + + beforeEach(function () { + testDomainValues = [ 1, 3, 7, 9, 14, 15 ]; + testRangeValues = [ 8, 0, 3, 9, 8, 11 ]; + mockSeries = jasmine.createSpyObj( + "series", + ['getPointCount', 'getDomainValue', 'getRangeValue'] + ); + mockSeries.getPointCount.andCallFake(function () { + return testDomainValues.length; + }); + mockSeries.getDomainValue.andCallFake(function (i) { + return testDomainValues[i]; + }); + mockSeries.getRangeValue.andCallFake(function (i) { + return testRangeValues[i]; + }); + + buffer = new PlotLineBuffer( + TEST_DOMAIN_OFFSET, + TEST_INITIAL_SIZE, + TEST_MAX_SIZE + ); + + // Start with some data in there + buffer.insert(mockSeries, 0); + }); + + it("allows insertion of series data", function () { + // Convert to a regular array for checking. + // Verify that domain/ranges were interleaved and + // that domain offset was adjusted for. + expect( + Array.prototype.slice.call(buffer.getBuffer()).slice(0, 12) + ).toEqual([ -41, 8, -39, 0, -35, 3, -33, 9, -28, 8, -27, 11]); + }); + + it("finds insertion indexes", function () { + expect(buffer.findInsertionIndex(0)).toEqual(0); + expect(buffer.findInsertionIndex(2)).toEqual(1); + expect(buffer.findInsertionIndex(5)).toEqual(2); + expect(buffer.findInsertionIndex(10)).toEqual(4); + expect(buffer.findInsertionIndex(14.5)).toEqual(5); + expect(buffer.findInsertionIndex(20)).toEqual(6); + + // 9 is already in there, disallow insertion + expect(buffer.findInsertionIndex(9)).toEqual(-1); + }); + + + }); + } +); \ No newline at end of file diff --git a/platform/features/plot/test/suite.json b/platform/features/plot/test/suite.json index 92ee3b07c8..7d099f14da 100644 --- a/platform/features/plot/test/suite.json +++ b/platform/features/plot/test/suite.json @@ -6,6 +6,7 @@ "SubPlot", "SubPlotFactory", "elements/PlotAxis", + "elements/PlotLineBuffer", "elements/PlotPalette", "elements/PlotPanZoomStack", "elements/PlotPanZoomStackGroup",