在圖數據處理過程中,如果無法使用形象化的展示,可能會給分析人員帶來一定困惑。下面這個組件可以在需要的時候,將圖數據形象化的展示出來。
/**
* 可視化組件類的使用方式:
* 1、獲取數據
* 2、封裝d3格式數據
* 3、啓動http服務
* 4、查看可視化效果
**/
package casia.isi.neo4j.visual;
import casia.isi.neo4j.http.server.HttpService;
import casia.isi.neo4j.util.FileUtil;
import casia.isi.neo4j.util.JSONTool;
import com.alibaba.fastjson.JSONObject;
import java.io.IOException;
/**
* @author YanchaoMa [email protected]
* @PACKAGE_NAME: casia.isi.neo4j.visual
* @Description: TODO(Visualization Plugin Run)
* @date 2019/8/22 10:14
*/
public class Visualization {
private final static String VISUAL_DATA_PATH = "neo-import-csv/check-graph-traversal.json";
/**
* @param queryResult:檢索工具的查詢結果
* @return
* @Description: TODO(Run graph data visualization)
*/
public void run(JSONObject queryResult) throws IOException {
JSONObject result = JSONTool.transferToOtherD3(queryResult);
FileUtil.writeFileByNewFile(VISUAL_DATA_PATH, result.toJSONString());
new HttpService().run();
}
/**
* @param queryResult:檢索工具的查詢結果
* @param port:指定HTTP服務的端口啓動 - 需要對應在neo4j-engine-inter\src\main\resources\static\js\graph.js中修改HTTP請求的端口
* @return
* @Description: TODO(Run graph data visualization)
*/
public void run(JSONObject queryResult, int port) throws IOException {
JSONObject result = JSONTool.transferToOtherD3(queryResult);
FileUtil.writeFileByNewFile(VISUAL_DATA_PATH, result.toJSONString());
new HttpService().run(port);
}
}
一、可視化效果
二、neo4j的數據封裝
數據封裝請參考:基於neo4j-java-driver驅動訪問圖庫封裝rest-api風格的數據
三、將封裝好的數據轉換爲d3.js格式並寫入文件
// queryResult是‘一’中封裝好的數據
JSONObject result = JSONTool.transferToOtherD3(queryResult);
package casia.isi.neo4j.util;
import casia.isi.neo4j.common.SortOrder;
import casia.isi.neo4j.model.Label;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* @author YanchaoMa [email protected]
* @PACKAGE_NAME: casia.isi.neo4j.util
* @Description: TODO(處理JSON數據)
* @date 2019/7/10 9:49
*/
public class JSONTool {
/**
* @param
* @return
* @Description: TODO(去掉JSON數據的KEY的雙引號)
*/
public static String removeKeyDoubleQuotationMarkJustEnglish(JSON json) {
// 僅支持英文
// DATA PACKAGE中屬性KEY不能有雙引號
String dataPackage = null;
if (json != null) {
dataPackage = json.toJSONString().replaceAll("\"(\\w+)\"(\\s*:\\s*)", "$1$2");
}
return dataPackage;
}
/**
* @param
* @return
* @Description: TODO(去掉JSONArray數據的KEY的雙引號)
*/
public static String removeKeyDoubleQuotationMark(JSONArray array) {
StringBuilder builder = new StringBuilder();
builder.append("[");
int size = array.size();
for (int i = 0; i < size; i++) {
JSONObject object = array.getJSONObject(i);
String objectStr = removeJSONObjKeyDoubleQuotationMark(object);
if (i == size - 1) {
builder.append(objectStr);
} else {
builder.append(objectStr + ",");
}
}
builder.append("]");
String dataPackage = builder.toString();
return dataPackage;
}
/**
* @param
* @return
* @Description: TODO(去掉JSONObject數據的KEY的雙引號)
*/
public static String removeJSONObjKeyDoubleQuotationMark(JSONObject object) {
StringBuilder builder = new StringBuilder();
builder.append("{");
int j = 0;
int outSize = object.size();
for (Map.Entry entry : object.entrySet()) {
String key = (String) entry.getKey();
Object value = entry.getValue();
builder.append(key.replace("\"", "") + ":");
if (value instanceof JSONObject) {
JSONObject valueObj = (JSONObject) value;
builder.append("{");
int i = 0;
int size = valueObj.size();
for (Map.Entry entry2 : valueObj.entrySet()) {
String key2 = (String) entry2.getKey();
Object value2 = entry2.getValue();
builder.append(key2.replace("\"", "") + ":");
i++;
value2 = repalceChars(value2);
if (i == size) {
builder.append("\"" + value2 + "\"");
} else {
builder.append("\"" + value2 + "\",");
}
}
j++;
if (j == outSize) {
builder.append("}");
} else {
builder.append("},");
}
} else if (value instanceof JSONArray) {
builder.append(((JSONArray) value).toJSONString());
} else {
j++;
value = repalceChars(value);
if (j == outSize) {
builder.append("\"" + value + "\"");
} else {
builder.append("\"" + value + "\",");
}
}
}
builder.append("}");
String dataPackage = builder.toString();
return dataPackage;
}
/**
* @param
* @return
* @Description: TODO(去掉JSONObject數據的KEY的雙引號)
*/
public static String removeOnlyJSONObjectKeyDoubleQuotation(JSONObject object) {
StringBuilder builder = new StringBuilder();
builder.append("{");
Set<Map.Entry<String, Object>> entries = object.entrySet();
Iterator<Map.Entry<String, Object>> iterator = entries.iterator();
boolean hasNext = iterator.hasNext();
while (hasNext) {
Map.Entry<String, Object> next = iterator.next();
String key = next.getKey();
Object value = next.getValue();
builder.append(key.replace("\"", "") + ":");
builder.append(value.toString());
hasNext = iterator.hasNext();
if (hasNext) {
builder.append(",");
}
}
builder.append("}");
String dataPackage = builder.toString();
return dataPackage;
}
/**
* @param
* @return
* @Description: TODO(替換影響數據入庫的特殊字符)
*/
private static Object repalceChars(Object object) {
if (object instanceof String) {
String entityName = (String) object;
if (entityName != null) {
// 先替換反斜槓
entityName = entityName.replace("\\", "\\\\");
// 再替換單引號
entityName = String.valueOf(entityName).replace("'", "\\'");
// 再替換雙引號
entityName = String.valueOf(entityName).replace("\"", "\\\"");
return entityName;
} else {
return object;
}
} else {
return object;
}
}
/**
* @param
* @return
* @Description: TODO(按照D3格式打包數據)
*/
public static JSONObject packD3Json(JSONObject result) {
if (result != null && !result.isEmpty()) {
JSONObject queryResult = result.getJSONArray("queryResultList").getJSONObject(0);
result.put("message", queryResult.getBooleanValue("message"));
result.put("results", queryResult.getJSONArray("results"));
result.put("totalNodeSize", queryResult.getIntValue("totalNodeSize"));
result.put("totalRelationSize", queryResult.getIntValue("totalRelationSize"));
result.put("errors", queryResult.getJSONArray("errors"));
result.remove("queryResultList");
// 重新組合RESULTS
JSONArray results = result.getJSONArray("results");
if (!results.isEmpty()) {
JSONObject resultsVri = results.getJSONObject(0);
JSONArray data = resultsVri.getJSONArray("data");
JSONArray relationships = new JSONArray();
JSONArray nodes = new JSONArray();
JSONArray properties = new JSONArray();
data.stream().forEach(v -> {
JSONObject graph = (JSONObject) v;
relationships.addAll(graph.getJSONObject("graph").getJSONArray("relationships"));
nodes.addAll(graph.getJSONObject("graph").getJSONArray("nodes"));
properties.addAll(graph.getJSONObject("graph").getJSONArray("properties"));
});
data.clear();
JSONObject relaNodes = new JSONObject();
relaNodes.put("relationships", distinctRelation(relationships));
relaNodes.put("nodes", distinctAndRemoveNull(nodes));
relaNodes.put("properties", properties);
JSONObject graph = new JSONObject();
graph.put("graph", relaNodes);
data.add(graph);
}
return result;
}
return result;
}
/**
* @param
* @return
* @Description: TODO(排重關係)
*/
private static JSONArray distinctRelation(JSONArray relationships) {
return relationships.parallelStream().filter(v -> v != null).filter(distinctById(v -> {
JSONObject object = (JSONObject) v;
if (object != null && object.containsKey("id")) {
return object.getString("id");
} else {
return null;
}
})).collect(Collectors.toCollection(JSONArray::new));
}
/**
* @param
* @return
* @Description: TODO(排重節點並去掉標籤的null值)
*/
private static JSONArray distinctAndRemoveNull(JSONArray nodes) {
nodes.removeIf(v -> v == null);
if (!nodes.isEmpty()) {
return nodes.parallelStream().filter(distinctById(v -> {
JSONObject object = (JSONObject) v;
if (object != null) {
return object.getString("id");
} else {
return null;
}
})).map(v -> {
JSONObject object = (JSONObject) v;
JSONArray labels = object.getJSONArray("labels");
labels = labels.parallelStream().filter(obj -> obj != null).collect(Collectors.toCollection(JSONArray::new));
object.put("labels", labels);
return object;
}).sorted((object1, object2) -> {
// searchEngineWeight排序
JSONObject nodePro1 = object1.getJSONObject("properties");
JSONObject nodePro2 = object2.getJSONObject("properties");
Double dou1 = nodePro1.getDouble("searchEngineWeight");
Double dou2 = nodePro2.getDouble("searchEngineWeight");
return weightCompare(dou1, dou2);
}).collect(Collectors.toCollection(JSONArray::new));
} else {
return nodes;
}
}
/**
* @param
* @return
* @Description: TODO(權重比較)
*/
private static int weightCompare(Double d1, Double d2) {
Optional<Double> dou1 = Optional.ofNullable(d1);
Optional<Double> dou2 = Optional.ofNullable(d2);
Integer int1 = 0, int2 = 0;
if (dou1.orElse(0.0).intValue() == dou2.orElse(0.0).intValue()) {
if (dou1.orElse(0.0) > dou2.orElse(0.0)) {
int1 = dou1.orElse(0.0).intValue() + 1;
int2 = dou2.orElse(0.0).intValue();
} else if (dou1.orElse(0.0) < dou2.orElse(0.0)) {
int1 = dou1.orElse(0.0).intValue();
int2 = dou2.orElse(0.0).intValue() + 1;
}
} else {
int1 = dou1.orElse(0.0).intValue();
int2 = dou2.orElse(0.0).intValue();
}
// SEARCH ENGINE WEIGHT RESULT ASC
return int2 - int1;
}
/**
* @param
* @return
* @Description: TODO(對節點集通過ID去重)
*/
public static <T> Predicate<T> distinctById(Function<? super T, ?> idExtractor) {
Map<Object, Boolean> seen = new ConcurrentHashMap<>();
return t -> seen.putIfAbsent(idExtractor.apply(t), Boolean.TRUE) == null;
}
/**
* @param
* @return
* @Description: TODO(對返回的D3格式數據進行排序)
*/
public static JSONObject sortD3GraphDataByNodeProperty(JSONObject result, String sortField, SortOrder sortOrder) {
JSONArray nodeList = getNodeOrRelaList(result, "nodes");
JSONArray sortNodeList = nodeList.stream().sorted((v1, v2) -> {
JSONObject node1 = (JSONObject) v1;
JSONObject node2 = (JSONObject) v2;
JSONObject nodes1Pro = node1.getJSONObject("properties");
JSONObject nodes2Pro = node2.getJSONObject("properties");
if (nodes1Pro.containsKey(sortField) && nodes2Pro.containsKey(sortField)) {
Integer para1 = nodes1Pro.getInteger(sortField);
Integer para2 = nodes2Pro.getInteger(sortField);
if (SortOrder.ASC.equals(sortOrder))
return para1.compareTo(para2);
else if (SortOrder.DESC.equals(sortOrder))
return para2.compareTo(para1);
}
return 0;
}).collect(Collectors.toCollection(JSONArray::new));
return putNodeOrRelaList(result, sortNodeList, "nodes");
}
/**
* @param resultObject:結果集合
* @param key:拿到節點或者關係集合 relationships-獲取關係列表 nodes-獲取節點列表
* @return
* @Description: TODO(從結果集解析NODE列表 - 判斷結果集是否NODES爲空)
*/
public static JSONArray getNodeOrRelaList(JSONObject resultObject, String key) {
if (resultObject != null) {
JSONArray jsonArray = resultObject.getJSONArray("results");
JSONObject jsonObject = jsonArray.getJSONObject(0);
JSONArray jsonArray1 = jsonObject.getJSONArray("data");
JSONObject jsonObject1 = jsonArray1.getJSONObject(0);
JSONObject jsonObject2 = jsonObject1.getJSONObject("graph");
return jsonObject2.getJSONArray(key);
}
return new JSONArray();
}
/**
* @param resultObject:結果集合
* @param nodesOrRelas:需要被放回的節點列表或關係列表
* @param key:拿到節點或者關係集合 relationships-獲取關係列表 nodes-獲取節點列表
* @return
* @Description: TODO(從結果集解析NODE列表 - 判斷結果集是否NODES爲空)
*/
public static JSONObject putNodeOrRelaList(JSONObject resultObject, JSONArray nodesOrRelas, String key) {
if (resultObject != null) {
JSONArray jsonArray = resultObject.getJSONArray("results");
JSONObject jsonObject = jsonArray.getJSONObject(0);
JSONArray jsonArray1 = jsonObject.getJSONArray("data");
JSONObject jsonObject1 = jsonArray1.getJSONObject(0);
JSONObject jsonObject2 = jsonObject1.getJSONObject("graph");
jsonObject2.put(key, nodesOrRelas);
return resultObject;
}
return resultObject;
}
/**
* @param result:D3格式數據
* @param reserveLabels:需要保留的標籤數組
* @return
* @Description: TODO(結果集裏面過濾 ( 只保留指定標籤的節點 ))
*/
public static JSONObject filterD3GraphDataByNodeLabel(JSONObject result, Label[] reserveLabels) {
List<String> labelList = new ArrayList<>();
for (int i = 0; i < reserveLabels.length; i++) {
Label reserveLabel = reserveLabels[i];
labelList.add(reserveLabel.name());
}
JSONArray nodeList = getNodeOrRelaList(result, "nodes");
JSONArray sortNodeList = nodeList.stream().filter(v -> {
JSONObject node = (JSONObject) v;
JSONArray labels = node.getJSONArray("labels");
if (hasLabel(labels, labelList))
return true;
return false;
}).collect(Collectors.toCollection(JSONArray::new));
return putNodeOrRelaList(result, sortNodeList, "nodes");
}
/**
* @param labels:原始數據的標籤
* @param reserveLabels:需要被保留的節點標籤數組
* @return
* @Description: TODO(標籤包含判斷)
*/
private static boolean hasLabel(JSONArray labels, List<String> reserveLabels) {
for (int i = 0; i < labels.size(); i++) {
String label = (String) labels.get(i);
if (reserveLabels.contains(label))
return true;
}
return false;
}
public static JSONObject tansferGenericPara(Object[] config) {
if (config.length % 2 != 0) throw new IllegalArgumentException();
JSONObject paraMap = new JSONObject();
for (int i = 0; i < config.length; i++) {
Object para = config[i];
try {
paraMap.put(String.valueOf(para), config[i + 1]);
} catch (Exception e) {
e.printStackTrace();
}
i++;
}
return paraMap;
}
/**
* @param
* @return
* @Description: TODO(獲取節點集的最大ID)
*/
public static long getMaxNodeId(JSONArray nodes) {
Optional optional = nodes.parallelStream().max(Comparator.comparingInt(v -> {
JSONObject object = (JSONObject) v;
return object.getInteger("id");
}));
return optional.isPresent() ? JSONObject.parseObject(String.valueOf(optional.get())).getLongValue("id") : -1;
}
/**
* @param
* @return
* @Description: TODO(獲取節點集的最小ID)
*/
// parallelStream裏直接去修改變量是非線程安全的,但是採用collect和reduce操作就是滿足線程安全的
public static long getMinNodeId(JSONArray nodes) {
Optional optional = nodes.parallelStream().min(Comparator.comparingInt(v -> {
JSONObject object = (JSONObject) v;
return object.getInteger("id");
}));
return optional.isPresent() ? JSONObject.parseObject(String.valueOf(optional.get())).getLongValue("id") : -1;
}
/**
* @param
* @return
* @Description: TODO(重新統計節點和關係數量)
*/
public static JSONObject recountD3NodeRelation(JSONObject traversal) {
traversal.put("totalNodeSize", getNodeOrRelaList(traversal, "nodes").size());
traversal.put("totalRelationSize", getNodeOrRelaList(traversal, "relationships").size());
return traversal;
}
/**
* @param result:原始數據-高版本D3格式封裝的數據
* @return
* @Description: TODO(轉換爲d3 - 3.2.8支持的格式數據)
*/
public static JSONObject transferToOtherD3(JSONObject result) {
JSONObject data = new JSONObject();
HashMap<Long, Integer> nodeIndexMap = packNodeIndexMap(getNodeOrRelaList(result, "nodes"));
JSONArray nodes = getNodeOrRelaList(result, "nodes")
.parallelStream()
.map(v -> {
JSONObject node = (JSONObject) v;
JSONObject properties = node.getJSONObject("properties");
node.put("index", nodeIndexMap.get(node.getLongValue("id")));
String name = properties.getString("name") == null ? "" : properties.getString("name");
String _entity_name = properties.getString("_entity_name") == null ? "" : properties.getString("_entity_name");
node.put("name", name + _entity_name);
//
// node.put("image", "default.png");
node.put("image", "node-image-4.png");
// node.put("image", "twitter.svg");
node.remove("properties");
node.remove("labels");
return node;
})
.collect(Collectors.toCollection(JSONArray::new));
JSONArray relationships = getNodeOrRelaList(result, "relationships")
.parallelStream()
.map(v -> {
JSONObject relation = (JSONObject) v;
relation.put("sourceId", relation.getLongValue("startNode"));
relation.put("targetId", relation.getLongValue("endNode"));
relation.put("source", nodeIndexMap.get(relation.getLongValue("startNode")));
relation.put("target", nodeIndexMap.get(relation.getLongValue("endNode")));
relation.remove("startNode");
relation.remove("endNode");
relation.remove("properties");
return relation;
})
.collect(Collectors.toCollection(JSONArray::new));
data.put("nodes", nodes);
data.put("links", relationships);
return data;
}
private static HashMap<Long, Integer> packNodeIndexMap(JSONArray nodes) {
HashMap<Long, Integer> map = new HashMap<>();
for (int i = 0; i < nodes.size(); i++) {
JSONObject node = nodes.getJSONObject(i);
map.put(node.getLongValue("id"), i);
}
return map;
}
/**
* @param filterId:被過濾的節點ID
* @return
* @Description: TODO(打包節點IDS)
*/
public static List<Long> packNodeIds(JSONObject result, long filterId) {
JSONArray nodes = getNodeOrRelaList(result, "nodes");
return nodes.parallelStream()
.map(v -> {
JSONObject node = (JSONObject) v;
return node.getLongValue("id");
})
.filter(v -> v != filterId)
.collect(Collectors.toCollection(ArrayList::new));
}
/**
* @param
* @return
* @Description: TODO(打包節點IDS)
*/
public static List<Long> packNodeIds(JSONObject result) {
JSONArray nodes = getNodeOrRelaList(result, "nodes");
return nodes.parallelStream()
.map(v -> {
JSONObject node = (JSONObject) v;
return node.getLongValue("id");
})
.collect(Collectors.toCollection(ArrayList::new));
}
public static JSONArray resultRetrievePro(JSONObject clusterObject) {
return clusterObject.getJSONArray("queryResultList").getJSONObject(0).getJSONArray("retrieve_properties");
}
/**
* @param labelsTreeResult:標籤樹數據
* @param leafResult:葉子節點
* @return
* @Description: TODO(標籤樹葉子節點MAP)
*/
public static Map<Label, List<Label>> labelsTreeChildMap(JSONObject labelsTreeResult, JSONObject leafResult) {
Map<Label, List<Label>> leafLabelsMap = new HashMap<>();
JSONArray leafLabels = getNodeOrRelaList(leafResult, "nodes");
for (Object node : leafLabels) {
JSONObject nodeLeaf = (JSONObject) node;
JSONObject properties = nodeLeaf.getJSONObject("properties");
String label = properties.getString("labelName");
List<Label> fatherLabels = getFatherLabel(labelsTreeResult, nodeLeaf);
leafLabelsMap.put(Label.label(label), fatherLabels);
}
return leafLabelsMap;
}
private static List<Label> getFatherLabel(JSONObject labelsTreeResult, JSONObject nodeLeaf) {
List<Label> fatherLabels = new ArrayList<>();
long nodeLeafId = nodeLeaf.getLongValue("id");
JSONArray relations = getNodeOrRelaList(labelsTreeResult, "relationships");
JSONArray nodes = getNodeOrRelaList(labelsTreeResult, "nodes");
List<Long> relationIds = getRelationIds(relations, nodeLeafId);
if (!relationIds.isEmpty()) {
long relationId = relationIds.get(0);
fatherLabels.add(getLabelName(nodes, relationId));
List<Long> relationIds2 = getRelationIds(relations, relationId);
if (!relationIds2.isEmpty()) {
long relationId2 = relationIds2.get(0);
fatherLabels.add(0, getLabelName(nodes, relationId2));
}
}
return fatherLabels;
}
private static Label getLabelName(JSONArray nodes, long relationId2) {
List<String> filterLabel = nodes.parallelStream()
.filter(v -> {
JSONObject node = (JSONObject) v;
long id = node.getLongValue("id");
return id == relationId2;
})
.map(v -> {
JSONObject node = (JSONObject) v;
return node.getJSONObject("properties").getString("labelName");
})
.collect(Collectors.toCollection(ArrayList::new));
if (!filterLabel.isEmpty()) return Label.label(filterLabel.get(0));
return null;
}
private static List<Long> getRelationIds(JSONArray relations, long nodeLeafId) {
return relations.parallelStream()
.filter(v -> {
JSONObject relation = (JSONObject) v;
long endNodeId = relation.getLongValue("endNode");
return endNodeId == nodeLeafId;
})
.map(v -> {
JSONObject relation = (JSONObject) v;
return relation.getLongValue("startNode");
})
.collect(Collectors.toCollection(ArrayList::new));
}
/**
* @param leafLabelsMap:標籤樹葉子節點MAP,KEY是葉子節點,VALUE是葉子節點的所有父級節點
* @param result:一些檢索到的數據
* @return
* @Description: TODO(根據標籤樹補充父級標籤)
*/
public static JSONObject supplyFatherLabels(Map<Label, List<Label>> leafLabelsMap, JSONObject result) {
JSONArray nodesTransfer = getNodeOrRelaList(result, "nodes")
.parallelStream()
.map(v -> {
JSONObject node = (JSONObject) v;
JSONArray labels = node.getJSONArray("labels");
node.put("labels", mergeFatherLabels(leafLabelsMap, labels));
return node;
})
.collect(Collectors.toCollection(JSONArray::new));
putNodeOrRelaList(result, nodesTransfer, "nodes");
return result;
}
private static JSONArray mergeFatherLabels(Map<Label, List<Label>> leafLabelsMap, JSONArray labels) {
if (labels.size() == 1 && leafLabelsMap.containsKey(Label.label(labels.getString(0)))) {
String childLabel = labels.getString(0);
JSONArray fatherLabels = leafLabelsMap.get(Label.label(childLabel))
.parallelStream()
.map(v -> v.name())
.collect(Collectors.toCollection(JSONArray::new));
fatherLabels.add(childLabel);
return fatherLabels;
} else {
return labels;
}
}
/**
* @param result1:第一次的檢索結果
* @param result2:第二次的檢索結果
* @return
* @Description: TODO(合併兩個檢索結果)
*/
public static JSONObject mergeResult(JSONObject result1, JSONObject result2) {
if (!valueCheck(result1) || !valueCheck(result2)) throw new IllegalArgumentException();
final String NODE_KEY = "nodes";
final String RELATIONSHIP_KEY = "relationships";
final String NODE_SIZE = "totalNodeSize";
final String RELATIONSHIP_SIZE = "totalRelationSize";
// --NODES--
JSONArray node1 = getNodeOrRelaList(result1, NODE_KEY);
JSONArray node2 = getNodeOrRelaList(result2, NODE_KEY);
// MERGE
JSONArray mergeNodes = mergeDistinct(node1, node2);
// --RELATIONSHIPS--
JSONArray relationships1 = getNodeOrRelaList(result1, RELATIONSHIP_KEY);
JSONArray relationships2 = getNodeOrRelaList(result2, RELATIONSHIP_KEY);
// MERGE
JSONArray mergeRelationships = mergeDistinct(relationships1, relationships2);
// --PUT--
putNodeOrRelaList(result1, mergeNodes, NODE_KEY);
putNodeOrRelaList(result1, mergeRelationships, RELATIONSHIP_KEY);
// --MODIFY STATISTICS--
if (result1.containsKey(NODE_SIZE)) result1.put(NODE_SIZE, mergeNodes.size());
if (result1.containsKey(RELATIONSHIP_SIZE)) result1.put(RELATIONSHIP_SIZE, mergeRelationships.size());
return result1;
}
private static boolean valueCheck(JSONObject result) {
return result != null && !result.isEmpty();
}
/**
* @param
* @return
* @Description: TODO(合併且去重兩個相似的集合)
*/
private static JSONArray mergeDistinct(JSONArray assemble1, JSONArray assemble2) {
assemble1.addAll(assemble2);
return assemble1.parallelStream().distinct()
.collect(Collectors.toCollection(JSONArray::new));
}
public static boolean isNeoD3ObjEmpty(JSONObject result) {
JSONArray nodes = JSONTool.getNodeOrRelaList(result, "nodes");
return nodes == null || nodes.isEmpty();
}
public static JSONArray removeNull(JSONArray relationships) {
return relationships.stream().filter(rela -> {
JSONObject object = (JSONObject) rela;
String end = object.getString("endNode");
String startNode = object.getString("startNode");
return (end != null && !"".equals(end)) && (startNode != null && !"".equals(startNode));
}).collect(Collectors.toCollection(JSONArray::new));
}
}
四、啓動http服務加載文件數據
package casia.isi.neo4j.http.server;
import casia.isi.neo4j.common.NeoUrl;
import casia.isi.neo4j.util.FileUtil;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author YanchaoMa [email protected]
* @PACKAGE_NAME: casia.isi.neo4j.http.server
* @Description: TODO(HTTP服務端)
* @date 2019/7/22 9:50
*/
public class HttpService {
private static Logger logger = Logger.getLogger(HttpService.class);
private static String urlInterface;
public static String getUrlInterface() {
return urlInterface;
}
public static void setUrlInterface(String uri) throws UnknownHostException {
HttpService.urlInterface = "http://" + getLocalhostIP() + ":8000" + uri;
}
public static void setUrlInterface(String uri, int port) throws UnknownHostException {
HttpService.urlInterface = "http://" + getLocalhostIP() + ":" + port + uri;
}
private static String getLocalhostIP() throws UnknownHostException {
InetAddress address = InetAddress.getLocalHost();
return address.getHostAddress();
}
/**
* @param
* @Description: TODO(獲取CSV文件)
* @return
*/
private static class NeoCsvHandle implements HttpHandler {
@Override
public void handle(HttpExchange httpExchange) throws IOException {
URI uri = httpExchange.getRequestURI();
String uriPath = uri.getPath();
String csvName = uriPath.split("/")
.clone()[uriPath.split("/").length - 1];
String csvContent = FileUtil.getFileContent(NeoUrl.NEO_CSV.getSymbolValue(), csvName);
int length = 0;
if (csvContent != null) {
// SOLVE-PROBLEM-CORS:No 'Access-Control-Allow-Origin' header is present on the requested resource.
Headers responseHeaders = httpExchange.getResponseHeaders();
responseHeaders.set("Access-Control-Allow-Origin", "*");
length = csvContent.getBytes().length;
httpExchange.sendResponseHeaders(200, length);
OutputStream outputStream = httpExchange.getResponseBody();
outputStream.write(csvContent.getBytes());
outputStream.close();
}
logger.info("URI:" + uriPath + " CSV:" + csvName + " CSV-LENGTH:" + length);
}
}
/**
* @param port:端口號
* @return
* @Description: TODO(使用指定端口啓動)
*/
public void run(int port) throws IOException {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> {
try {
String uri = "/" + NeoUrl.NEO_CSV.getSymbolValue();
setUrlInterface(uri, port);
logger.info("Start http service:" + uri);
HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
server.createContext(uri, new NeoCsvHandle());
server.setExecutor(Executors.newCachedThreadPool());
server.start();
logger.info("Http service register ok! URL:" + urlInterface);
} catch (IOException e) {
e.printStackTrace();
}
});
}
/**
* @param
* @return
* @Description: TODO(使用默認端口號啓動)
*/
public void run() throws IOException {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> {
try {
String uri = "/" + NeoUrl.NEO_CSV.getSymbolValue();
setUrlInterface(uri);
logger.info("Start http service:" + uri);
HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
server.createContext(uri, new NeoCsvHandle());
server.setExecutor(Executors.newCachedThreadPool());
server.start();
logger.info("Http service register ok! URL:" + urlInterface);
} catch (IOException e) {
e.printStackTrace();
}
});
}
public static void main(String[] args) throws IOException {
PropertyConfigurator.configureAndWatch("config" + File.separator + "log4j.properties");
new HttpService().run();
}
}
五、使用HTML可視化圖數據
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>GRAPH VISUALIZATION</title>
<style type="text/css">
</style>
</head>
<body style="background: url(images/bg.jpg);">
<!--Load JavaScript-->
<script type='text/javascript' src="js/d3.js" charset="utf-8"></script>
<script type='text/javascript' src='js/graph.js'></script>
</body>
</html>
// 定義畫布 (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.index;
})
.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", "#A9A9A9");
}
// 添加連線
function add_edges() {
setMarkers(); // 設置連線箭頭屬性
var svg_edges = svg.select("#lineAndText")
.selectAll("line")
.data(edges)
.enter()
.append("line")
.attr("id", function (d) {
return d.index;
})
.style("stroke", "#A9A9A9")
.style("stroke_width", 1)
.attr("marker-end", function (d) {
return "url(#" + d.index + ")";
})
.attr("stroke", "#A9A9A9")
.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 "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", "#A9A9A9")
.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=\"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 re_line, re_circle, re_cir_text, re_line_text; // 擴展節點同步更新
// 節點添加圓形分區(添加三頁扇形)
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.index;
};
// 通過添加'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();
}
// 判斷元素是否在ARRAY中
function isInArray(arr, value) {
for (var i = 0; i < arr.length; i++) {
if (value === arr[i]) {
return true;
}
}
return false;
}
// 擴展當前節點,距離爲1
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);
}
}
}
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]);
}
}
}
// 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.index == obj2.index) {
edgesRemoveIndex.push(i);
}
}
}
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, i)) {
edges.push(obj);
coverEdges.push(obj);
}
}
// console.log("找出需要擴展的數據");
// console.log(arrEdges);
// console.log(arrNodes);
// console.log("添加到原始數據集");
// console.log(nodes);
// console.log(edges);
// d3.select("#svgGraph").remove(); // 刪除整個SVG
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", "#A9A9A9")
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.index;
})
.style("fill", "#A9A9A9")
.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; // 構建知識圖譜需要操作的數據 (rawNodes, rawEdges將加載的原始構圖數據緩存一份)
// 知識圖譜可視化構建
function graph(data) {
// 定義力佈局(數據轉換)
nodes = nodes.concat(data.nodes); // 多數組連接
edges = edges.concat(data.links);
rawNodes = nodes;
rawEdges = edges;
for (var i = 0; i < edges.length; i++) { // 關係數據添加INDEX值(爲了方便對應圖形元素)
var obj = edges[i];
obj.index = i;
}
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, 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() {
dataCirclePartition = {
"children": [
{
"name": "☿"
},
{
"name": "✂"
},
{
"name": "✠"
},
{
"name": "오"
},
{
"name": "◎"
}
]
};
// 使用HTTP服務請求數據
d3.json("http://localhost:8000/neo-import-csv/check-graph-traversal.json", function (error, data) {
if (error) {
return console.warn(error);
}
var json = data;
graph(json);
});
// graph(json);
}
// 執行知識圖譜數據可視化
load();