<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script type="text/javascript" src="../Day1/lib/graph.min.js"></script>
<script type="text/javascript" src="demo_graph.js"></script>
<script>
var drawing;
function onloadGraph() {
drawing = new Drawing.Demo_Graph({layout:'3d',selection: true,numNodes:50,graphLayout:{attraction:5,repulstion:0.5}, showStats:true,showInfo:true,showLabels:true});
}
</script>
<style type="text/css">
body {
margin: 0;
padding: 0;
font: 11px courier;
overflow: hidden;
}
#graph-info {
position: absolute;
top: 0px;
left: 40%;
margin: 10px;
background-color: #ffffe0;
color: #333;
padding: 5px 10px;
}
#options {
position: absolute;
top: 0;
right: 0;
z-index: 10;
}
</style>
</head>
<body onload="onloadGraph()">
<div id="options">
<form>
<p>
<input type="checkbox" id="show_labels" onclick="drawing.show_labels = this.checked;">
<label for="show_labels">展示节点信息</label>
</p>
<p>
<input type="button" value="停止布局" onclick="drawing.stop_calculation();"
</p>
</form>
</div>
<div style="position: absolute; bottom: 0;">
Rotate: Left Mouse Button and Move<br />
Zoom: Press Key S + Left Mouse Button and Move<br />
Drag: Press Key D + Left Mouse Button and Move
</div>
</body>
</html>
var Drawing = Drawing || {};
Drawing.Demo_Graph = function(options) {
options = options || {};
this.layout = options.layout || "2d";
this.layout_options = options.graphLayout || {}; //图形布局
this.show_stats = options.showStats || false;
this.show_info = options.showInfo || false;
this.show_labels = options.showLabels || false;
this.selection = options.selection || false;
this.limit = options.limit || 10;
this.nodes_count = options.numNodes || 20;
this.edges_count = options.numEdges || 10;
//https://blog.csdn.net/w405722907/article/details/78132478
//相机、控制器、场景、渲染器、约束、图形、object_selection
var camera, controls, scene, renderer, interaction, geometry, object_selection;
//性能监视器FPS ms
var stats;
var info_text = {};
//创建具有最大节点数的图形
var graph = new GRAPHVIS.Graph({limit: options.limit});
var geometries = [];
var that=this;
//初始化方法
init();
createGraph();
//游戏循环调用controls.update()
animate();
function init() {
// Three.js initialization
//初始化渲染器
//Three.js的初始化,详情看官方文档 https://threejs.org/docs/index.html#manual/en/introduction/Creating-a-scene
//alpha:是否可以设置背景色透明
//antialias:是否开启反锯齿,设置为true为开启状态
//https://blog.csdn.net/weixin_41111068/article/details/82491985
renderer = new THREE.WebGLRenderer({alpha: true, antialias: true});
//setPixeRatio:像素比 为window的分辨比
renderer.setPixelRatio(window.devicePixelRatio);
//setSize:大小
//innerWidth 浏览器窗口可视区宽度(不包括浏览器控制台、菜单栏、工具栏)
//innerHeight 浏览器窗口可视区高度(不包括浏览器控制台、菜单栏、工具栏)
//https://blog.csdn.net/qq_33036599/article/details/81224346
renderer.setSize(window.innerWidth, window.innerHeight);
//初始化透视投影相机,用于渲染3D场景的最常见投影模式
//PerspectiveCamera参数 (fov:Number,aspect:Number,near:Number,far:Number)
//摄像机的视锥体的垂直视野角 40° (默认50° 从下到上。相机视野角)
//aspect:相机平视锥体宽高比(一般都是画布canvas的宽高比)
//near:相机视锥体的近平面 (默认0.1)
//far:相机视锥体的远平面 (默认2000)
camera = new THREE.PerspectiveCamera(40, window.innerWidth/window.innerHeight, 1, 1000000);
//相机默认坐标位置(0,0,0), z轴方向10000
camera.position.z = 10000;
//轨迹球控件
//https://blog.csdn.net/ithanmang/article/details/82348928
//TrackballControls创建控件并绑定到相机上
//参数(camera,renderer.domElement),第二个参数默认为当前文档的document
controls = new THREE.TrackballControls(camera);
//旋转速度
controls.rotateSpeed = 0.5;
//缩放速度
controls.zoomSpeed = 5.2;
//平controls
controls.panSpeed = 1;
//如果设置为true, 则禁用 鼠标滚轮调整大小(远近)的功能
controls.noZoom = false;
//如设置为true, 则禁用 按下鼠标右键平移的功能
controls.noPan = false;
//静止移动,为true,则没有惯性
controls.staticMoving = false;
//阻尼系数,系数越小,滑动越大
controls.dynamicDampingFactor = 0.3;
controls.keys = [ 65, 83, 68 ];
//为控制器增加一个动态监听(监听渲染器)
controls.addEventListener('change', render);
//初始化场景
scene = new THREE.Scene();
//判断
// Node geometry
if(that.layout === "3d") {
//https://threejs.org/docs/index.html#api/zh/geometries/SphereGeometry
//设置几何图形为球几何体的3d效果,球体半径为30,
geometry = new THREE.SphereGeometry(50);
} else {
//设置几何图形为立方几何体的效果
//参数(width,height,depth)默认值为1,
geometry = new THREE.BoxGeometry( 50, 50, 0 );
}
//创建节点
//Create node selection, if set
if(that.selection) {
object_selection = new THREE.ObjectSelection({
domElement: renderer.domElement,
selected: function(obj) {
// display info
if(obj !== null) {
info_text.select = "Object " + obj.id;
} else {
delete info_text.select;
}
},
clicked: function(obj) {
}
});
}
//将当前文档中生成的节点加入到主体中
document.body.appendChild( renderer.domElement );
// Stats.js
if(that.show_stats) {
//实例化一个性能监视器Stats
stats = new Stats();
//将stats的位置放在左侧顶部位置
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
document.body.appendChild( stats.domElement );
}
// Create info box
if(that.show_info) {
//HTML DOM Document操作 创建一个新的div元素
var info = document.createElement("div");
//createAttribute(name)创建新的Attr节点
var id_attr = document.createAttribute("id");
//element.nodeValue 返回或设置元素值
id_attr.nodeValue = "graph-info";
//setAttributeNode()方法用于添加新的属性节点
info.setAttributeNode(id_attr);
document.body.appendChild( info );
}
}
/**
* Creates a graph with random nodes and edges.
* Number of nodes and edges can be set with
* numNodes and numEdges.
*/
//创建具有随机节点和边的图形
//
function createGraph() {
//根据id创建节点
var node = new GRAPHVIS.Node(0);
node.data.title = "This is node " + node.id;
//添加node到graph
graph.addNode(node);
drawNode(node);
//将所有的创建的节点push到nodes数组中
var nodes = [];
nodes.push(node);
//创建标志位
var steps = 1;
//判断,数组长度 、 节点的限制长度
while(nodes.length !== 0 && steps < that.nodes_count) {
//shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值
//https://www.cnblogs.com/franson-2016/p/5913708.html
node = nodes.shift();
//生成一个1~that.edges_count范围之间的数(边)
var numEdges = randomFromTo(1, that.edges_count);
for(var i=1; i <= numEdges; i++) {
//生成根节点后的子节点
var target_node = new GRAPHVIS.Node(i*steps);
if(graph.addNode(target_node)) {
target_node.data.title = "This is node " + target_node.id;
//定义子节点的位置、材质属性
drawNode(target_node);
//向nodes数组中push子节点
nodes.push(target_node);
if(graph.addEdge(node, target_node)) {
//画两点间的连接线
//参数(根节点,子节点)
drawEdge(node, target_node);
}
}
}
//标志位增加1,直到大于所规定的最大节点数 numNodes
steps++;
}
//图形布局宽度
that.layout_options.width = that.layout_options.width || 2000;
//图形布局高度
that.layout_options.height = that.layout_options.height || 2000;
//图形布局叠代、次数?
that.layout_options.iterations = that.layout_options.iterations || 100000;
//图形布局效果
that.layout_options.layout = that.layout_options.layout || that.layout;
//https://gojs.net/latest/api/symbols/ForceDirectedLayout.html
graph.layout = new Layout.ForceDirected(graph, that.layout_options);
graph.layout.init();
//获取点和线的数量
info_text.nodes = "Nodes " + graph.nodes.length;
info_text.edges = "Edges " + graph.edges.length;
}
/**
* Create a node object and add it to the scene.
*/
function drawNode(node) {
//https://threejs.org/docs/index.html#api/zh/objects/Mesh
//网格
//参数(geometry:几何图形实例, MeshBasicMaterial材质)
//材质参数: 是由Material基类或者一个包含材质的数组派生类而来的材质实例,定义物体外观
//默认值是一个具有随机颜色的材质
var draw_object = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { color: Math.random() * 0xe0e0e0, opacity: 0.8 } ) );
var label_object;
//判断显示标签是否为真
if(that.show_labels) {
//如果节点的title不是未定义
if(node.data.title !== undefined) {
//赋值
label_object = new THREE.Label(node.data.title);
} else {
label_object = new THREE.Label(node.id);
}
node.data.label_object = label_object;
scene.add( node.data.label_object );
}
//随机设置对象材质的位置
var area = 5000;
draw_object.position.x = Math.floor(Math.random() * (area + area + 1) - area);
draw_object.position.y = Math.floor(Math.random() * (area + area + 1) - area);
if(that.layout === "3d") {
draw_object.position.z = Math.floor(Math.random() * (area + area + 1) - area);
}
draw_object.id = node.id;
node.data.draw_object = draw_object;
node.position = draw_object.position;
scene.add( node.data.draw_object );
}
/**
* Create an edge object (line) and add it to the scene.
* 创建边缘对象(线)并将其添加到场景中
*/
function drawEdge(source, target) {
//设置基础线条材质
material = new THREE.LineBasicMaterial({ color: 0x606060 });
//Geomentry代替了BufferGeometry() ,更好的利用Vector3,更容易读写,但运行效率不高
//https://threejs.org/docs/index.html#api/zh/core/Geometry
//是面片、线或点几何体的有效表达,包括定点位置,面片索引、法向量、颜色值、UV坐标和自定义缓存属性值
var tmp_geo = new THREE.Geometry();
//vetices:顶点创建
tmp_geo.vertices.push(source.data.draw_object.position);
tmp_geo.vertices.push(target.data.draw_object.position);
//线段,在对若干对顶点之间绘制一系列线段
//参数:geometry:表示每条线段的两个顶点,material:线的材质
line = new THREE.LineSegments( tmp_geo, material );
line.scale.x = line.scale.y = line.scale.z = 1;
line.originalScale = 1;
// NOTE: Deactivated frustumCulled, otherwise it will not draw all lines (even though
// it looks like the lines are in the view frustum).
//停用视锥体,否则不会绘制所有线条
//防止对线进行视锥体剔除
line.frustumCulled = false;
//向几何体中加入材质
geometries.push(tmp_geo);
//向场景中加入线条
scene.add( line );
}
//创建动画
function animate() {
//请求动画框架
//https://blog.csdn.net/vhwfr2u02q/article/details/79492303
requestAnimationFrame( animate );
//对控制器进行更新
//需要在循环函数中不断的更新
controls.update();
render();
if(that.show_info) {
printInfo();
}
}
//加载过程,类似于缓冲
function render() {
var i, length, node;
//未完成时生成布局
// Generate layout if not finished
if(!graph.layout.finished) {
info_text.calc = "<span style='color: red'>Calculating layout...</span>";
graph.layout.generate();
} else {
info_text.calc = "";
}
//更新线(边)的位置
// Update position of lines (edges)
//https://blog.csdn.net/qq_30100043/article/details/79157102
for(i=0; i<geometries.length; i++) {
geometries[i].verticesNeedUpdate = true;
}
// Show labels if set
// It creates the labels when this options is set during visualization
if(that.show_labels) {
//获取nodes数组的长度
length = graph.nodes.length;
for(i=0; i<length; i++) {
//获取nodes数组的下标节点
node = graph.nodes[i];
if(node.data.label_object !== undefined) {
//为节点信息设置x,y,z
node.data.label_object.position.x = node.data.draw_object.position.x;
node.data.label_object.position.y = node.data.draw_object.position.y - 100;
node.data.label_object.position.z = node.data.draw_object.position.z;
node.data.label_object.lookAt(camera.position);
} else {
//如果节点title未进行定义
var label_object;
if(node.data.title !== undefined) {
//重新对节点进行定义
label_object = new THREE.Label(node.data.title, node.data.draw_object);
} else {
label_object = new THREE.Label(node.id, node.data.draw_object);
}
node.data.label_object = label_object;
//向场景中添加节点信息
scene.add( node.data.label_object );
}
}
} else {
length = graph.nodes.length;
for(i=0; i<length; i++) {
node = graph.nodes[i];
if(node.data.label_object !== undefined) {
//场景中移除
scene.remove( node.data.label_object );
node.data.label_object = undefined;
}
}
}
// render selection
//渲染选择
if(that.selection) {
object_selection.render(scene, camera);
}
// update stats
//更新统计信息
if(that.show_stats) {
stats.update();
}
// render scene
//设置场景
renderer.render( scene, camera );
}
/**
* Prints info from the attribute info_text.
* 从属性信息文本打印信息
*/
function printInfo(text) {
var str = '';
for(var index in info_text) {
if(str !== '' && info_text[index] !== '') {
str += " - ";
}
str += info_text[index];
}
document.getElementById("graph-info").innerHTML = str;
}
// Generate random number生成随机数
function randomFromTo(from, to) {
return Math.floor(Math.random() * (to - from + 1) + from);
}
//停止布局计算
// Stop layout calculation
this.stop_calculating = function() {
graph.layout.stop_calculating();
};
};