辅助图分析人员的自定义图数据可视化组件

在图数据处理过程中,如果无法使用形象化的展示,可能会给分析人员带来一定困惑。下面这个组件可以在需要的时候,将图数据形象化的展示出来。

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