hefeihvac_java/node_modules/echarts-gl/lib/chart/common/PointsBuilder.js

548 lines
16 KiB
JavaScript
Raw Normal View History

2024-04-07 18:15:00 +08:00
import * as echarts from 'echarts/lib/echarts';
import graphicGL from '../../util/graphicGL';
import spriteUtil from '../../util/sprite';
import PointsMesh from './PointsMesh';
import LabelsBuilder from '../../component/common/LabelsBuilder';
import Matrix4 from 'claygl/src/math/Matrix4';
import retrieve from '../../util/retrieve';
import { getItemVisualColor, getItemVisualOpacity } from '../../util/visual';
import { getVisualColor, getVisualOpacity } from '../../util/visual';
var SDF_RANGE = 20;
var Z_2D = -10;
function isSymbolSizeSame(a, b) {
return a && b && a[0] === b[0] && a[1] === b[1];
} // TODO gl_PointSize has max value.
function PointsBuilder(is2D, api) {
this.rootNode = new graphicGL.Node();
/**
* @type {boolean}
*/
this.is2D = is2D;
this._labelsBuilder = new LabelsBuilder(256, 256, api); // Give a large render order.
this._labelsBuilder.getMesh().renderOrder = 100;
this.rootNode.add(this._labelsBuilder.getMesh());
this._api = api;
this._spriteImageCanvas = document.createElement('canvas');
this._startDataIndex = 0;
this._endDataIndex = 0;
this._sizeScale = 1;
}
PointsBuilder.prototype = {
constructor: PointsBuilder,
/**
* If highlight on over
*/
highlightOnMouseover: true,
update: function (seriesModel, ecModel, api, start, end) {
// Swap barMesh
var tmp = this._prevMesh;
this._prevMesh = this._mesh;
this._mesh = tmp;
var data = seriesModel.getData();
if (start == null) {
start = 0;
}
if (end == null) {
end = data.count();
}
this._startDataIndex = start;
this._endDataIndex = end - 1;
if (!this._mesh) {
var material = this._prevMesh && this._prevMesh.material;
this._mesh = new PointsMesh({
// Render after axes
renderOrder: 10,
// FIXME
frustumCulling: false
});
if (material) {
this._mesh.material = material;
}
}
var material = this._mesh.material;
var geometry = this._mesh.geometry;
var attributes = geometry.attributes;
this.rootNode.remove(this._prevMesh);
this.rootNode.add(this._mesh);
this._setPositionTextureToMesh(this._mesh, this._positionTexture);
var symbolInfo = this._getSymbolInfo(seriesModel, start, end);
var dpr = api.getDevicePixelRatio(); // TODO image symbol
var itemStyle = seriesModel.getModel('itemStyle').getItemStyle();
var largeMode = seriesModel.get('large');
var pointSizeScale = 1;
if (symbolInfo.maxSize > 2) {
pointSizeScale = this._updateSymbolSprite(seriesModel, itemStyle, symbolInfo, dpr);
material.enableTexture('sprite');
} else {
material.disableTexture('sprite');
}
attributes.position.init(end - start);
var rgbaArr = [];
if (largeMode) {
material.undefine('VERTEX_SIZE');
material.undefine('VERTEX_COLOR');
var color = getVisualColor(data);
var opacity = getVisualOpacity(data);
graphicGL.parseColor(color, rgbaArr);
rgbaArr[3] *= opacity;
material.set({
color: rgbaArr,
'u_Size': symbolInfo.maxSize * this._sizeScale
});
} else {
material.set({
color: [1, 1, 1, 1]
});
material.define('VERTEX_SIZE');
material.define('VERTEX_COLOR');
attributes.size.init(end - start);
attributes.color.init(end - start);
this._originalOpacity = new Float32Array(end - start);
}
var points = data.getLayout('points');
var positionArr = attributes.position.value;
var hasTransparentPoint = false;
for (var i = 0; i < end - start; i++) {
var i3 = i * 3;
var i2 = i * 2;
if (this.is2D) {
positionArr[i3] = points[i2];
positionArr[i3 + 1] = points[i2 + 1];
positionArr[i3 + 2] = Z_2D;
} else {
positionArr[i3] = points[i3];
positionArr[i3 + 1] = points[i3 + 1];
positionArr[i3 + 2] = points[i3 + 2];
}
if (!largeMode) {
var color = getItemVisualColor(data, i);
var opacity = getItemVisualOpacity(data, i);
graphicGL.parseColor(color, rgbaArr);
rgbaArr[3] *= opacity;
attributes.color.set(i, rgbaArr);
if (rgbaArr[3] < 0.99) {
hasTransparentPoint = true;
}
var symbolSize = data.getItemVisual(i, 'symbolSize');
symbolSize = symbolSize instanceof Array ? Math.max(symbolSize[0], symbolSize[1]) : symbolSize; // NaN pointSize may have strange result.
if (isNaN(symbolSize)) {
symbolSize = 0;
} // Scale point size because canvas has margin.
attributes.size.value[i] = symbolSize * pointSizeScale * this._sizeScale; // Save the original opacity for recover from fadeIn.
this._originalOpacity[i] = rgbaArr[3];
}
}
this._mesh.sizeScale = pointSizeScale;
geometry.updateBoundingBox();
geometry.dirty(); // Update material.
this._updateMaterial(seriesModel, itemStyle);
var coordSys = seriesModel.coordinateSystem;
if (coordSys && coordSys.viewGL) {
var methodName = coordSys.viewGL.isLinearSpace() ? 'define' : 'undefine';
material[methodName]('fragment', 'SRGB_DECODE');
}
if (!largeMode) {
this._updateLabelBuilder(seriesModel, start, end);
}
this._updateHandler(seriesModel, ecModel, api);
this._updateAnimation(seriesModel);
this._api = api;
},
getPointsMesh: function () {
return this._mesh;
},
updateLabels: function (highlightDataIndices) {
this._labelsBuilder.updateLabels(highlightDataIndices);
},
hideLabels: function () {
this.rootNode.remove(this._labelsBuilder.getMesh());
},
showLabels: function () {
this.rootNode.add(this._labelsBuilder.getMesh());
},
dispose: function () {
this._labelsBuilder.dispose();
},
_updateSymbolSprite: function (seriesModel, itemStyle, symbolInfo, dpr) {
symbolInfo.maxSize = Math.min(symbolInfo.maxSize * 2, 200);
var symbolSize = [];
if (symbolInfo.aspect > 1) {
symbolSize[0] = symbolInfo.maxSize;
symbolSize[1] = symbolInfo.maxSize / symbolInfo.aspect;
} else {
symbolSize[1] = symbolInfo.maxSize;
symbolSize[0] = symbolInfo.maxSize * symbolInfo.aspect;
} // In case invalid data.
symbolSize[0] = symbolSize[0] || 1;
symbolSize[1] = symbolSize[1] || 1;
if (this._symbolType !== symbolInfo.type || !isSymbolSizeSame(this._symbolSize, symbolSize) || this._lineWidth !== itemStyle.lineWidth) {
spriteUtil.createSymbolSprite(symbolInfo.type, symbolSize, {
fill: '#fff',
lineWidth: itemStyle.lineWidth,
stroke: 'transparent',
shadowColor: 'transparent',
minMargin: Math.min(symbolSize[0] / 2, 10)
}, this._spriteImageCanvas);
spriteUtil.createSDFFromCanvas(this._spriteImageCanvas, Math.min(this._spriteImageCanvas.width, 32), SDF_RANGE, this._mesh.material.get('sprite').image);
this._symbolType = symbolInfo.type;
this._symbolSize = symbolSize;
this._lineWidth = itemStyle.lineWidth;
}
return this._spriteImageCanvas.width / symbolInfo.maxSize * dpr;
},
_updateMaterial: function (seriesModel, itemStyle) {
var blendFunc = seriesModel.get('blendMode') === 'lighter' ? graphicGL.additiveBlend : null;
var material = this._mesh.material;
material.blend = blendFunc;
material.set('lineWidth', itemStyle.lineWidth / SDF_RANGE);
var strokeColor = graphicGL.parseColor(itemStyle.stroke);
material.set('strokeColor', strokeColor); // Because of symbol texture, we always needs it be transparent.
material.transparent = true;
material.depthMask = false;
material.depthTest = !this.is2D;
material.sortVertices = !this.is2D;
},
_updateLabelBuilder: function (seriesModel, start, end) {
var data = seriesModel.getData();
var geometry = this._mesh.geometry;
var positionArr = geometry.attributes.position.value;
var start = this._startDataIndex;
var pointSizeScale = this._mesh.sizeScale;
this._labelsBuilder.updateData(data, start, end);
this._labelsBuilder.getLabelPosition = function (dataIndex, positionDesc, distance) {
var idx3 = (dataIndex - start) * 3;
return [positionArr[idx3], positionArr[idx3 + 1], positionArr[idx3 + 2]];
};
this._labelsBuilder.getLabelDistance = function (dataIndex, positionDesc, distance) {
var size = geometry.attributes.size.get(dataIndex - start) / pointSizeScale;
return size / 2 + distance;
};
this._labelsBuilder.updateLabels();
},
_updateAnimation: function (seriesModel) {
graphicGL.updateVertexAnimation([['prevPosition', 'position'], ['prevSize', 'size']], this._prevMesh, this._mesh, seriesModel);
},
_updateHandler: function (seriesModel, ecModel, api) {
var data = seriesModel.getData();
var pointsMesh = this._mesh;
var self = this;
var lastDataIndex = -1;
var isCartesian3D = seriesModel.coordinateSystem && seriesModel.coordinateSystem.type === 'cartesian3D';
var grid3DModel;
if (isCartesian3D) {
grid3DModel = seriesModel.coordinateSystem.model;
}
pointsMesh.seriesIndex = seriesModel.seriesIndex;
pointsMesh.off('mousemove');
pointsMesh.off('mouseout');
pointsMesh.on('mousemove', function (e) {
var dataIndex = e.vertexIndex + self._startDataIndex;
if (dataIndex !== lastDataIndex) {
if (this.highlightOnMouseover) {
this.downplay(data, lastDataIndex);
this.highlight(data, dataIndex);
this._labelsBuilder.updateLabels([dataIndex]);
}
if (isCartesian3D) {
api.dispatchAction({
type: 'grid3DShowAxisPointer',
value: [data.get(seriesModel.coordDimToDataDim('x')[0], dataIndex), data.get(seriesModel.coordDimToDataDim('y')[0], dataIndex), data.get(seriesModel.coordDimToDataDim('z')[0], dataIndex)],
grid3DIndex: grid3DModel.componentIndex
});
}
}
pointsMesh.dataIndex = dataIndex;
lastDataIndex = dataIndex;
}, this);
pointsMesh.on('mouseout', function (e) {
var dataIndex = e.vertexIndex + self._startDataIndex;
if (this.highlightOnMouseover) {
this.downplay(data, dataIndex);
this._labelsBuilder.updateLabels();
}
lastDataIndex = -1;
pointsMesh.dataIndex = -1;
if (isCartesian3D) {
api.dispatchAction({
type: 'grid3DHideAxisPointer',
grid3DIndex: grid3DModel.componentIndex
});
}
}, this);
},
updateLayout: function (seriesModel, ecModel, api) {
var data = seriesModel.getData();
if (!this._mesh) {
return;
}
var positionArr = this._mesh.geometry.attributes.position.value;
var points = data.getLayout('points');
if (this.is2D) {
for (var i = 0; i < points.length / 2; i++) {
var i3 = i * 3;
var i2 = i * 2;
positionArr[i3] = points[i2];
positionArr[i3 + 1] = points[i2 + 1];
positionArr[i3 + 2] = Z_2D;
}
} else {
for (var i = 0; i < points.length; i++) {
positionArr[i] = points[i];
}
}
this._mesh.geometry.dirty();
api.getZr().refresh();
},
updateView: function (camera) {
if (!this._mesh) {
return;
}
var worldViewProjection = new Matrix4();
Matrix4.mul(worldViewProjection, camera.viewMatrix, this._mesh.worldTransform);
Matrix4.mul(worldViewProjection, camera.projectionMatrix, worldViewProjection);
this._mesh.updateNDCPosition(worldViewProjection, this.is2D, this._api);
},
highlight: function (data, dataIndex) {
if (dataIndex > this._endDataIndex || dataIndex < this._startDataIndex) {
return;
}
var itemModel = data.getItemModel(dataIndex);
var emphasisItemStyleModel = itemModel.getModel('emphasis.itemStyle');
var emphasisColor = emphasisItemStyleModel.get('color');
var emphasisOpacity = emphasisItemStyleModel.get('opacity');
if (emphasisColor == null) {
var color = getItemVisualColor(data, dataIndex);
emphasisColor = echarts.color.lift(color, -0.4);
}
if (emphasisOpacity == null) {
emphasisOpacity = getItemVisualOpacity(data, dataIndex);
}
var colorArr = graphicGL.parseColor(emphasisColor);
colorArr[3] *= emphasisOpacity;
this._mesh.geometry.attributes.color.set(dataIndex - this._startDataIndex, colorArr);
this._mesh.geometry.dirtyAttribute('color');
this._api.getZr().refresh();
},
downplay: function (data, dataIndex) {
if (dataIndex > this._endDataIndex || dataIndex < this._startDataIndex) {
return;
}
var color = getItemVisualColor(data, dataIndex);
var opacity = getItemVisualOpacity(data, dataIndex);
var colorArr = graphicGL.parseColor(color);
colorArr[3] *= opacity;
this._mesh.geometry.attributes.color.set(dataIndex - this._startDataIndex, colorArr);
this._mesh.geometry.dirtyAttribute('color');
this._api.getZr().refresh();
},
fadeOutAll: function (fadeOutPercent) {
if (this._originalOpacity) {
var geo = this._mesh.geometry;
for (var i = 0; i < geo.vertexCount; i++) {
var fadeOutOpacity = this._originalOpacity[i] * fadeOutPercent;
geo.attributes.color.value[i * 4 + 3] = fadeOutOpacity;
}
geo.dirtyAttribute('color');
this._api.getZr().refresh();
}
},
fadeInAll: function () {
this.fadeOutAll(1);
},
setPositionTexture: function (texture) {
if (this._mesh) {
this._setPositionTextureToMesh(this._mesh, texture);
}
this._positionTexture = texture;
},
removePositionTexture: function () {
this._positionTexture = null;
if (this._mesh) {
this._setPositionTextureToMesh(this._mesh, null);
}
},
setSizeScale: function (sizeScale) {
if (sizeScale !== this._sizeScale) {
if (this._mesh) {
var originalSize = this._mesh.material.get('u_Size');
this._mesh.material.set('u_Size', originalSize / this._sizeScale * sizeScale);
var attributes = this._mesh.geometry.attributes;
if (attributes.size.value) {
for (var i = 0; i < attributes.size.value.length; i++) {
attributes.size.value[i] = attributes.size.value[i] / this._sizeScale * sizeScale;
}
}
}
this._sizeScale = sizeScale;
}
},
_setPositionTextureToMesh: function (mesh, texture) {
if (texture) {
mesh.material.set('positionTexture', texture);
}
mesh.material[texture ? 'enableTexture' : 'disableTexture']('positionTexture');
},
_getSymbolInfo: function (seriesModel, start, end) {
if (seriesModel.get('large')) {
var symbolSize = retrieve.firstNotNull(seriesModel.get('symbolSize'), 1);
var maxSymbolSize;
var symbolAspect;
if (symbolSize instanceof Array) {
maxSymbolSize = Math.max(symbolSize[0], symbolSize[1]);
symbolAspect = symbolSize[0] / symbolSize[1];
} else {
maxSymbolSize = symbolSize;
symbolAspect = 1;
}
return {
maxSize: symbolSize,
type: seriesModel.get('symbol'),
aspect: symbolAspect
};
}
var data = seriesModel.getData();
var symbolAspect;
var differentSymbolAspect = false;
var symbolType = data.getItemVisual(0, 'symbol') || 'circle';
var differentSymbolType = false;
var maxSymbolSize = 0;
for (var idx = start; idx < end; idx++) {
var symbolSize = data.getItemVisual(idx, 'symbolSize');
var currentSymbolType = data.getItemVisual(idx, 'symbol');
var currentSymbolAspect;
if (!(symbolSize instanceof Array)) {
// Ignore NaN value.
if (isNaN(symbolSize)) {
continue;
}
currentSymbolAspect = 1;
maxSymbolSize = Math.max(symbolSize, maxSymbolSize);
} else {
currentSymbolAspect = symbolSize[0] / symbolSize[1];
maxSymbolSize = Math.max(Math.max(symbolSize[0], symbolSize[1]), maxSymbolSize);
}
if (process.env.NODE_ENV !== 'production') {
if (symbolAspect != null && Math.abs(currentSymbolAspect - symbolAspect) > 0.05) {
differentSymbolAspect = true;
}
if (currentSymbolType !== symbolType) {
differentSymbolType = true;
}
}
symbolType = currentSymbolType;
symbolAspect = currentSymbolAspect;
}
if (process.env.NODE_ENV !== 'production') {
if (differentSymbolAspect) {
console.warn('Different symbol width / height ratio will be ignored.');
}
if (differentSymbolType) {
console.warn('Different symbol type will be ignored.');
}
}
return {
maxSize: maxSymbolSize,
type: symbolType,
aspect: symbolAspect
};
}
};
export default PointsBuilder;