[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.
This commit is contained in:
Victor Woeltjen 2015-04-17 11:35:24 -07:00
parent 9215eb1427
commit 15b1c824e3
6 changed files with 343 additions and 1 deletions

View File

@ -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;
}
);

View File

@ -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;
}
);

View File

@ -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
)
] : [];
}
};
}
}
);

View File

@ -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

View File

@ -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);
});
});
}
);

View File

@ -6,6 +6,7 @@
"SubPlot",
"SubPlotFactory",
"elements/PlotAxis",
"elements/PlotLineBuffer",
"elements/PlotPalette",
"elements/PlotPanZoomStack",
"elements/PlotPanZoomStackGroup",