在图数据处理过程中,如果无法使用形象化的展示,可能会给分析人员带来一定困惑。下面这个组件可以在需要的时候,将图数据形象化的展示出来。
/**
* 可视化组件类的使用方式:
* 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();