UNPKG

cione-comp-lib

Version:

`$ yarn add cione-comp-lib` 或者 `$ npm i cione-comp-lib -S`

575 lines (574 loc) 20.7 kB
import "../../../../echarts/lib/echarts.mjs"; import { getLayoutRect } from "../../../../echarts/lib/util/layout.mjs"; import graphicGL from "../../util/graphicGL.mjs"; import ViewGL from "../../core/ViewGL.mjs"; import Lines2DGeometry from "../../util/geometry/Lines2D.mjs"; import retrieve from "../../util/retrieve.mjs"; import ForceAtlas2GPU from "./ForceAtlas2GPU.mjs"; import ForceAtlas2 from "./ForceAtlas2.mjs"; import requestAnimationFrame from "../../../../zrender/lib/animation/requestAnimationFrame.mjs"; import glmatrix from "../../../../claygl/src/glmatrix/index.mjs"; import { getItemVisualColor, getItemVisualOpacity } from "../../util/visual.mjs"; import Roam2DControl from "../../util/Roam2DControl.mjs"; import PointsBuilder from "../common/PointsBuilder.mjs"; import lines2DGLSL from "../../util/shader/lines2D.glsl.mjs"; import ChartView from "../../../../echarts/lib/view/Chart.mjs"; import { linearMap } from "../../../../echarts/lib/util/number.mjs"; var vec2 = glmatrix.vec2; graphicGL.Shader.import(lines2DGLSL); var globalLayoutId = 1; var GraphGLView = ChartView.extend({ type: "graphGL", __ecgl__: true, init: function(ecModel, api) { this.groupGL = new graphicGL.Node(); this.viewGL = new ViewGL("orthographic"); this.viewGL.camera.left = this.viewGL.camera.right = 0; this.viewGL.add(this.groupGL); this._pointsBuilder = new PointsBuilder(true, api); this._forceEdgesMesh = new graphicGL.Mesh({ material: new graphicGL.Material({ shader: graphicGL.createShader("ecgl.forceAtlas2.edges"), transparent: true, depthMask: false, depthTest: false }), $ignorePicking: true, geometry: new graphicGL.Geometry({ attributes: { node: new graphicGL.Geometry.Attribute("node", "float", 2), color: new graphicGL.Geometry.Attribute("color", "float", 4, "COLOR") }, dynamic: true, mainAttribute: "node" }), renderOrder: -1, mode: graphicGL.Mesh.LINES }); this._edgesMesh = new graphicGL.Mesh({ material: new graphicGL.Material({ shader: graphicGL.createShader("ecgl.meshLines2D"), transparent: true, depthMask: false, depthTest: false }), $ignorePicking: true, geometry: new Lines2DGeometry({ useNativeLine: false, dynamic: true }), renderOrder: -1, culling: false }); this._layoutId = 0; this._control = new Roam2DControl({ zr: api.getZr(), viewGL: this.viewGL }); this._control.setTarget(this.groupGL); this._control.init(); this._clickHandler = this._clickHandler.bind(this); }, render: function(seriesModel, ecModel, api) { this.groupGL.add(this._pointsBuilder.rootNode); this._model = seriesModel; this._api = api; this._initLayout(seriesModel, ecModel, api); this._pointsBuilder.update(seriesModel, ecModel, api); if (!(this._forceLayoutInstance instanceof ForceAtlas2GPU)) { this.groupGL.remove(this._forceEdgesMesh); } this._updateCamera(seriesModel, api); this._control.off("update"); this._control.on("update", function() { api.dispatchAction({ type: "graphGLRoam", seriesId: seriesModel.id, zoom: this._control.getZoom(), offset: this._control.getOffset() }); this._pointsBuilder.updateView(this.viewGL.camera); }, this); this._control.setZoom(retrieve.firstNotNull(seriesModel.get("zoom"), 1)); this._control.setOffset(seriesModel.get("offset") || [0, 0]); var mesh = this._pointsBuilder.getPointsMesh(); mesh.off("mousemove", this._mousemoveHandler); mesh.off("mouseout", this._mouseOutHandler, this); api.getZr().off("click", this._clickHandler); this._pointsBuilder.highlightOnMouseover = true; if (seriesModel.get("focusNodeAdjacency")) { var focusNodeAdjacencyOn = seriesModel.get("focusNodeAdjacencyOn"); if (focusNodeAdjacencyOn === "click") { api.getZr().on("click", this._clickHandler); } else if (focusNodeAdjacencyOn === "mouseover") { mesh.on("mousemove", this._mousemoveHandler, this); mesh.on("mouseout", this._mouseOutHandler, this); this._pointsBuilder.highlightOnMouseover = false; } else ; } this._lastMouseOverDataIndex = -1; }, _clickHandler: function(e) { if (this._layouting) { return; } var dataIndex = this._pointsBuilder.getPointsMesh().dataIndex; if (dataIndex >= 0) { this._api.dispatchAction({ type: "graphGLFocusNodeAdjacency", seriesId: this._model.id, dataIndex }); } else { this._api.dispatchAction({ type: "graphGLUnfocusNodeAdjacency", seriesId: this._model.id }); } }, _mousemoveHandler: function(e) { if (this._layouting) { return; } var dataIndex = this._pointsBuilder.getPointsMesh().dataIndex; if (dataIndex >= 0) { if (dataIndex !== this._lastMouseOverDataIndex) { this._api.dispatchAction({ type: "graphGLFocusNodeAdjacency", seriesId: this._model.id, dataIndex }); } } else { this._mouseOutHandler(e); } this._lastMouseOverDataIndex = dataIndex; }, _mouseOutHandler: function(e) { if (this._layouting) { return; } this._api.dispatchAction({ type: "graphGLUnfocusNodeAdjacency", seriesId: this._model.id }); this._lastMouseOverDataIndex = -1; }, _updateForceEdgesGeometry: function(edges, seriesModel) { var geometry = this._forceEdgesMesh.geometry; var edgeData = seriesModel.getEdgeData(); var offset = 0; var layoutInstance = this._forceLayoutInstance; var vertexCount = edgeData.count() * 2; geometry.attributes.node.init(vertexCount); geometry.attributes.color.init(vertexCount); edgeData.each(function(idx) { var edge = edges[idx]; geometry.attributes.node.set(offset, layoutInstance.getNodeUV(edge.node1)); geometry.attributes.node.set(offset + 1, layoutInstance.getNodeUV(edge.node2)); var color = getItemVisualColor(edgeData, edge.dataIndex); var colorArr = graphicGL.parseColor(color); colorArr[3] *= retrieve.firstNotNull(getItemVisualOpacity(edgeData, edge.dataIndex), 1); geometry.attributes.color.set(offset, colorArr); geometry.attributes.color.set(offset + 1, colorArr); offset += 2; }); geometry.dirty(); }, _updateMeshLinesGeometry: function() { var edgeData = this._model.getEdgeData(); var geometry = this._edgesMesh.geometry; var edgeData = this._model.getEdgeData(); var points = this._model.getData().getLayout("points"); geometry.resetOffset(); geometry.setVertexCount(edgeData.count() * geometry.getLineVertexCount()); geometry.setTriangleCount(edgeData.count() * geometry.getLineTriangleCount()); var p0 = []; var p1 = []; var lineWidthQuery = ["lineStyle", "width"]; this._originalEdgeColors = new Float32Array(edgeData.count() * 4); this._edgeIndicesMap = new Float32Array(edgeData.count()); edgeData.each(function(idx) { var edge = edgeData.graph.getEdgeByIndex(idx); var idx1 = edge.node1.dataIndex * 2; var idx2 = edge.node2.dataIndex * 2; p0[0] = points[idx1]; p0[1] = points[idx1 + 1]; p1[0] = points[idx2]; p1[1] = points[idx2 + 1]; var color = getItemVisualColor(edgeData, edge.dataIndex); var colorArr = graphicGL.parseColor(color); colorArr[3] *= retrieve.firstNotNull(getItemVisualOpacity(edgeData, edge.dataIndex), 1); var itemModel = edgeData.getItemModel(edge.dataIndex); var lineWidth = retrieve.firstNotNull(itemModel.get(lineWidthQuery), 1) * this._api.getDevicePixelRatio(); geometry.addLine(p0, p1, colorArr, lineWidth); for (var k = 0; k < 4; k++) { this._originalEdgeColors[edge.dataIndex * 4 + k] = colorArr[k]; } this._edgeIndicesMap[edge.dataIndex] = idx; }, this); geometry.dirty(); }, _updateForceNodesGeometry: function(nodeData) { var pointsMesh = this._pointsBuilder.getPointsMesh(); var pos = []; for (var i = 0; i < nodeData.count(); i++) { this._forceLayoutInstance.getNodeUV(i, pos); pointsMesh.geometry.attributes.position.set(i, pos); } pointsMesh.geometry.dirty("position"); }, _initLayout: function(seriesModel, ecModel, api) { var layout = seriesModel.get("layout"); var graph = seriesModel.getGraph(); var boxLayoutOption = seriesModel.getBoxLayoutParams(); var viewport = getLayoutRect(boxLayoutOption, { width: api.getWidth(), height: api.getHeight() }); if (layout === "force") { layout = "forceAtlas2"; } this.stopLayout(seriesModel, ecModel, api, { beforeLayout: true }); var nodeData = seriesModel.getData(); var edgeData = seriesModel.getData(); if (layout === "forceAtlas2") { var layoutModel = seriesModel.getModel("forceAtlas2"); var layoutInstance = this._forceLayoutInstance; var nodes = []; var edges = []; var nodeDataExtent = nodeData.getDataExtent("value"); var edgeDataExtent = edgeData.getDataExtent("value"); var edgeWeightRange = retrieve.firstNotNull(layoutModel.get("edgeWeight"), 1); var nodeWeightRange = retrieve.firstNotNull(layoutModel.get("nodeWeight"), 1); if (typeof edgeWeightRange === "number") { edgeWeightRange = [edgeWeightRange, edgeWeightRange]; } if (typeof nodeWeightRange === "number") { nodeWeightRange = [nodeWeightRange, nodeWeightRange]; } var offset = 0; var nodesIndicesMap = {}; var layoutPoints = new Float32Array(nodeData.count() * 2); graph.eachNode(function(node) { var dataIndex = node.dataIndex; var value = nodeData.get("value", dataIndex); var x; var y; if (nodeData.hasItemOption) { var itemModel = nodeData.getItemModel(dataIndex); x = itemModel.get("x"); y = itemModel.get("y"); } if (x == null) { x = viewport.x + Math.random() * viewport.width; y = viewport.y + Math.random() * viewport.height; } layoutPoints[offset * 2] = x; layoutPoints[offset * 2 + 1] = y; nodesIndicesMap[node.id] = offset++; var mass = linearMap(value, nodeDataExtent, nodeWeightRange); if (isNaN(mass)) { if (!isNaN(nodeWeightRange[0])) { mass = nodeWeightRange[0]; } else { mass = 1; } } nodes.push({ x, y, mass, size: nodeData.getItemVisual(dataIndex, "symbolSize") }); }); nodeData.setLayout("points", layoutPoints); graph.eachEdge(function(edge) { var dataIndex = edge.dataIndex; var value = nodeData.get("value", dataIndex); var weight = linearMap(value, edgeDataExtent, edgeWeightRange); if (isNaN(weight)) { if (!isNaN(edgeWeightRange[0])) { weight = edgeWeightRange[0]; } else { weight = 1; } } edges.push({ node1: nodesIndicesMap[edge.node1.id], node2: nodesIndicesMap[edge.node2.id], weight, dataIndex }); }); if (!layoutInstance) { var isGPU = layoutModel.get("GPU"); if (this._forceLayoutInstance) { if (isGPU && !(this._forceLayoutInstance instanceof ForceAtlas2GPU) || !isGPU && !(this._forceLayoutInstance instanceof ForceAtlas2)) { this._forceLayoutInstanceToDispose = this._forceLayoutInstance; } } layoutInstance = this._forceLayoutInstance = isGPU ? new ForceAtlas2GPU() : new ForceAtlas2(); } layoutInstance.initData(nodes, edges); layoutInstance.updateOption(layoutModel.option); this._updateForceEdgesGeometry(layoutInstance.getEdges(), seriesModel); this._updatePositionTexture(); api.dispatchAction({ type: "graphGLStartLayout", from: this.uid }); } else { var layoutPoints = new Float32Array(nodeData.count() * 2); var offset = 0; graph.eachNode(function(node) { var dataIndex = node.dataIndex; var x; var y; if (nodeData.hasItemOption) { var itemModel = nodeData.getItemModel(dataIndex); x = itemModel.get("x"); y = itemModel.get("y"); } layoutPoints[offset++] = x; layoutPoints[offset++] = y; }); nodeData.setLayout("points", layoutPoints); this._updateAfterLayout(seriesModel, ecModel, api); } }, _updatePositionTexture: function() { var positionTex = this._forceLayoutInstance.getNodePositionTexture(); this._pointsBuilder.setPositionTexture(positionTex); this._forceEdgesMesh.material.set("positionTex", positionTex); }, startLayout: function(seriesModel, ecModel, api, payload) { if (payload && payload.from != null && payload.from !== this.uid) { return; } var viewGL = this.viewGL; var api = this._api; var layoutInstance = this._forceLayoutInstance; var data = this._model.getData(); var layoutModel = this._model.getModel("forceAtlas2"); if (!layoutInstance) { return; } this.groupGL.remove(this._edgesMesh); this.groupGL.add(this._forceEdgesMesh); if (!this._forceLayoutInstance) { return; } this._updateForceNodesGeometry(seriesModel.getData()); this._pointsBuilder.hideLabels(); var self = this; var layoutId = this._layoutId = globalLayoutId++; var maxSteps = layoutModel.getShallow("maxSteps"); var steps = layoutModel.getShallow("steps"); var stepsCount = 0; var syncStepCount = Math.max(steps * 2, 20); var doLayout = function(layoutId2) { if (layoutId2 !== self._layoutId) { return; } if (layoutInstance.isFinished(maxSteps)) { api.dispatchAction({ type: "graphGLStopLayout", from: self.uid }); api.dispatchAction({ type: "graphGLFinishLayout", points: data.getLayout("points"), from: self.uid }); return; } layoutInstance.update(viewGL.layer.renderer, steps, function() { self._updatePositionTexture(); stepsCount += steps; if (stepsCount >= syncStepCount) { self._syncNodePosition(seriesModel); stepsCount = 0; } api.getZr().refresh(); requestAnimationFrame(function() { doLayout(layoutId2); }); }); }; requestAnimationFrame(function() { if (self._forceLayoutInstanceToDispose) { self._forceLayoutInstanceToDispose.dispose(viewGL.layer.renderer); self._forceLayoutInstanceToDispose = null; } doLayout(layoutId); }); this._layouting = true; }, stopLayout: function(seriesModel, ecModel, api, payload) { if (payload && payload.from != null && payload.from !== this.uid) { return; } this._layoutId = 0; this.groupGL.remove(this._forceEdgesMesh); this.groupGL.add(this._edgesMesh); if (!this._forceLayoutInstance) { return; } if (!this.viewGL.layer) { return; } if (!(payload && payload.beforeLayout)) { this._syncNodePosition(seriesModel); this._updateAfterLayout(seriesModel, ecModel, api); } this._api.getZr().refresh(); this._layouting = false; }, _syncNodePosition: function(seriesModel) { var points = this._forceLayoutInstance.getNodePosition(this.viewGL.layer.renderer); seriesModel.getData().setLayout("points", points); seriesModel.setNodePosition(points); }, _updateAfterLayout: function(seriesModel, ecModel, api) { this._updateMeshLinesGeometry(); this._pointsBuilder.removePositionTexture(); this._pointsBuilder.updateLayout(seriesModel, ecModel, api); this._pointsBuilder.updateView(this.viewGL.camera); this._pointsBuilder.updateLabels(); this._pointsBuilder.showLabels(); }, focusNodeAdjacency: function(seriesModel, ecModel, api, payload) { var data = this._model.getData(); this._downplayAll(); var dataIndex = payload.dataIndex; var graph = data.graph; var focusNodes = []; var node = graph.getNodeByIndex(dataIndex); focusNodes.push(node); node.edges.forEach(function(edge) { if (edge.dataIndex < 0) { return; } edge.node1 !== node && focusNodes.push(edge.node1); edge.node2 !== node && focusNodes.push(edge.node2); }, this); this._pointsBuilder.fadeOutAll(0.05); this._fadeOutEdgesAll(0.05); focusNodes.forEach(function(node2) { this._pointsBuilder.highlight(data, node2.dataIndex); }, this); this._pointsBuilder.updateLabels(focusNodes.map(function(node2) { return node2.dataIndex; })); var focusEdges = []; node.edges.forEach(function(edge) { if (edge.dataIndex >= 0) { this._highlightEdge(edge.dataIndex); focusEdges.push(edge); } }, this); this._focusNodes = focusNodes; this._focusEdges = focusEdges; }, unfocusNodeAdjacency: function(seriesModel, ecModel, api, payload) { this._downplayAll(); this._pointsBuilder.fadeInAll(); this._fadeInEdgesAll(); this._pointsBuilder.updateLabels(); }, _highlightEdge: function(dataIndex) { var itemModel = this._model.getEdgeData().getItemModel(dataIndex); var emphasisColor = graphicGL.parseColor(itemModel.get("emphasis.lineStyle.color") || itemModel.get("lineStyle.color")); var emphasisOpacity = retrieve.firstNotNull(itemModel.get("emphasis.lineStyle.opacity"), itemModel.get("lineStyle.opacity"), 1); emphasisColor[3] *= emphasisOpacity; this._edgesMesh.geometry.setItemColor(this._edgeIndicesMap[dataIndex], emphasisColor); }, _downplayAll: function() { if (this._focusNodes) { this._focusNodes.forEach(function(node) { this._pointsBuilder.downplay(this._model.getData(), node.dataIndex); }, this); } if (this._focusEdges) { this._focusEdges.forEach(function(edge) { this._downplayEdge(edge.dataIndex); }, this); } }, _downplayEdge: function(dataIndex) { var color = this._getColor(dataIndex, []); this._edgesMesh.geometry.setItemColor(this._edgeIndicesMap[dataIndex], color); }, _setEdgeFade: function() { var color = []; return function(dataIndex, percent) { this._getColor(dataIndex, color); color[3] *= percent; this._edgesMesh.geometry.setItemColor(this._edgeIndicesMap[dataIndex], color); }; }(), _getColor: function(dataIndex, out) { for (var i = 0; i < 4; i++) { out[i] = this._originalEdgeColors[dataIndex * 4 + i]; } return out; }, _fadeOutEdgesAll: function(percent) { var graph = this._model.getData().graph; graph.eachEdge(function(edge) { this._setEdgeFade(edge.dataIndex, percent); }, this); }, _fadeInEdgesAll: function() { this._fadeOutEdgesAll(1); }, _updateCamera: function(seriesModel, api) { this.viewGL.setViewport(0, 0, api.getWidth(), api.getHeight(), api.getDevicePixelRatio()); var camera = this.viewGL.camera; var nodeData = seriesModel.getData(); var points = nodeData.getLayout("points"); var min = vec2.create(Infinity, Infinity); var max = vec2.create(-Infinity, -Infinity); var pt = []; for (var i = 0; i < points.length; ) { pt[0] = points[i++]; pt[1] = points[i++]; vec2.min(min, min, pt); vec2.max(max, max, pt); } var cy = (max[1] + min[1]) / 2; var cx = (max[0] + min[0]) / 2; if (cx > camera.left && cx < camera.right && cy < camera.bottom && cy > camera.top) { return; } var width = Math.max(max[0] - min[0], 10); var height = width / api.getWidth() * api.getHeight(); width *= 1.4; height *= 1.4; min[0] -= width * 0.2; camera.left = min[0]; camera.top = cy - height / 2; camera.bottom = cy + height / 2; camera.right = width + min[0]; camera.near = 0; camera.far = 100; }, dispose: function() { var renderer = this.viewGL.layer.renderer; if (this._forceLayoutInstance) { this._forceLayoutInstance.dispose(renderer); } this.groupGL.removeAll(); this._layoutId = -1; this._pointsBuilder.dispose(); }, remove: function() { this.groupGL.removeAll(); this._control.dispose(); } }); export { GraphGLView as default };