diff --git a/src/chart/candlestick/CandlestickSeries.js b/src/chart/candlestick/CandlestickSeries.js index 682cb7ef112337a72605d73d3d2116dcce150b7e..0d2d352f080e477fe585cf957fc0d242fec3b3ff 100644 --- a/src/chart/candlestick/CandlestickSeries.js +++ b/src/chart/candlestick/CandlestickSeries.js @@ -60,6 +60,7 @@ var CandlestickSeries = SeriesModel.extend({ progressive: 5e3, progressiveThreshold: 1e4, + progressiveChunkMode: 'mod', animationUpdate: false, animationEasing: 'linear', diff --git a/src/chart/candlestick/CandlestickView.js b/src/chart/candlestick/CandlestickView.js index 527f62a443d98bbec73663bd76bbbefc200073cc..4200c1cf444a50c31283fa1a5e40839f459e40b8 100644 --- a/src/chart/candlestick/CandlestickView.js +++ b/src/chart/candlestick/CandlestickView.js @@ -45,7 +45,6 @@ var CandlestickView = ChartView.extend({ }, _renderNormal: function (seriesModel) { - // var largePoints = data.getLayout('largePoints'); var data = seriesModel.getData(); var oldData = this._data; var group = this.group; @@ -115,7 +114,8 @@ var CandlestickView = ChartView.extend({ var data = seriesModel.getData(); var isSimpleBox = data.getLayout('isSimpleBox'); - for (var dataIndex = params.start; dataIndex < params.end; dataIndex++) { + var dataIndex; + while ((dataIndex = params.next()) != null) { var el; var itemLayout = data.getItemLayout(dataIndex); diff --git a/src/chart/candlestick/candlestickLayout.js b/src/chart/candlestick/candlestickLayout.js index 3aa8d5c0f0acf1e1c626969a60c33ac3c7e792ab..40f6712ef5ba143a713a46d36df3075b03dbfce7 100644 --- a/src/chart/candlestick/candlestickLayout.js +++ b/src/chart/candlestick/candlestickLayout.js @@ -42,8 +42,8 @@ export default { }; function normalProgress(params, data) { - - for (var dataIndex = params.start; dataIndex < params.end; dataIndex++) { + var dataIndex; + while ((dataIndex = params.next()) != null) { var axisDimVal = data.get(cDim, dataIndex); var openVal = data.get(openDim, dataIndex); @@ -126,15 +126,15 @@ export default { } function largeProgress(params, data) { - var segCount = params.end - params.start; // Structure: [sign, x, yhigh, ylow, sign, x, yhigh, ylow, ...] - var points = new LargeArr(segCount * 5); - - for ( - var dataIndex = params.start, offset = 0, point, tmpIn = [], tmpOut = []; - dataIndex < params.end; - dataIndex++ - ) { + var points = new LargeArr(params.count * 5); + var offset = 0; + var point; + var tmpIn = []; + var tmpOut = []; + var dataIndex; + + while ((dataIndex = params.next()) != null) { var axisDimVal = data.get(cDim, dataIndex); var openVal = data.get(openDim, dataIndex); var closeVal = data.get(closeDim, dataIndex); diff --git a/src/chart/candlestick/candlestickVisual.js b/src/chart/candlestick/candlestickVisual.js index 9273480675c313e167e6601916e3c8ff86354184..abca11e6af8d60783df21a12948d4a9bad07883c 100644 --- a/src/chart/candlestick/candlestickVisual.js +++ b/src/chart/candlestick/candlestickVisual.js @@ -36,7 +36,8 @@ export default { function progress(params, data) { - for (var dataIndex = params.start; dataIndex < params.end; dataIndex++) { + var dataIndex; + while ((dataIndex = params.next()) != null) { var itemModel = data.getItemModel(dataIndex); var sign = data.getItemLayout(dataIndex).sign; diff --git a/src/stream/Scheduler.js b/src/stream/Scheduler.js index 4dfcf803a96cd3b20d3596697098d5376fb8393a..beb5d88ccf000689f28b1836a0fd1ad07cbac576 100644 --- a/src/stream/Scheduler.js +++ b/src/stream/Scheduler.js @@ -93,7 +93,11 @@ proto.getPerformArgs = function (task, isBlock) { && (!pCtx || pCtx.progressiveRender) && task.__idxInPipeline > pipeline.bockIndex; - return {step: incremental ? pipeline.step : null}; + var step = incremental ? pipeline.step : null; + var modDataCount = pCtx && pCtx.modDataCount; + var modBy = modDataCount != null ? Math.ceil(modDataCount / step): null; + + return {step: step, modBy: modBy, modDataCount: modDataCount}; }; proto.getPipeline = function (pipelineId) { @@ -123,8 +127,13 @@ proto.updateStreamModes = function (seriesModel, view) { var large = seriesModel.get('large') && dataLen >= seriesModel.get('largeThreshold'); + // TODO: modDataCount should not updated if `appendData`, otherwise cause whole repaint. + // see `test/candlestick-large3.html` + var modDataCount = seriesModel.get('progressiveChunkMode') === 'mod' ? dataLen : null; + seriesModel.pipelineContext = pipeline.context = { progressiveRender: progressiveRender, + modDataCount: modDataCount, large: large }; }; @@ -145,7 +154,7 @@ proto.restorePipelines = function (ecModel) { progressiveEnabled: progressive && !(seriesModel.preventIncremental && seriesModel.preventIncremental()), bockIndex: -1, - step: progressive || 700, // ??? Temporarily number + step: Math.round(progressive || 700), count: 0 }); diff --git a/src/stream/task.js b/src/stream/task.js index e55b8dd6980d38c86ba6f32f2755e43b3cb2a848..e1d19bc8a6d0d00bf52a0ae7ea0aa995bc4386ab 100644 --- a/src/stream/task.js +++ b/src/stream/task.js @@ -38,6 +38,8 @@ var taskProto = Task.prototype; * @param {Object} performArgs * @param {number} [performArgs.step] Specified step. * @param {number} [performArgs.skip] Skip customer perform call. + * @param {number} [performArgs.modBy] Sampling window size. + * @param {number} [performArgs.modDataCount] Sampling count. */ taskProto.perform = function (performArgs) { var upTask = this._upstream; @@ -60,12 +62,30 @@ taskProto.perform = function (performArgs) { planResult = this._plan(this.context); } + // Support sharding by mod, which changes the render sequence and makes the rendered graphic + // elements uniformed distributed when progress, especially when moving or zooming. + var lastModBy = normalizeModBy(this._modBy); + var lastModDataCount = this._modDataCount || 0; + var modBy = normalizeModBy(performArgs && performArgs.modBy); + var modDataCount = performArgs && performArgs.modDataCount || 0; + if (lastModBy !== modBy || lastModDataCount !== modDataCount) { + planResult = 'reset'; + } + + function normalizeModBy(val) { + !(val >= 1) && (val = 1); // jshint ignore:line + return val; + } + var forceFirstProgress; if (this._dirty || planResult === 'reset') { this._dirty = false; forceFirstProgress = reset(this, skip); } + this._modBy = modBy; + this._modDataCount = modDataCount; + var step = performArgs && performArgs.step; if (upTask) { @@ -92,9 +112,12 @@ taskProto.perform = function (performArgs) { this._dueEnd ); - !skip && (forceFirstProgress || start < end) && ( - this._progress({start: start, end: end}, this.context) - ); + if (!skip && (forceFirstProgress || start < end)) { + iterator.reset(start, end, modBy, modDataCount); + this._progress({ + start: start, end: end, step: 1, count: end - start, next: iterator.next + }, this.context); + } this._dueIndex = end; // If no `outputDueEnd`, assume that output data and @@ -120,6 +143,47 @@ taskProto.perform = function (performArgs) { return this.unfinished(); }; +var iterator = (function () { + + var end; + var current; + var modBy; + var modDataCount; + var winCount; + + var it = { + reset: function (s, e, sStep, sCount) { + current = s; + end = e; + + modBy = sStep; + modDataCount = sCount; + winCount = Math.ceil(modDataCount / modBy); + + it.next = (modBy > 1 && modDataCount > 0) ? modNext : sequentialNext; + } + }; + + return it; + + function sequentialNext() { + return current < end ? current++ : null; + } + + function modNext() { + var dataIndex = (current % winCount) * modBy + Math.ceil(current / winCount); + var result = current >= end + ? null + : dataIndex < modDataCount + ? dataIndex + // If modDataCount is smaller than data.count() (consider `appendData` case), + // Use normal linear rendering mode. + : current; + current++; + return result; + } +})(); + taskProto.dirty = function () { this._dirty = true; this._onDirty && this._onDirty(this.context); @@ -144,6 +208,7 @@ function reset(taskIns, skip) { } taskIns._progress = progress; + taskIns._modBy = taskIns._modDataCount = null; var downstream = taskIns._downstream; downstream && downstream.dirty(); diff --git a/test/candlestick-large2.html b/test/candlestick-large2.html index ec9997118e60781d8fa3ceff8ac6f5229d9b5e29..9c4019b2df4bb03a2ceeab1ebd9925c9fde7e577 100644 --- a/test/candlestick-large2.html +++ b/test/candlestick-large2.html @@ -3,11 +3,12 @@ - + + @@ -21,6 +22,7 @@
+
@@ -28,14 +30,15 @@ diff --git a/test/candlestick-large3.html b/test/candlestick-large3.html index ca595ae1b92d8a14d7718e1ce62380a15267293f..63612014e981b07b6665f57f1e33c46acfaed8cf 100644 --- a/test/candlestick-large3.html +++ b/test/candlestick-large3.html @@ -33,20 +33,54 @@ require(['echarts'], function (echarts) { // The data count is from a real requirement. - var rawDataCount = 2e5; + var rawDataChunkSize = 1e4; + var chunkCount = 20; + + var minute = 60 * 1000; + var xValue = +new Date(2011, 0, 1); + var baseValue = Math.random() * 12000; + var xValueMin = 0; + var xValueMax = rawDataChunkSize * chunkCount; + var yValueMin = Infinity; + var yValueMax = -Infinity; + + var rawData = []; + for (var i = 0; i < chunkCount; i++) { + rawData.push(generateOHLC(rawDataChunkSize)); + } + yValueMax = Math.ceil(yValueMax); + yValueMin = Math.floor(yValueMin); function run() { - var data = generateOHLC(rawDataCount); - // var result = reorder(data); - init(data); + + frameInsight.init(echarts, 'duration'); + + // var data = generateOHLC(rawDataChunkSize); + var chart = window.chart = init(); + + var loadedChunkIndex = 0; + + appendData(); + + function appendData() { + if (loadedChunkIndex >= chunkCount) { + return; + } + + setTimeout(function () { + + chart.appendData({seriesIndex: 0, data: rawData[loadedChunkIndex]}); + + loadedChunkIndex++; + + appendData(); + }, 300); + } } function generateOHLC(count) { var data = []; - var xValue = +new Date(2011, 0, 1); - var minute = 60 * 1000; - var baseValue = Math.random() * 12000; var tmpVals = new Array(4); var dayRange = 12; @@ -55,6 +89,12 @@ for (var j = 0; j < 4; j++) { tmpVals[j] = (Math.random() - 0.5) * dayRange + baseValue; + if (tmpVals[j] < yValueMin) { + yValueMin = tmpVals[j]; + } + if (tmpVals[j] > yValueMax) { + yValueMax = tmpVals[j]; + } } tmpVals.sort(); @@ -95,14 +135,9 @@ return result; } - function init(rawData) { - - frameInsight.init(echarts, 'duration'); + function init() { var option = { - dataset: { - source: rawData - }, backgroundColor: '#eee', // animation: false, legend: { @@ -153,8 +188,8 @@ axisLine: {onZero: false}, splitLine: {show: false}, splitNumber: 20, - min: 'dataMin', - max: 'dataMax' + min: xValueMin, + max: xValueMax }, // { // type: 'category', @@ -176,7 +211,9 @@ scale: true, splitArea: { show: true - } + }, + min: yValueMin, + max: yValueMax }, // { // scale: true, @@ -192,27 +229,28 @@ { type: 'inside', // xAxisIndex: [0, 1], - start: 10, - end: 100 + // start: 10, + // end: 100 }, { show: true, // xAxisIndex: [0, 1], type: 'slider', bottom: 10, - start: 10, - end: 100 + // start: 10, + // end: 100 } ], series: [ { - name: 'Fake index', type: 'candlestick', + // progressiveMode: 'linear', // data: data, encode: { x: 0, y: [1, 4, 3, 2] }, + // progressiveChunkMode: 'sequential' // progressive: false // progressive: progressive // tooltip: { @@ -276,11 +314,13 @@ var panel = document.getElementById('panel0'); var chart = testHelper.create(echarts, 'main0', { - title: 'Fake OHLC data', + title: 'Append data and progressive by mod', option: option, height: 550 }); + return chart; + // chart && chart.on('brushSelected', renderBrushed); // function renderBrushed(params) {