代碼地址以下:
http://www.demodashi.com/demo/13181.htmlcss
將結構化數據經過關係預處理程序處理爲圖數據庫能夠查詢的數據,示例是將其中一部分(人物關係數據)可視化表示。html
技術點:圖數據庫Neo4j,d3.js,java,css,spring boot
開發工具:IDEA專業版(可找學生帳號註冊無償使用一年,社區版不支持WEB開發)前端
實現思路這樣:
1,先定義基礎的展現頁面index.html
二、完成畫圖js(graph.js)
3,提供一個基礎的拿數據接口加載測試繪圖數據和繪圖須要的數據(例如節點選中以後的小圖標加載)
四、頁面從數據接口請求數據以後,調用繪圖JS在頁面完成畫圖操做(請求數據的接口能夠很方便的改成從圖數據庫拿取數據進行展現)java
主要文件目錄說明:node
一、data目錄
bg.jpg可視化背景圖片數據
CircularPartition.json節點圓形分區圖工具欄須要加載的數據
test.json可視化須要展現的數據格式spring
二、images
此目錄存儲節點屬性圖片數據數據庫
三、js
d3.js version-3.2.8json
四、src
JS以及其它HTML等源碼數組
五、index.html
知識圖譜可視化入口文件緩存
六、拿數據接口
經過數據Type id加載圓形分區圖數據和測試知識圖譜構圖數據(type等於1加載圓形分區數據,type是等於2加載測試知識圖譜展現數據)
GET:http://localhost:7476/knowledge-graph/hello/dataSource/type/{id}
作圖過程(graph.js):
// 定義畫布 (radius是鼠標點擊生成圓形分區圖的半徑) var width = 1345, height = 750, color = d3.scale.category20(); var svg = d3.select("body") .append("svg") .attr("id", "svgGraph") .attr("width", width) .attr("height", height) .append("g") .attr("id", "svgOne") .call(d3.behavior.zoom() // 自動建立事件偵聽器 .scaleExtent([0.1, 10]) // 縮放容許的級數 .on("zoom", zoom) ) .on("dblclick.zoom", null); // remove雙擊縮放
// 實時獲取SVG畫布座標 function printPosition() { var position = d3.mouse(svg.node()); return position; }
// 縮放函數 function zoom() { // translate變換矢量(使用二元組標識)scale當前尺度的數字 svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); // 畫布縮放與移動 // svg.attr("transform", "scale(" + d3.event.scale + ")"); // 畫布縮放 }
// 設置連線箭頭屬性 function setMarkers() { svg.append("g") .attr("id", "lineAndText") .selectAll("marker") .data(edges) .enter() .append("marker") .attr("id", function (d) { return d.id; }) .attr("viewBox", "0 -5 10 10") // 座標系的區域 .attr("class", "arrow") .attr("refX", 27) // refX,refY在viewBox內的基準點,繪製時此點在直線端點上(要注意大小寫) .attr("refY", 0) .attr("markerWidth", 10) // 標識的大小 .attr("markerHeight", 18) // 標識的大小 .attr("markerUnits", "userSpaceOnUse") // 標識大小的基準,有兩個值:strokeWidth(線的寬度)和userSpaceOnUse(圖形最前端的大小) .attr("orient", "auto") // 繪製方向,可設定爲:auto(自動確認方向)和 角度值 .append("path") .attr("d", "M0,-5L10,0L0,5") .attr("fill", "#ccc"); }
// 添加連線 function add_edges() { setMarkers(); // 設置連線箭頭屬性 var svg_edges = svg.select("#lineAndText") .selectAll("line") .data(edges) .enter() .append("line") .attr("id", function (d) { return d.id; }) .style("stroke", "#ccc") .style("stroke_width", 1) .attr("marker-end", function (d) { return "url(#" + d.id + ")"; }) .attr("stroke", "#999") .on("mouseover", function (d) { // 鼠標選中時觸發 mouseSelectLine(d); addToolTip(d); //添加提示框的div }) .on("mouseout", function () { d3.select("#relation").remove(); d3.select("#tooltip").remove(); }); return svg_edges; }
// 求直線與圓的交點 // 函數參數說明:cx:圓X軸座標 cy:圓y軸座標 r:圓半徑 stx:起點直線的X軸座標 sty:起點直線的軸座標 edx:終點直線的X軸座標 edy:終點直線的Y軸座標 // 返回值:交點座標(x,y) function getPoint(cx, cy, r, stx, sty, edx, edy) { // 求直線 var k = (edy - sty) / (edx - stx); var b = edy - k * edx; //列方程 var x1, y1, x2, y2; var c = cx * cx + (b - cy) * (b - cy) - r * r; var a = (1 + k * k); var b1 = (2 * cx - 2 * k * (b - cy)); var tmp = Math.sqrt(b1 * b1 - 4 * a * c); x1 = (b1 + tmp) / (2 * a); y1 = k * x1 + b; x2 = (b1 - tmp) / (2 * a); y2 = k * x2 + b; // 過濾距離最近的座標 var p = {}; function lineIf(lx, ly, lxx, lyy) { var d = Math.sqrt((lx - lxx) * (lx - lxx) + (ly - lyy) * (ly - lyy)); return d; } if (cx != stx) { // stx, sty var d1 = lineIf(x1, y1, stx, sty); var d2 = lineIf(x2, y2, stx, sty); if (d1 < d2) { p.x = x1; p.y = y1; } else { p.x = x2; p.y = y2; } } else { // edx, edy var d1 = lineIf(x1, y1, edx, edy); var d2 = lineIf(x2, y2, edx, edy); if (d1 < d2) { p.x = x1; p.y = y1; } else { p.x = x2; p.y = y2; } } return p; }
// 鼠標選中關係添加顯示效果 function mouseSelectLine(d) { var p1 = getPoint(d.source.x, d.source.y, 20, d.source.x, d.source.y, d.target.x, d.target.y); var p2 = getPoint(d.target.x, d.target.y, 20, d.source.x, d.source.y, d.target.x, d.target.y); var json = [p1, p2]; //構造默認線性生成器 var line = d3.svg.line() .x(function (d) { //指定x存取器爲:取每一個數據元素的x屬性的值 return d.x; }) .y(function (d) { //指定y存取器爲:取每一個數據元素的y屬性的值 return d.y; }); svg.append('path') .attr({ "d": function () { //生成路徑數據 return line(json); }, "id": "relation" }) .style({ "stroke": "#87CEFA", //path顏色 "stroke-width": 6 //path粗細 }); }
// 添加節點 function add_nodes() { var svg_nodes = svg.append("g") .attr("id", "circleAndText") .selectAll("circle") .data(nodes) .enter() .append("g") .call(force.drag().on("dragstart", function (d) { d3.select("#eee").remove(); // 刪除節點扇形 d3.select("#sel").remove(); // 刪除節點選中 d3.event.sourceEvent.stopPropagation(); // 畫布拖動與節點拖動分離 d3.select(this).attr("r", 20 * 2); }) .on("dragend", function (d) { d3.select("#eee").remove(); // 刪除節點扇形 d3.select("#sel").remove(); // 刪除節點選中 d.fixed = true; // 拖動結束後節點固定 d3.select(this).attr("r", 20); }) ) .on("click", function (d) { // 鼠標點擊時觸發 // 在當前節點處畫三頁扇形 d3.select("#eee").remove(); drawCirclePartition(d); }) .on("mouseover", function (d) { // 光標放在某元素上s mouseSelect(d); // 鼠標選中效果 addToolTip(d); //添加提示框的div }) .on("mouseout", function (d) { d3.select("#sel").remove(); // 刪除節點選中 d3.select("#tooltip").remove(); d3.select("#tooltipCir").remove(); }); svg_nodes.append("circle") .attr("id", function (d) { return d.index; }) .attr("r", 20) .attr("fill", function (d, i) { return color(i); }); svg_nodes.append("image") .attr("class", "circle") .attr("xlink:href", function (d) { var img = d.image; if (img != undefined) { return "http://222.216.195.154:7476/knowledge-graph/path/images/" + d.image } else { return null; } }) .attr("x", "-20px") .attr("y", "-20px") .attr("width", "40px") .attr("height", "40px"); svg_nodes.append("svg:text") .style("fill", "#ccc") .attr("dx", 20) .attr("dy", 8) .text(function (d) { return d.name }); return svg_nodes; }
//添加提示框的div function addToolTip(d) { var htmlStr; if (d.source && d.target && d.type) { htmlStr = "name:" + d.type + "<br/>"; } else { htmlStr = "id:" + d.id + "<br/>" + "name:" + d.name + "<br/>"; } var position = printPosition(d); var tooltip = d3.select("body").append("div") .attr("class", "tooltip") //用於css設置類樣式 .attr("opacity", 0.0) .attr("id", "tooltip"); htmlStr = htmlStr + "locx:" + position[0] + "<br/>" + "locy:" + position[1] + "<br/>"; if (d.image != undefined) { htmlStr = htmlStr + "<img src=\"http://222.216.195.154:7476/knowledge-graph/path/images/" + d.image + "\" height=\"100\" width=\"100\" />"; } tooltip.html(htmlStr) .style("left", (d3.event.pageX) + "px") .style("top", (d3.event.pageY + 20) + "px") .style("opacity", 0.75); } function addToolTipCir(d) { var htmlStr; if (d.name == "☿") { htmlStr = "notes:解鎖當前節點<br/>"; } if (d.name == "✂") { htmlStr = "notes:裁剪當前節點與關係<br/>"; } if (d.name == "✠") { htmlStr = "notes:拓展當前節點與關係<br/>"; } if (d.name == "◎") { htmlStr = "notes:釋放全部鎖定的節點<br/>"; } if (d.name == "오") { htmlStr = "notes:鎖定全部節點<br/>"; } var tooltip = d3.select("body").append("div") .attr("class", "tooltip") //用於css設置類樣式 .attr("opacity", 0.0) .attr("id", "tooltipCir"); tooltip.html(htmlStr) .style("left", (d3.event.pageX) + "px") .style("top", (d3.event.pageY + 20) + "px") .style("opacity", 0.75); }
// 生成圓弧須要的角度數據 var arcDataTemp = [{startAngle: 0, endAngle: 2 * Math.PI}]; var arc_temp = d3.svg.arc().outerRadius(26).innerRadius(20);
// 鼠標選中節點添加顯示效果 var svg_selectNode; function mouseSelect(d) { svg_selectNode = svg.append("g") .attr("id", "sel") .attr("transform", "translate(" + d.x + "," + d.y + ")") .selectAll("path.arc") .data(arcDataTemp) .enter() .append("path") .attr("fill", "#87CEFA") .attr("d", function (d, i) { return arc_temp(d, i); }); }
// 全局中止力做用之間的影響 function stopForce() { for (var i = 0; i < nodes.length; i++) { var obj = nodes[i]; obj.fixed = true; } }
// 全局開始力做用之間的影響 function startForce() { for (var i = 0; i < nodes.length; i++) { var obj = nodes[i]; obj.fixed = false; } force.resume(); }
var nodesMark = [], edgesMark = [], indexNodeMark = []; // 緩存中全部已加載的數據標記 // 節點添加圓形分區(添加三頁扇形) function drawCirclePartition(d) { // 圓形分區佈局(數據轉換) var radius = 40; var partition = d3.layout.partition() .sort(null) .size([2 * Math.PI, radius * radius]) // 第一個值域時2 PI,第二個值時圓半徑的平方 .value(function (d) { return 1; }); // 繪製圓形分區圖 // 若是以圓形的形式來轉換數據那麼d.x和d.y分別表明圓弧的繞圓心 // 方向的起始位置和由圓心向外的起始位置d.dx和d.dy分別表明各自的寬度 var arc = d3.svg.arc() .startAngle(function (d) { return d.x; }) .endAngle(function (d) { return d.x + d.dx; }) .innerRadius(function (d) { return 26; }) .outerRadius(function (d) { return 80; }); var circlePart = partition.nodes(dataCirclePartition); // "☿" 釋放固定的節點 function releaseNode() { d.fixed = false; // force.start(); // 開啓或恢復結點間的位置影響 force.resume(); } // "✂" 刪除當前節點以及當前節點到其它節點之間的關係 function removeNode() { var newNodes = []; for (var i = 0; i < nodes.length; i++) { var obj = nodes[i]; if (obj.id != d.id) { newNodes.push(obj); } } var newedges = []; for (var i = 0; i < edges.length; i++) { var obj = edges[i]; if ((d.index != obj.source.index) && (d.index != obj.target.index)) { newedges.push(obj); } } nodes = newNodes; edges = newedges; var nIndex = function (d) { return d.index; }; var lIndex = function (d) { return d.id; }; // 經過添加'g'元素分組刪除 svg.select("#circleAndText").selectAll("circle") .data(nodes, nIndex) .exit() .remove(); svg.select("#circleAndText").selectAll("image") .data(nodes, nIndex) .exit() .remove(); svg.select("#circleAndText").selectAll("text") .data(nodes, nIndex) .exit() .remove(); svg.select("#lineAndText").selectAll("line") .data(edges, lIndex) .exit() .remove(); svg.select("#lineAndText").selectAll("text") .data(edges, lIndex) .exit() .remove(); }
// 擴展當前節點,距離爲1 // 1.從rawData(rawNodes/rawEdges)中找出當前節點須要擴展的節點與關係數據 // 2.拿出須要擴展的數據到node/edges中去除已經綁定圖形元素的數據 // 3.將過濾出的未綁定圖形元素須要擴展的數據從新調用構圖方法進行構圖 // 添加從服務器實時加載數據的功能:基本思想與1~3相似 function extendNode() { var index = d.index; var arrEdges = [], arrIndex = [], arrNodes = []; for (var i = 0; i < rawEdges.length; i++) { if ((index == rawEdges[i].source.index) || (index == rawEdges[i].target.index)) { arrEdges.push(rawEdges[i]); if (index != rawEdges[i].source.index) { arrIndex.push(rawEdges[i].source.index); } else if (index != rawEdges[i].target.index) { arrIndex.push(rawEdges[i].target.index); } } edgesMark.push(rawEdges[i].id); } for (var i = 0; i < rawNodes.length; i++) { for (var j = 0; j < arrIndex.length; j++) { var obj = arrIndex[j]; if (rawNodes[i].index == obj) { arrNodes.push(rawNodes[i]); } } nodesMark.push(rawNodes[i].id); indexNodeMark.push(rawNodes[i].index); } // nodes.push(arrNodes); // edges.push(arrEdges); var nodesRemoveIndex = []; for (var i = 0; i < arrNodes.length; i++) { var obj = arrNodes[i]; for (var j = 0; j < nodes.length; j++) { var obj2 = nodes[j]; if (obj.index == obj2.index) { nodesRemoveIndex.push(i); } } } var edgesRemoveIndex = []; for (var i = 0; i < arrEdges.length; i++) { var obj = arrEdges[i]; for (var j = 0; j < edges.length; j++) { var obj2 = edges[j]; if (obj.id == obj2.id) { edgesRemoveIndex.push(obj.id); } } } var coverNodes = []; for (var i = 0; i < arrNodes.length; i++) { var obj = arrNodes[i]; if (!isInArray(nodesRemoveIndex, i)) { nodes.push(obj); coverNodes.push(obj); } } var coverEdges = []; for (var i = 0; i < arrEdges.length; i++) { var obj = arrEdges[i]; if (!isInArray(edgesRemoveIndex, obj.id)) { edges.push(obj); coverEdges.push(obj); } } // console.log("找出須要擴展的數據"); // console.log(arrEdges); // console.log(arrNodes); // console.log("添加到原始須要綁定圖形元素的數據集集合/與rawNodes,rawEdges服務器加載的原始數據保持區分"); // console.log(nodes); // console.log(edges); // 添加從服務器請求擴展數據 // var url = "http://222.216.195.154:7476/knowledge-graph/hello/dataSource/node/extend/" + d.id + ""; // d3.json(url, function (error, json) { // 服務器加載知識圖譜數據 // if (error) { // return console.warn(error); // } // console.log("從服務器請求的擴展數據:"); // var serverNodes = json.nodes; // var serverEdges = json.links; // console.log(serverNodes); // console.log(serverEdges); // console.log(nodesMark); // console.log(edgesMark); // // 從新設置INDEX // var maxIndex = Math.max.apply(null, indexNodeMark); // console.log("MAX:" + maxIndex); // // for (var i = 0; i < serverNodes.length; i++) { // if (!isInArray(nodesMark, serverNodes[i].id)) { // serverNodes[i].index = maxIndex + 1 // maxIndex = maxIndex + 1; // nodes.concat(serverNodes[i]); // console.log(serverNodes[i]); // } // } // for (var i = 0; i < serverEdges.length; i++) { // if (!isInArray(edgesMark, serverEdges[i].id)) { // edges.concat(serverEdges); // console.log(serverEdges[i]); // } // } // console.log("服務器加載而且合併以後的數據:"); // console.log(nodes); // console.log(edges); // d3.select("#svgGraph").select("#svgOne").selectAll("*").remove(); // 清空SVG中的內容 // buildGraph(); // }); d3.select("#svgGraph").select("#svgOne").selectAll("*").remove(); // 清空SVG中的內容 buildGraph(); } var arcs = svg.append("g") .attr("id", "eee") .attr("transform", "translate(" + d.x + "," + d.y + ")") .selectAll("g") .data(circlePart) .enter() .append("g") .on("click", function (d) { // 圓形分區綁定Click事件 if (d.name == "☿") { releaseNode(); } if (d.name == "✂") { removeNode(); } if (d.name == "✠") { extendNode(); } if (d.name == "◎") { startForce(); } if (d.name == "오") { stopForce(); } d3.select("#eee").remove(); d3.select("#tooltipCir").remove(); }); arcs.append("path") .attr("display", function (d) { return d.depth ? null : "none"; // hide inner ring }) .attr("d", arc) .style("stroke", "#fff") .style("fill", "#A9A9A9") .on("mouseover", function (d) { d3.select(this).style("fill", "#747680"); addToolTipCir(d); //添加提示框的div }) .on("mouseout", function () { d3.select("#tooltipCir").remove(); d3.select(this).transition() .duration(200) .style("fill", "#ccc") var array = printPosition(); var distance = Math.sqrt(Math.pow((d.x - array[0]), 2) + Math.pow((d.y - array[1]), 2)); if (distance > 80) { d3.select("#eee").remove(); // 刪除節點扇形 } }); arcs.append("text") .style("font-size", "16px") .style("font-family", "simsun") .style("fill", "white") .attr("text-anchor", "middle") .attr("transform", function (d, i) { // 平移和旋轉 var r = 0; if ((d.x + d.dx / 2) / Math.PI * 180 < 180) // 0-180度之內的 r = 180 * ((d.x + d.dx / 2 - Math.PI / 2) / Math.PI); else // 180-360度 r = 180 * ((d.x + d.dx / 2 + Math.PI / 2) / Math.PI); return "translate(" + arc.centroid(d) + ")" + "rotate(" + r + ")"; }) .text(function (d) { return d.name; }); return arcs; }
// 添加描述關係文字 function add_text_edges() { var svg_text_edges = svg.select("#lineAndText") .selectAll("line.text") .data(edges) .enter() .append("text") .attr("id", function (d) { return d.id; }) .style("fill", "#ccc") .attr("x", function (d) { return (d.source.x + d.target.x) / 2 }) .attr("y", function (d) { return (d.source.y + d.target.y) / 2 }) .text(function (d) { return d.type; }) .on("mouseover", function (d) { // 鼠標選中時觸發 mouseSelectLine(d); addToolTip(d); //添加提示框的div }) .on("mouseout", function () { d3.select("#relation").remove(); d3.select("#tooltip").remove(); }) .on("click", function () { }); return svg_text_edges; }
// 對於每個時間間隔進行更新 function refresh() { force.on("tick", function () { // 對於每個時間間隔 // 更新連線座標· svg_edges.attr("x1", function (d) { return d.source.x; }) .attr("y1", function (d) { return d.source.y; }) .attr("x2", function (d) { return d.target.x; }) .attr("y2", function (d) { return d.target.y; }); // 更新節點以及文字座標 svg_nodes.attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; }); // 更新關係文字座標 svg_text_edges.attr("x", function (d) { return (d.source.x + d.target.x) / 2 }) .attr("y", function (d) { return (d.source.y + d.target.y) / 2 }); }); }
var force, nodes = [], edges = [], rawNodes, rawEdges, mapNodes = new Map(); // 構建知識圖譜須要操做的數據 (rawNodes, rawEdges將加載的原始構圖數據緩存一份) // 知識圖譜可視化構建 function graph(data) { // 先清空佈局中的圖形元素 // d3.select("#svgGraph").select("#svgOne").selectAll("*").remove(); // var serverD = data.nodes; // var serverE = data.links; // 去除NODES中重複的節點,若是有節點重複即將EDGES中的數據從新設置source值和target值 // serverD,serverE,nodes,edges // var filterServerD = []; // for (var i = 0; i < serverD.length; i++) { // if (!isInArray(nodesIndexId, serverD[i].id)) { // filterServerD.push(serverD[i]); // } // } // 去重以後從新調整filterServerD的NODE index值 // mapNodes.forEach(function (value, key) { // console.log(value); // console.log(key); // if (isInArray(nodesIndexId,key)){ // // } // }); // recordNodesIndex(serverD); // console.log(nodesIndexValue); // 多數組鏈接 // nodes = nodes.concat(data.nodes); // edges = edges.concat(data.links); // console.log(nodes); // console.log(edges); // rawNodes = nodes; // rawEdges = edges; // // 定義力佈局(數據轉換) // force = d3.layout.force() // .nodes(nodes) // 指定節點數組 // .links(edges) // 指定連線數組 // .size([width, height]) // 指定範圍 // .linkDistance(150) // 指定連線長度 // // .gravity(0.02) // 設置引力避免躍出佈局 // .friction(0.9) // 設置摩擦力速度衰減 // .charge(-400); // 相互之間的做用力 // force.start(); // 開始做用 // buildGraph(); }
var svg_edges, svg_nodes, svg_text_edges; // 須要動態更新的函數(dynamic update function) // Strat build Knowledge Graph/Vault function buildGraph() { console.log("開始構建可視化知識圖譜:"); console.log(nodes); console.log(edges); svg_edges = add_edges(); // 添加連線與箭頭 svg_nodes = add_nodes(); // 添加節點與文字 svg_text_edges = add_text_edges(); // 添加描述關係的文字 refresh(); // 對於每個時間間隔進行更新 force.resume(); // 必須添加不然圖形元素更新不及時 }
// 服務器加載數據 var dataCirclePartition; function load() { d3.json("http://222.216.195.154:7476/knowledge-graph/hello/dataSource/type/1", function (error, root) { // 服務器加載節點圓形分區數據 if (error) { return console.warn(error); } dataCirclePartition = root; }); d3.json("http://222.216.195.154:7476/knowledge-graph/hello/dataSource/type/2", function (error, json) { // 服務器加載知識圖譜數據 if (error) { return console.warn(error); } console.log("初始加載:"); console.log(json.nodes); console.log(json.links); graph(json); }); // d3.json("http://222.216.195.154:7476/knowledge-graph/hello/dataSource/node/extend/99817", function (error, json) { // 服務器加載知識圖譜數據 // if (error) { // return console.warn(error); // } // console.log("初始加載:"); // console.log(json); // graph(json); // }); } // 初始化圖數據庫配置信息 startNeo4j(); // 執行知識圖譜數據可視化 load(); // 傳入NODE ID與NODE INDEX,節點的INDEX與構圖時數據加載的順序密切相關 function loadById(id, maxNodeIndex, nodesIdList) { // var para = ["id:" + id, "maxNodeIndex:" + maxNodeIndex, "nodesIdList:" + nodesIdList]; var para = {"id": id, "maxNodeIndex": maxNodeIndex, "nodesIdList": nodesIdList}; console.log(para); d3.json("http://222.216.195.154:7476/knowledge-graph/hello/dataSource/node/idIndex/" + para, function (error, data) { // 服務器加載知識圖譜數據 if (error) { return console.warn(error); } console.log("動態ID加載的數據:"); console.log(nodesMark); console.log(edgesMark); console.log(nodes); console.log(edges); console.log(data); graph(data); }); } function loadZdrSearch(json) { d3.json("http://222.216.195.154:7476/knowledge-graph/hello/dataSource/type/1", function (error, root) { // 服務器加載節點圓形分區數據 if (error) { return console.warn(error); } dataCirclePartition = root; }); graph(json); } // 執行知識圖譜數據可視化 // loadById(id);
啓動入口類KnowledgeGraphApplication以後,
調用接口:http://localhost:7476/knowledge-graph/hello/index
此接口調用控制類加載index.html,HTML中調用了js文件加載展現數據,詳細的實現過程請看完整的代碼註釋。
代碼目錄結構說明一:
代碼目錄結構說明二:
☿ 解鎖當前節點
✂ 剪切當前節點於關係
✠ 擴展當前節點與關係
오 固定全部節點
◎ 解鎖全部節點
實體擴展功能:
節點效果:
完整效果顯示示例一:
完整效果顯示示例二:
知識圖譜可視化
注:本文著做權歸做者,由demo大師代發,拒絕轉載,轉載須要做者受權