/**
* This class we summarize the data and returns it when required.
*/
Ext.define("Ext.draw.SegmentTree", {
config: {
strategy: "double"
},
/**
* @private
* @param {Object} result
* @param {Number} last
* @param {Number} dataX
* @param {Number} dataOpen
* @param {Number} dataHigh
* @param {Number} dataLow
* @param {Number} dataClose
*/
time: function(result, last, dataX, dataOpen, dataHigh, dataLow, dataClose) {
var start = 0,
lastOffset, lastOffsetEnd,
minimum = new Date(dataX[result.startIdx[0]]),
maximum = new Date(dataX[result.endIdx[last - 1]]),
extDate = Ext.Date,
units = [
[extDate.MILLI, 1, 'ms1', null],
[extDate.MILLI, 2, 'ms2', 'ms1'],
[extDate.MILLI, 5, 'ms5', 'ms1'],
[extDate.MILLI, 10, 'ms10', 'ms5'],
[extDate.MILLI, 50, 'ms50', 'ms10'],
[extDate.MILLI, 100, 'ms100', 'ms50'],
[extDate.MILLI, 500, 'ms500', 'ms100'],
[extDate.SECOND, 1, 's1', 'ms500'],
[extDate.SECOND, 10, 's10', 's1'],
[extDate.SECOND, 30, 's30', 's10'],
[extDate.MINUTE, 1, 'mi1', 's10'],
[extDate.MINUTE, 5, 'mi5', 'mi1'],
[extDate.MINUTE, 10, 'mi10', 'mi5'],
[extDate.MINUTE, 30, 'mi30', 'mi10'],
[extDate.HOUR, 1, 'h1', 'mi30'],
[extDate.HOUR, 6, 'h6', 'h1'],
[extDate.HOUR, 12, 'h12', 'h6'],
[extDate.DAY, 1, 'd1', 'h12'],
[extDate.DAY, 7, 'd7', 'd1'],
[extDate.MONTH, 1, 'mo1', 'd1'],
[extDate.MONTH, 3, 'mo3', 'mo1'],
[extDate.MONTH, 6, 'mo6', 'mo3'],
[extDate.YEAR, 1, 'y1', 'mo3'],
[extDate.YEAR, 5, 'y5', 'y1'],
[extDate.YEAR, 10, 'y10', 'y5'],
[extDate.YEAR, 100, 'y100', 'y10']
],
unitIdx, currentUnit,
plainStart = start,
plainEnd = last,
startIdxs = result.startIdx,
endIdxs = result.endIdx,
minIdxs = result.minIdx,
maxIdxs = result.maxIdx,
opens = result.open,
closes = result.close,
minXs = result.minX,
minYs = result.minY,
maxXs = result.maxX,
maxYs = result.maxY,
i, current;
for (unitIdx = 0; last > start + 1 && unitIdx < units.length; unitIdx++) {
minimum = new Date(dataX[startIdxs[0]]);
currentUnit = units[unitIdx];
minimum = extDate.align(minimum, currentUnit[0], currentUnit[1]);
// eslint-disable-next-line max-len
if (extDate.diff(minimum, maximum, currentUnit[0]) > dataX.length * 2 * currentUnit[1]) {
continue;
}
if (currentUnit[3] && result.map['time_' + currentUnit[3]]) {
lastOffset = result.map['time_' + currentUnit[3]][0];
lastOffsetEnd = result.map['time_' + currentUnit[3]][1];
}
else {
lastOffset = plainStart;
lastOffsetEnd = plainEnd;
}
start = last;
current = minimum;
startIdxs[last] = startIdxs[lastOffset];
endIdxs[last] = endIdxs[lastOffset];
minIdxs[last] = minIdxs[lastOffset];
maxIdxs[last] = maxIdxs[lastOffset];
opens[last] = opens[lastOffset];
closes[last] = closes[lastOffset];
minXs[last] = minXs[lastOffset];
minYs[last] = minYs[lastOffset];
maxXs[last] = maxXs[lastOffset];
maxYs[last] = maxYs[lastOffset];
current = Ext.Date.add(current, currentUnit[0], currentUnit[1]);
for (i = lastOffset + 1; i < lastOffsetEnd; i++) {
if (dataX[endIdxs[i]] < +current) {
endIdxs[last] = endIdxs[i];
closes[last] = closes[i];
if (maxYs[i] > maxYs[last]) {
maxYs[last] = maxYs[i];
maxXs[last] = maxXs[i];
maxIdxs[last] = maxIdxs[i];
}
if (minYs[i] < minYs[last]) {
minYs[last] = minYs[i];
minXs[last] = minXs[i];
minIdxs[last] = minIdxs[i];
}
}
else {
last++;
startIdxs[last] = startIdxs[i];
endIdxs[last] = endIdxs[i];
minIdxs[last] = minIdxs[i];
maxIdxs[last] = maxIdxs[i];
opens[last] = opens[i];
closes[last] = closes[i];
minXs[last] = minXs[i];
minYs[last] = minYs[i];
maxXs[last] = maxXs[i];
maxYs[last] = maxYs[i];
current = Ext.Date.add(current, currentUnit[0], currentUnit[1]);
}
}
if (last > start) {
result.map['time_' + currentUnit[2]] = [start, last];
}
}
},
/**
* @private
* @param {Object} result
* @param {Number} position
* @param {Number} dataX
* @param {Number} dataOpen
* @param {Number} dataHigh
* @param {Number} dataLow
* @param {Number} dataClose
*/
"double": function(result, position, dataX, dataOpen, dataHigh, dataLow, dataClose) {
var offset = 0,
lastOffset,
step = 1,
i,
startIdx,
endIdx,
minIdx,
maxIdx,
open,
close,
minX,
minY,
maxX,
maxY;
while (position > offset + 1) {
lastOffset = offset;
offset = position;
step += step;
for (i = lastOffset; i < offset; i += 2) {
if (i === offset - 1) {
startIdx = result.startIdx[i];
endIdx = result.endIdx[i];
minIdx = result.minIdx[i];
maxIdx = result.maxIdx[i];
open = result.open[i];
close = result.close[i];
minX = result.minX[i];
minY = result.minY[i];
maxX = result.maxX[i];
maxY = result.maxY[i];
}
else {
startIdx = result.startIdx[i];
endIdx = result.endIdx[i + 1];
open = result.open[i];
close = result.close[i];
if (result.minY[i] <= result.minY[i + 1]) {
minIdx = result.minIdx[i];
minX = result.minX[i];
minY = result.minY[i];
}
else {
minIdx = result.minIdx[i + 1];
minX = result.minX[i + 1];
minY = result.minY[i + 1];
}
if (result.maxY[i] >= result.maxY[i + 1]) {
maxIdx = result.maxIdx[i];
maxX = result.maxX[i];
maxY = result.maxY[i];
}
else {
maxIdx = result.maxIdx[i + 1];
maxX = result.maxX[i + 1];
maxY = result.maxY[i + 1];
}
}
result.startIdx[position] = startIdx;
result.endIdx[position] = endIdx;
result.minIdx[position] = minIdx;
result.maxIdx[position] = maxIdx;
result.open[position] = open;
result.close[position] = close;
result.minX[position] = minX;
result.minY[position] = minY;
result.maxX[position] = maxX;
result.maxY[position] = maxY;
position++;
}
result.map['double_' + step] = [offset, position];
}
},
/**
* @method
* @private
*/
none: Ext.emptyFn,
/**
* @private
*
* @param {Number} dataX
* @param {Number} dataOpen
* @param {Number} dataHigh
* @param {Number} dataLow
* @param {Number} dataClose
* @return {Object}
*/
aggregateData: function(dataX, dataOpen, dataHigh, dataLow, dataClose) {
var length = dataX.length,
startIdx = [],
endIdx = [],
minIdx = [],
maxIdx = [],
open = [],
minX = [],
minY = [],
maxX = [],
maxY = [],
close = [],
result = {
startIdx: startIdx,
endIdx: endIdx,
minIdx: minIdx,
maxIdx: maxIdx,
open: open,
minX: minX,
minY: minY,
maxX: maxX,
maxY: maxY,
close: close
},
minValue = Infinity,
maxValue = -Infinity,
i, v;
for (i = 0; i < length; i++) {
v = dataX[i];
startIdx[i] = i;
endIdx[i] = i;
minIdx[i] = i;
maxIdx[i] = i;
open[i] = dataOpen[i];
minX[i] = v;
minY[i] = dataLow[i];
maxX[i] = v;
maxY[i] = dataHigh[i];
close[i] = dataClose[i];
if (v < minValue) {
minValue = v;
}
if (v > maxValue) {
maxValue = v;
}
}
result.map = {
original: [0, length]
};
result.range = Math.abs(maxValue - minValue);
if (length) {
this[this.getStrategy()](result, length, dataX, dataOpen, dataHigh, dataLow, dataClose);
}
return result;
},
binarySearch: function(items, start, end, key, isMin) {
var dx = this.dataX,
mid, val;
if (key <= dx[items[0]]) {
return start;
}
if (key >= dx[items[end - 1]]) {
return end - 1;
}
while (start + 1 < end) {
mid = (start + end) >> 1;
val = dx[items[mid]];
if (val === key) {
return mid;
}
else if (val < key) {
start = mid;
}
else {
end = mid;
}
}
return isMin ? start : end;
},
constructor: function(config) {
this.initConfig(config);
},
/**
* Sets the data of the segment tree.
* @param {Number} dataX
* @param {Number} dataOpen
* @param {Number} dataHigh
* @param {Number} dataLow
* @param {Number} dataClose
*/
setData: function(dataX, dataOpen, dataHigh, dataLow, dataClose) {
if (!dataHigh) {
dataClose = dataLow = dataHigh = dataOpen;
}
this.dataX = dataX;
this.dataOpen = dataOpen;
this.dataHigh = dataHigh;
this.dataLow = dataLow;
this.dataClose = dataClose;
if (dataX.length === dataHigh.length &&
dataX.length === dataLow.length) {
this.cache = this.aggregateData(dataX, dataOpen, dataHigh, dataLow, dataClose);
}
},
/**
* Returns the minimum range of data that fits the given range and step size.
*
* @param {Number} min
* @param {Number} max
* @param {Number} estStep
* @return {Object} The aggregation information.
* @return {Number} return.start
* @return {Number} return.end
* @return {Object} return.data The aggregated data
*/
getAggregation: function(min, max, estStep) {
if (!this.cache) {
return null;
}
// eslint-disable-next-line vars-on-top
var minStep = Infinity,
cache = this.cache,
range = cache.range,
cacheMap = cache.map,
result = cacheMap.original,
name, positions, ln, step, minIdx, maxIdx;
for (name in cacheMap) {
positions = cacheMap[name];
ln = positions[1] - positions[0] - 1;
if (ln === 0) {
continue;
}
step = range / ln;
if (estStep <= step && step < minStep) {
result = positions;
minStep = step;
}
}
minIdx = Math.max(this.binarySearch(cache.startIdx, result[0], result[1], min, true),
result[0]);
maxIdx = Math.min(this.binarySearch(cache.endIdx, result[0], result[1], max, false) + 1,
result[1]);
return {
data: cache,
start: minIdx,
end: maxIdx
};
}
});