diff --git a/src/chart/effectScatter/EffectScatterView.js b/src/chart/effectScatter/EffectScatterView.js index aed34a6cc61565dec24ca535704283ba3f782bc4..1233bb1cc244df71774891586084f353ada13b13 100644 --- a/src/chart/effectScatter/EffectScatterView.js +++ b/src/chart/effectScatter/EffectScatterView.js @@ -3,6 +3,8 @@ import SymbolDraw from '../helper/SymbolDraw'; import EffectSymbol from '../helper/EffectSymbol'; import * as matrix from 'zrender/src/core/matrix'; +import pointsLayout from '../../layout/points'; + export default echarts.extendChartView({ type: 'effectScatter', @@ -12,8 +14,6 @@ export default echarts.extendChartView({ }, render: function (seriesModel, ecModel, api) { - this._removeRoamTransformInPoints(seriesModel); - var data = seriesModel.getData(); var effectSymbolDraw = this._symbolDraw; effectSymbolDraw.updateData(data); @@ -21,18 +21,16 @@ export default echarts.extendChartView({ }, updateTransform: function (seriesModel, ecModel, api) { - var coordSys = seriesModel.coordinateSystem; - // Must mark group dirty and make sure the incremental layer will be cleared - // PENDING + var data = seriesModel.getData(); + this.group.dirty(); - if (coordSys.getRoamTransform) { - this._updateGroupTransform(seriesModel); - } - else { - return { - update: true - }; + + var res = pointsLayout().reset(seriesModel); + if (res.progress) { + res.progress({ start: 0, end: data.count() }, seriesModel); } + + this._symbolDraw.updateLayout(data); }, _updateGroupTransform: function (seriesModel) { @@ -43,17 +41,6 @@ export default echarts.extendChartView({ } }, - _removeRoamTransformInPoints: function (seriesModel) { - var coordSys = seriesModel.coordinateSystem; - if (coordSys && coordSys.removeRoamTransformInPoint) { - var data = seriesModel.getData(); - data.each(function (i) { - var pt = data.getItemLayout(i); - coordSys.removeRoamTransformInPoint(pt); - }); - } - }, - remove: function (ecModel, api) { this._symbolDraw && this._symbolDraw.remove(api); }, diff --git a/src/chart/heatmap/HeatmapView.js b/src/chart/heatmap/HeatmapView.js index 29e74edc3a5e1984edfdb1e0c115803675e13e29..3309299c0eea26ba66c732a2c0400bf3691a74a5 100644 --- a/src/chart/heatmap/HeatmapView.js +++ b/src/chart/heatmap/HeatmapView.js @@ -153,7 +153,7 @@ export default echarts.extendChartView({ if (coordSysType === 'cartesian2d') { // Ignore empty data if (isNaN(data.get(dataDims[2], idx))) { - return; + continue; } var point = coordSys.dataToPoint([ @@ -177,7 +177,7 @@ export default echarts.extendChartView({ else { // Ignore empty data if (isNaN(data.get(dataDims[1], idx))) { - return; + continue; } rect = new graphic.Rect({ diff --git a/src/chart/helper/LargeSymbolDraw.js b/src/chart/helper/LargeSymbolDraw.js index 98d31e9d27dabd2e352d0e25f3ab3a65674d0e38..be89ad1bf586602080bdee01e721f0bb20013347 100644 --- a/src/chart/helper/LargeSymbolDraw.js +++ b/src/chart/helper/LargeSymbolDraw.js @@ -127,6 +127,19 @@ largeSymbolProto.updateData = function (data) { this._incremental = null; }; +largeSymbolProto.updateLayout = function (data) { + if (this._incremental) { + return; + } + + var points = data.getLayout('symbolPoints'); + this.group.eachChild(function (child) { + var len = (child.endIndex - child.startIndex) * 2; + var byteOffset = child.startIndex * 4 * 2; + child.setShape('points', new Float32Array(points.buffer, byteOffset, len)); + }); +}; + largeSymbolProto.incrementalPrepareUpdate = function (data) { this.group.removeAll(); @@ -155,10 +168,11 @@ largeSymbolProto.incrementalUpdate = function (taskParams, data) { else { symbolEl = new LargeSymbolPath({ rectHover: true, - cursor: 'default' + cursor: 'default', + startIndex: taskParams.start, + endIndex: taskParams.end }); symbolEl.incremental = true; - symbolEl.__startIndex = taskParams.start; this.group.add(symbolEl); } @@ -210,7 +224,7 @@ largeSymbolProto._setCommon = function (symbolEl, data, isIncremental) { var dataIndex = symbolEl.findDataIndex(e.offsetX, e.offsetY); if (dataIndex >= 0) { // Provide dataIndex for tooltip - symbolEl.dataIndex = dataIndex + symbolEl.__startIndex; + symbolEl.dataIndex = dataIndex + symbolEl.startIndex; } }); } diff --git a/src/chart/scatter.js b/src/chart/scatter.js index b47a3e608258380e680f547858925f60bdebb406..fc339c719afe7b10c11d7120137ceaea487edc1f 100644 --- a/src/chart/scatter.js +++ b/src/chart/scatter.js @@ -1,11 +1,9 @@ import * as echarts from '../echarts'; +import * as zrUtil from 'zrender/src/core/util'; import './scatter/ScatterSeries'; import './scatter/ScatterView'; -// import './scatter/StreamScatterSeries'; -// import './scatter/StreamScatterView'; - import visualSymbol from '../visual/symbol'; import layoutPoints from '../layout/points'; @@ -15,5 +13,23 @@ import '../component/gridSimple'; echarts.registerVisual(visualSymbol('scatter', 'circle')); echarts.registerLayout(layoutPoints('scatter')); -// echarts.registerVisual(zrUtil.curry(visualSymbol, 'streamScatter', 'circle', null)); -// echarts.registerLayout(zrUtil.curry(layoutPoints, 'streamScatter')); +// echarts.registerProcessor(function (ecModel, api) { +// ecModel.eachSeriesByType('scatter', function (seriesModel) { +// var data = seriesModel.getData(); +// var coordSys = seriesModel.coordinateSystem; +// if (coordSys.type !== 'geo') { +// return; +// } +// var startPt = coordSys.pointToData([0, 0]); +// var endPt = coordSys.pointToData([api.getWidth(), api.getHeight()]); + +// var dims = zrUtil.map(coordSys.dimensions, function (dim) { +// return seriesModel.coordDimToDataDim(dim)[0]; +// }); +// var range = {}; +// range[dims[0]] = [Math.min(startPt[0], endPt[0]), Math.max(startPt[0], endPt[0])]; +// range[dims[1]] = [Math.min(startPt[1], endPt[1]), Math.max(startPt[1], endPt[1])]; + +// data.selectRange(range); +// }); +// }); \ No newline at end of file diff --git a/src/chart/scatter/ScatterView.js b/src/chart/scatter/ScatterView.js index e8e46baf26407f877739d19c83ac0e7f0d1bbace..3fdb2b335382d59e23793c5f687eb24ce388058d 100644 --- a/src/chart/scatter/ScatterView.js +++ b/src/chart/scatter/ScatterView.js @@ -3,14 +3,14 @@ import SymbolDraw from '../helper/SymbolDraw'; import LargeSymbolDraw from '../helper/LargeSymbolDraw'; import * as matrix from 'zrender/src/core/matrix'; +import pointsLayout from '../../layout/points'; + echarts.extendChartView({ type: 'scatter', render: function (seriesModel, ecModel, api) { var data = seriesModel.getData(); - this._removeRoamTransformInPoints(seriesModel, 0, data.count()); - this._updateGroupTransform(seriesModel); var symbolDraw = this._updateSymbolDraw(data, seriesModel); symbolDraw.updateData(data); @@ -19,8 +19,6 @@ echarts.extendChartView({ }, incrementalPrepareRender: function (seriesModel, ecModel, api) { - this._updateGroupTransform(seriesModel); - var data = seriesModel.getData(); var symbolDraw = this._updateSymbolDraw(data, seriesModel); @@ -30,61 +28,29 @@ echarts.extendChartView({ }, incrementalRender: function (taskParams, seriesModel, ecModel) { - this._removeRoamTransformInPoints(seriesModel, taskParams.start, taskParams.end); this._symbolDraw.incrementalUpdate(taskParams, seriesModel.getData()); this._finished = taskParams.end === seriesModel.getData().count(); }, updateTransform: function (seriesModel, ecModel, api) { - var coordSys = seriesModel.coordinateSystem; - var update = true; + var data = seriesModel.getData(); // Must mark group dirty and make sure the incremental layer will be cleared // PENDING this.group.dirty(); - if (coordSys.getRoamTransform) { - update = false; - this._updateGroupTransform(seriesModel); - } - if (update || !this._finished || !this._symbolDraw.isPersistent()) { + if (!this._finished || data.count() > 2e5 || !this._symbolDraw.isPersistent()) { return { update: true }; } - }, - - _updateGroupTransform: function (seriesModel) { - var coordSys = seriesModel.coordinateSystem; - if (coordSys && coordSys.getRoamTransform) { - this.group.transform = matrix.clone(coordSys.getRoamTransform()); - this.group.decomposeTransform(); - } - }, - - _removeRoamTransformInPoints: function (seriesModel, start, end) { - var coordSys = seriesModel.coordinateSystem; - if (coordSys && coordSys.removeRoamTransformInPoint) { - var data = seriesModel.getData(); - var pt = []; - if (seriesModel.pipelineContext.large) { - var points = data.getLayout('symbolPoints'); - if (points) { - for (var i = 0; i < points.length; i += 2) { - pt[0] = points[i]; - pt[1] = points[i + 1]; - coordSys.removeRoamTransformInPoint(pt); - points[i] = pt[0]; - points[i + 1] = pt[1]; - } - } - } - else { - for (var i = start; i < end; i++) { - var pt = data.getItemLayout(i); - coordSys.removeRoamTransformInPoint(pt); - } + else { + var res = pointsLayout().reset(seriesModel); + if (res.progress) { + res.progress({ start: 0, end: data.count() }, seriesModel); } + + this._symbolDraw.updateLayout(data); } }, diff --git a/src/coord/View.js b/src/coord/View.js index d52850c58ac655bd943ea5730ec6f2e28e2079f2..2655876de5719e9bcf9a5ec26e041ca9414b4de3 100644 --- a/src/coord/View.js +++ b/src/coord/View.js @@ -163,12 +163,6 @@ View.prototype = { return this._roamTransformable.getLocalTransform(); }, - removeRoamTransformInPoint: function (pt) { - if (this._roamTransformable.invTransform) { - vector.applyTransform(pt, pt, this._roamTransformable.invTransform); - } - }, - /** * Remove roam */ diff --git a/src/data/List.js b/src/data/List.js index cafc4d40bb1be93a493c69ad20de52b5ebf53229..53b5ba39e23c70d6d133af39ea7a302acc2ade31 100644 --- a/src/data/List.js +++ b/src/data/List.js @@ -41,7 +41,7 @@ function cloneChunk(originalChunk) { var TRANSFERABLE_PROPERTIES = [ 'stackedOn', 'hasItemOption', '_nameList', '_idList', - '_rawData', '_rawExtent', '_chunkSize', '_dimValueGetter', '_count' + '_rawData', '_rawExtent', '_chunkSize', '_chunkCount', '_dimValueGetter', '_count' ]; function transferProperties(a, b) { @@ -82,10 +82,13 @@ function DefaultDataProvider(dataArray, dimSize) { zrUtil.extend(this, methods); } +var providerProto = DefaultDataProvider.prototype; // If data is pure without style configuration -DefaultDataProvider.prototype.pure = false; +providerProto.pure = false; // If data is persistent and will not be released after use. -DefaultDataProvider.prototype.persistent = true; +providerProto.persistent = true; +// If data is isTypedArray. +providerProto.isTypedArray = false; var normalProviderMethods = { @@ -109,6 +112,8 @@ var typedArrayProviderMethods = { pure: true, + isTypedArray: true, + count: function () { return this._array ? (this._array.length / this._dimSize) : 0; }, @@ -562,7 +567,7 @@ listProto._initDataFromProvider = function (start, end) { nameList[idx] = name; // Try using the id in option - var id = dataItem && dataItem.id; + var id = dataItem == null ? null : dataItem.id; if (id == null && name != null) { // Use name as id and add counter to avoid same name @@ -583,6 +588,9 @@ listProto._initDataFromProvider = function (start, end) { } this._count = end; + + // Reset data extent + this._extent = {}; }; /** @@ -614,7 +622,7 @@ listProto.getIndices = function () { * @return {number} */ listProto.get = function (dim, idx, stack) { - if (idx < 0 || idx >= this._count) { + if (!(idx >= 0 && idx < this._count)) { return NaN; } var storage = this._storage; @@ -716,23 +724,20 @@ listProto.getDataExtent = function (dim, stack) { var dimData = this._storage[dim]; var initialExtent = [Infinity, -Infinity]; - stack = stack || false; + stack = (stack || false) && isStacked(this, dim); if (!dimData) { return initialExtent; } // Make more strict checkings to ensure hitting cache. - var stacked = isStacked(this, dim); var currEnd = this.count(); - // Consider that the data is incremental, the extent should be - // cached by not only dim, but also the end index. - var cacheName = [dim, !!(stack && stacked), currEnd].join('_'); + var cacheName = [dim, !!stack].join('_'); // Consider the most cases when using data zoom, `getDataExtent` // happened before filtering. We cache raw extent, which is not // necessary to be cleared and recalculated when restore data. - var useRaw = !this._indices && !stacked; + var useRaw = !this._indices && !stack; var dimExtent; if (useRaw) { @@ -1006,6 +1011,10 @@ function validateDimensions(list, dims) { listProto.each = function (dims, cb, stack, context) { 'use strict'; + if (!this._count) { + return; + } + if (typeof dims === 'function') { context = stack; stack = cb; @@ -1058,6 +1067,10 @@ listProto.each = function (dims, cb, stack, context) { listProto.filterSelf = function (dimensions, cb, stack, context) { 'use strict'; + if (!this._count) { + return; + } + if (typeof dimensions === 'function') { context = stack; stack = cb; @@ -1124,10 +1137,15 @@ listProto.filterSelf = function (dimensions, cb, stack, context) { /** * Select data in range. (For optimization of filter) + * (Manually inline code, support 5 million data filtering in data zoom.) */ listProto.selectRange = function (range, stack) { 'use strict'; + if (!this._count) { + return; + } + stack = stack || false; var dimensions = []; @@ -1153,31 +1171,74 @@ listProto.selectRange = function (range, stack) { var offset = 0; var dim0 = dimensions[0]; - if (dimSize === 1) { - var min = range[dim0][0]; - var max = range[dim0][1]; - for (var i = 0; i < originalCount; i++) { - var rawIndex = this.getRawIndex(i); - var val = stack ? this.get(dim0, i, true) : this._getFast(dim0, rawIndex); - - if (val >= min && val <= max) { - newIndices[offset++] = rawIndex; + var min = range[dim0][0]; + var max = range[dim0][1]; + + var quickFinished = false; + if (!this._indices && !stack) { + // Extreme optimization for common case. About 2x faster in chrome. + var idx = 0; + if (dimSize === 1) { + var dimStorage = this._storage[dimensions[0]]; + for (var k = 0; k < this._chunkCount; k++) { + var chunkStorage = dimStorage[k]; + var len = Math.min(this._count - k * this._chunkSize, this._chunkSize); + for (var i = 0; i < len; i++) { + var val = chunkStorage[i]; + if (val >= min && val <= max) { + newIndices[offset++] = idx; + } + idx++; + } } + quickFinished = true; + } + else if (dimSize === 2) { + var dimStorage = this._storage[dim0]; + var dimStorage2 = this._storage[dimensions[1]]; + var min2 = range[dimensions[1]][0]; + var max2 = range[dimensions[1]][1]; + for (var k = 0; k < this._chunkCount; k++) { + var chunkStorage = dimStorage[k]; + var chunkStorage2= dimStorage2[k]; + var len = Math.min(this._count - k * this._chunkSize, this._chunkSize); + for (var i = 0; i < len; i++) { + var val = chunkStorage[i]; + var val2 = chunkStorage2[i]; + if (val >= min && val <= max && val2 >= min2 && val2 <= max2) { + newIndices[offset++] = idx; + } + idx++; + } + } + quickFinished = true; } } - else { - for (var i = 0; i < originalCount; i++) { - var keep = true; - var rawIndex = this.getRawIndex(i); - for (var k = 0; k < dimSize; k++) { - var dimk = dimensions[k]; - var val = stack ? this.get(dimk, i, true) : this._getFast(dim, rawIndex); - if (val < range[dimk][0] || val > range[dimk][1]) { - keep = false; + if (!quickFinished) { + if (dimSize === 1) { + stack = stack || isStacked(this, dim0); + for (var i = 0; i < originalCount; i++) { + var rawIndex = this.getRawIndex(i); + var val = stack ? this.get(dim0, i, true) : this._getFast(dim0, rawIndex); + if (val >= min && val <= max) { + newIndices[offset++] = rawIndex; } } - if (keep) { - newIndices[offset++] = this.getRawIndex(i); + } + else { + for (var i = 0; i < originalCount; i++) { + var keep = true; + var rawIndex = this.getRawIndex(i); + for (var k = 0; k < dimSize; k++) { + var dimk = dimensions[k]; + var val = stack ? this.get(dimk, i, true) : this._getFast(dim, rawIndex); + if (val < range[dimk][0] || val > range[dimk][1]) { + keep = false; + } + } + if (keep) { + newIndices[offset++] = this.getRawIndex(i); + } } } } @@ -1213,10 +1274,6 @@ listProto.mapArray = function (dimensions, cb, stack, context) { dimensions = []; } - if (__DEV__) { - validateDimensions(this, dimensions); - } - var result = []; this.each(dimensions, function () { result.push(cb && cb.apply(this, arguments)); @@ -1643,6 +1700,10 @@ listProto.TRANSFERABLE_METHODS = ['cloneShallow', 'downSample', 'map']; // listProto.CHANGABLE_METHODS = ['filterSelf']; listProto.defaultDimValueGetter = function (dataItem, dimName, dataIndex, dimIndex) { + if (this._rawData.isTypedArray) { + return dataIndex[dimIndex]; + } + var value = modelUtil.getDataItemValue(dataItem); // If any dataItem is like { value: 10 } if (!this._rawData.pure && modelUtil.isDataItemOption(dataItem)) { diff --git a/src/echarts.js b/src/echarts.js index 02681b2a964348f52853854a7eb01e035f81bc08..1ac751cf9024208cd0256313995017fc45aaf9a3 100644 --- a/src/echarts.js +++ b/src/echarts.js @@ -1021,7 +1021,14 @@ echartsProto.resize = function (opts) { var ecModel = this._model; - var optionChanged = ecModel && ecModel.resetOption('media'); + // Resize loading effect + this._loadingFX && this._loadingFX.resize(); + + if (!ecModel) { + return; + } + + var optionChanged = ecModel.resetOption('media'); optionChanged && ecModel.settingTask.dirty(); @@ -1042,9 +1049,6 @@ function refresh(ecIns, needPrepare, silent) { needPrepare && prepare(ecIns); updateMethods.update.call(ecIns); - // Resize loading effect - ecIns._loadingFX && ecIns._loadingFX.resize(); - ecIns[IN_MAIN_PROCESS] = false; flushPendingActions.call(ecIns, silent); diff --git a/src/visual/dataColor.js b/src/visual/dataColor.js index 28b843993fe882e7f428df5cf2ee778d94900080..c407185c3e29c888c532d83848008062f20dfc33 100644 --- a/src/visual/dataColor.js +++ b/src/visual/dataColor.js @@ -11,7 +11,7 @@ export default function (seriesType) { ecModel.eachSeriesByType(seriesType, function (seriesModel) { seriesModel.__paletteScope = paletteScope; - seriesModel.push(seriesModel); + seiresModels.push(seriesModel); }); return seiresModels; }, diff --git a/test/scatter-random-stream.html b/test/scatter-random-stream.html index 0c2e9106bf98b4d55146e8c9eafca971798da511..d87a1646ea00a97e7d33450eb8b09ab29a9b8e47 100644 --- a/test/scatter-random-stream.html +++ b/test/scatter-random-stream.html @@ -33,20 +33,27 @@ var chunkCount = 0; function genData1(len, offset) { + // console.profile('gen'); var lngRange = [-10.781327, 131.48]; var latRange = [18.252847, 52.33]; - var data = []; - while (len--) { - var x = +(Math.random() * 10).toFixed(3); - data.push([ - x, - +(Math.sin(x) - x * (len % 2 ? 0.1 : -0.1) * Math.random()).toFixed(3) + (offset || 0) / 10, - Math.random() * 100 - ]); + var arr = new Float32Array(len * 2); + var off = 0; + + // var data = []; + for (var i = 0; i < len; i++) { + var x = +Math.random() * 10; + var y = +Math.sin(x) - x * (len % 2 ? 0.1 : -0.1) * Math.random() + (offset || 0) / 10; + arr[off++] = x; + arr[off++] = y; + // data.push([ + // x, + // y + // ]); } - - return data; + // console.profileEnd('gen'); + return arr; + // return data; } function genData2(count) { @@ -118,14 +125,20 @@ name: 'pm2.5', type: 'scatter', large: true, - data: genData1(1e6), + data: genData1(5e6), + dimensions: ['x', 'y'], + encode: { + x: 0, + y: 1 + }, xAxisIndex: 0, yAxisIndex: 0, - symbolSize: 2, + symbolSize: 1, // symbol: 'rect', itemStyle: { normal: { - color: '#128de3' + color: '#128de3', + opacity: 0.2 } }, progressive: 3000 @@ -161,7 +174,7 @@ window.onresize = chart.resize; - next(); + // next(); function next() { if (chunkCount++ < chunkMax) {