輔助圖分析人員的自定義圖數據可視化組件

在圖數據處理過程中,如果無法使用形象化的展示,可能會給分析人員帶來一定困惑。下面這個組件可以在需要的時候,將圖數據形象化的展示出來。

/**
* 可視化組件類的使用方式:
* 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();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章