/*
|
* Licensed to the Apache Software Foundation (ASF) under one
|
* or more contributor license agreements. See the NOTICE file
|
* distributed with this work for additional information
|
* regarding copyright ownership. The ASF licenses this file
|
* to you under the Apache License, Version 2.0 (the
|
* "License"); you may not use this file except in compliance
|
* with the License. You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing,
|
* software distributed under the License is distributed on an
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
* KIND, either express or implied. See the License for the
|
* specific language governing permissions and limitations
|
* under the License.
|
*/
|
|
/* global Float32Array */
|
|
// TODO Batch by color
|
|
import * as graphic from '../../util/graphic';
|
import {createSymbol} from '../../util/symbol';
|
import IncrementalDisplayable from 'zrender/src/graphic/IncrementalDisplayable';
|
|
var BOOST_SIZE_THRESHOLD = 4;
|
|
var LargeSymbolPath = graphic.extendShape({
|
|
shape: {
|
points: null
|
},
|
|
symbolProxy: null,
|
|
buildPath: function (path, shape) {
|
var points = shape.points;
|
var size = shape.size;
|
|
var symbolProxy = this.symbolProxy;
|
var symbolProxyShape = symbolProxy.shape;
|
var ctx = path.getContext ? path.getContext() : path;
|
var canBoost = ctx && size[0] < BOOST_SIZE_THRESHOLD;
|
|
// Do draw in afterBrush.
|
if (canBoost) {
|
return;
|
}
|
|
for (var i = 0; i < points.length;) {
|
var x = points[i++];
|
var y = points[i++];
|
|
if (isNaN(x) || isNaN(y)) {
|
continue;
|
}
|
|
symbolProxyShape.x = x - size[0] / 2;
|
symbolProxyShape.y = y - size[1] / 2;
|
symbolProxyShape.width = size[0];
|
symbolProxyShape.height = size[1];
|
|
symbolProxy.buildPath(path, symbolProxyShape, true);
|
}
|
},
|
|
afterBrush: function (ctx) {
|
var shape = this.shape;
|
var points = shape.points;
|
var size = shape.size;
|
var canBoost = size[0] < BOOST_SIZE_THRESHOLD;
|
|
if (!canBoost) {
|
return;
|
}
|
|
this.setTransform(ctx);
|
// PENDING If style or other canvas status changed?
|
for (var i = 0; i < points.length;) {
|
var x = points[i++];
|
var y = points[i++];
|
if (isNaN(x) || isNaN(y)) {
|
continue;
|
}
|
// fillRect is faster than building a rect path and draw.
|
// And it support light globalCompositeOperation.
|
ctx.fillRect(
|
x - size[0] / 2, y - size[1] / 2,
|
size[0], size[1]
|
);
|
}
|
|
this.restoreTransform(ctx);
|
},
|
|
findDataIndex: function (x, y) {
|
// TODO ???
|
// Consider transform
|
|
var shape = this.shape;
|
var points = shape.points;
|
var size = shape.size;
|
|
var w = Math.max(size[0], 4);
|
var h = Math.max(size[1], 4);
|
|
// Not consider transform
|
// Treat each element as a rect
|
// top down traverse
|
for (var idx = points.length / 2 - 1; idx >= 0; idx--) {
|
var i = idx * 2;
|
var x0 = points[i] - w / 2;
|
var y0 = points[i + 1] - h / 2;
|
if (x >= x0 && y >= y0 && x <= x0 + w && y <= y0 + h) {
|
return idx;
|
}
|
}
|
|
return -1;
|
}
|
});
|
|
function LargeSymbolDraw() {
|
this.group = new graphic.Group();
|
}
|
|
var largeSymbolProto = LargeSymbolDraw.prototype;
|
|
largeSymbolProto.isPersistent = function () {
|
return !this._incremental;
|
};
|
|
/**
|
* Update symbols draw by new data
|
* @param {module:echarts/data/List} data
|
*/
|
largeSymbolProto.updateData = function (data) {
|
this.group.removeAll();
|
var symbolEl = new LargeSymbolPath({
|
rectHover: true,
|
cursor: 'default'
|
});
|
|
symbolEl.setShape({
|
points: data.getLayout('symbolPoints')
|
});
|
this._setCommon(symbolEl, data);
|
this.group.add(symbolEl);
|
|
this._incremental = null;
|
};
|
|
largeSymbolProto.updateLayout = function (data) {
|
if (this._incremental) {
|
return;
|
}
|
|
var points = data.getLayout('symbolPoints');
|
this.group.eachChild(function (child) {
|
if (child.startIndex != null) {
|
var len = (child.endIndex - child.startIndex) * 2;
|
var byteOffset = child.startIndex * 4 * 2;
|
points = new Float32Array(points.buffer, byteOffset, len);
|
}
|
child.setShape('points', points);
|
});
|
};
|
|
largeSymbolProto.incrementalPrepareUpdate = function (data) {
|
this.group.removeAll();
|
|
this._clearIncremental();
|
// Only use incremental displayables when data amount is larger than 2 million.
|
// PENDING Incremental data?
|
if (data.count() > 2e6) {
|
if (!this._incremental) {
|
this._incremental = new IncrementalDisplayable({
|
silent: true
|
});
|
}
|
this.group.add(this._incremental);
|
}
|
else {
|
this._incremental = null;
|
}
|
};
|
|
largeSymbolProto.incrementalUpdate = function (taskParams, data) {
|
var symbolEl;
|
if (this._incremental) {
|
symbolEl = new LargeSymbolPath();
|
this._incremental.addDisplayable(symbolEl, true);
|
}
|
else {
|
symbolEl = new LargeSymbolPath({
|
rectHover: true,
|
cursor: 'default',
|
startIndex: taskParams.start,
|
endIndex: taskParams.end
|
});
|
symbolEl.incremental = true;
|
this.group.add(symbolEl);
|
}
|
|
symbolEl.setShape({
|
points: data.getLayout('symbolPoints')
|
});
|
this._setCommon(symbolEl, data, !!this._incremental);
|
};
|
|
largeSymbolProto._setCommon = function (symbolEl, data, isIncremental) {
|
var hostModel = data.hostModel;
|
|
// TODO
|
// if (data.hasItemVisual.symbolSize) {
|
// // TODO typed array?
|
// symbolEl.setShape('sizes', data.mapArray(
|
// function (idx) {
|
// var size = data.getItemVisual(idx, 'symbolSize');
|
// return (size instanceof Array) ? size : [size, size];
|
// }
|
// ));
|
// }
|
// else {
|
var size = data.getVisual('symbolSize');
|
symbolEl.setShape('size', (size instanceof Array) ? size : [size, size]);
|
// }
|
|
// Create symbolProxy to build path for each data
|
symbolEl.symbolProxy = createSymbol(
|
data.getVisual('symbol'), 0, 0, 0, 0
|
);
|
// Use symbolProxy setColor method
|
symbolEl.setColor = symbolEl.symbolProxy.setColor;
|
|
var extrudeShadow = symbolEl.shape.size[0] < BOOST_SIZE_THRESHOLD;
|
symbolEl.useStyle(
|
// Draw shadow when doing fillRect is extremely slow.
|
hostModel.getModel('itemStyle').getItemStyle(extrudeShadow ? ['color', 'shadowBlur', 'shadowColor'] : ['color'])
|
);
|
|
var visualColor = data.getVisual('color');
|
if (visualColor) {
|
symbolEl.setColor(visualColor);
|
}
|
|
if (!isIncremental) {
|
// Enable tooltip
|
// PENDING May have performance issue when path is extremely large
|
symbolEl.seriesIndex = hostModel.seriesIndex;
|
symbolEl.on('mousemove', function (e) {
|
symbolEl.dataIndex = null;
|
var dataIndex = symbolEl.findDataIndex(e.offsetX, e.offsetY);
|
if (dataIndex >= 0) {
|
// Provide dataIndex for tooltip
|
symbolEl.dataIndex = dataIndex + (symbolEl.startIndex || 0);
|
}
|
});
|
}
|
};
|
|
largeSymbolProto.remove = function () {
|
this._clearIncremental();
|
this._incremental = null;
|
this.group.removeAll();
|
};
|
|
largeSymbolProto._clearIncremental = function () {
|
var incremental = this._incremental;
|
if (incremental) {
|
incremental.clearDisplaybles();
|
}
|
};
|
|
export default LargeSymbolDraw;
|