需求
搜索水质监测自动站上游及下游的其他自动站,以有向图的形式展示在web端,最终效果图如下
代码
1、使用Python的networkx包进行有向图的构建
2、使用Cytoscape前端js库绘制有向图
安装依赖
pip install networkx==2.5 -i https://pypi.tuna.tsinghua.edu.cn/simple
后端代码
import json
from functools import reduce
import networkx as nx
from networkx.readwrite import json_graph
def list_dict_duplicate_removal(data_list):
return reduce(lambda x, y: x if y in x else x + [y], [[], ] + data_list)
class Graph(object):
def __init__(self, node=147):
self.dict_node = {96: "剔除敏感数据", 97: "剔除敏感数据", 98: "剔除敏感数据", 99: "剔除敏感数据", 100: "剔除敏感数据", 102: "剔除敏感数据",
103: "剔除敏感数据", 104: "剔除敏感数据", 105: "剔除敏感数据", 106: "剔除敏感数据", 107: "剔除敏感数据",
108: "剔除敏感数据", 109: "剔除敏感数据", 110: "剔除敏感数据", 111: "剔除敏感数据", 112: "剔除敏感数据", 113: "剔除敏感数据",
114: "剔除敏感数据", 115: "剔除敏感数据", 116: "剔除敏感数据", 117: "剔除敏感数据", 118: "剔除敏感数据", 119: "剔除敏感数据",
120: "剔除敏感数据", 121: "剔除敏感数据", 122: "剔除敏感数据", 123: "剔除敏感数据", 124: "剔除敏感数据", 125: "剔除敏感数据",
126: "剔除敏感数据", 127: "剔除敏感数据", 128: "剔除敏感数据", 129: "剔除敏感数据", 130: "剔除敏感数据", 131: "剔除敏感数据",
132: "剔除敏感数据", 133: "剔除敏感数据", 134: "剔除敏感数据", 135: "剔除敏感数据", 136: "剔除敏感数据", 137: "剔除敏感数据", 143: "剔除敏感数据",
144: "剔除敏感数据", 145: "剔除敏感数据", 146: "剔除敏感数据", 147: "剔除敏感数据", 148: "剔除敏感数据", 149: "剔除敏感数据", 150: "剔除敏感数据",
151: "剔除敏感数据", 152: "剔除敏感数据", 153: "剔除敏感数据", 154: "剔除敏感数据", 155: "剔除敏感数据", 156: "剔除敏感数据", 157: "剔除敏感数据",
158: "剔除敏感数据", 159: "剔除敏感数据", 160: "剔除敏感数据", 161: "剔除敏感数据", 162: "剔除敏感数据", 163: "剔除敏感数据",
164: "剔除敏感数据", 165: "剔除敏感数据", 166: "剔除敏感数据", 167: "剔除敏感数据", 168: "剔除敏感数据", 169: "剔除敏感数据", 170: "剔除敏感数据",
171: "剔除敏感数据", 172: "剔除敏感数据", 173: "剔除敏感数据", 175: "剔除敏感数据", 176: "剔除敏感数据", 177: "剔除敏感数据",
178: "剔除敏感数据", 179: "剔除敏感数据", 180: "剔除敏感数据", 181: "剔除敏感数据", 183: "剔除敏感数据", 184: "剔除敏感数据"}
self.relationship = [
(131, 132), (132, 133), (132, 134), (134, 147), (147, 180), (180, 179), (179, 170), (179, 123),
(179, 128), (179, 169), (170, 124), (123, 124), (124, 184), (184, 168), (128, 126), (169, 126),
(126, 127), (143, 144), (144, 145), (145, 146), (146, 147), (99, 152), (152, 151), (153, 150),
(150, 149), (149, 151), (151, 118), (151, 117), (118, 116), (117, 115), (116, 112), (115, 112),
(112, 119), (119, 125), (171, 115), (178, 171), (108, 178), (100, 108), (155, 100), (156, 155),
(155, 154), (119, 122), (108, 148), (108, 104), (104, 107), (108, 111), (111, 103), (97, 98),
(98, 96), (96, 176), (176, 114), (114, 113), (113, 130), (130, 129), (98, 158), (102, 109),
(109, 110), (110, 103), (102, 105), (105, 106), (106, 173), (173, 183), (183, 121), (121, 136),
(136, 122), (173, 172), (172, 177), (177, 175), (175, 120), (165, 163), (163, 160), (160, 161),
(161, 157), (157, 135), (157, 137), (162, 167), (167, 159), (181, 164), (164, 157), (125, 126),
(166, 167), (155, 98), (159, 163)
]
self.start_node = [131, 143, 99, 153, 156, 97, 102, 165, 162, 166, 181] # 上游
self.node = node
def search(self):
G = nx.DiGraph() # 创建空的简单有向图
G.add_nodes_from(list(self.dict_node.keys()))
G.add_edges_from(self.relationship)
nodes, edges = [], []
for node in self.start_node: # 遍历入河自动站
river = nx.dfs_successors(G, node) # 从上到下查找自动站
if self.node in river: # 判断要查找得自动站是否在该河流上
GG = nx.Graph(river)
for n in GG:
GG.nodes[n]["name"] = self.dict_node[n]
item_dict = json_graph.cytoscape_data(GG)
nodes.extend(item_dict['elements']['nodes'])
edges.extend(item_dict['elements']['edges'])
return {'elements': {'nodes': list_dict_duplicate_removal(nodes), 'edges': list_dict_duplicate_removal(edges)}}
if __name__ == "main":
print(json.dumps(Graph(147).search()))
前端
忘了从哪里扒的代码了,稍作修改了一下,使用ajax同步获取后端的接口数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
#cy {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
}
</style>
</head>
<body>
<div id="cy"></div>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/cytoscape/3.16.3/cytoscape.min.js"></script>
<script>
var waterName = "剔除敏感数据";
$.ajax({
cache: false,
type: "GET",
url: "http://127.0.0.1:6070/water/water_prediction/nearby_unit",
dataType: 'json',
data: {
"waterName": waterName
},
async: false,
success: function (data) {
if (data.code == 1) {
// 此处请求回来的数据其中 elements 字段为, 该数据通过python networkx包json_graph.cytoscape_data生成的图json数据
// {"edges":[{"data":{"source":102,"target":109}},{"data":{"source":102,"target":105}},{"data":{"source":109,"target":110}},{"data":{"source":110,"target":103}},{"data":{"source":105,"target":106}},{"data":{"source":106,"target":173}},{"data":{"source":173,"target":183}},{"data":{"source":173,"target":172}},{"data":{"source":183,"target":121}},{"data":{"source":121,"target":136}},{"data":{"source":136,"target":122}},{"data":{"source":172,"target":177}},{"data":{"source":177,"target":175}},{"data":{"source":175,"target":120}}],"nodes":[{"data":{"id":"102","name":"\u00\u67\u6c34\u93","value":102}},{"data":{"id":"109","name":"\05\cb3\uf8","value":109}},{"data":{"id":"110","name":"\ub1\u58def","value":110}},{"data":{"id":"105","name":"\u6c99\9d","value":105}},{"data":{"id":"106","name":"\u0\ub\u\u6","value":106}},{"data":{"id":"173","name":"\u4\u61","value":173}},{"data":{"id":"183","name":"\u5927\u59","value":183}},{"data":{"id":"121","name":"\u59","value":121}},{"data":{"id":"136","name":"\u4e3","value":136}},{"data":{"id":"172","name":"\u73","value":172}},{"data":{"id":"177","name":"\u01\90f\u89\u5c\u8","value":177}},{"data":{"id":"175","name":"\u6\u25\u6c\634\u9","value":175}},{"data":{"id":"103","name":"\u93\u7c\596\27\7","value":103}},{"data":{"id":"122","name":"\u7\u3\u7\uf8","value":122}},{"data":{"id":"120","name":"\u5\u98\u7c","value":120}}]}
elements = data.resData.elements;
} else if (data.code == 0) {
alert(data.msg);
}
},
error: function () {
elements = {"edges":[{"data":{"source":102,"target":109}},{"data":{"source":102,"target":105}},{"data":{"source":109,"target":110}},{"data":{"source":110,"target":103}},{"data":{"source":105,"target":106}},{"data":{"source":106,"target":173}},{"data":{"source":173,"target":183}},{"data":{"source":173,"target":172}},{"data":{"source":183,"target":121}},{"data":{"source":121,"target":136}},{"data":{"source":136,"target":122}},{"data":{"source":172,"target":177}},{"data":{"source":177,"target":175}},{"data":{"source":175,"target":120}}],"nodes":[{"data":{"id":"102","name":"\u00\u67\u6c34\u93","value":102}},{"data":{"id":"109","name":"\05\cb3\uf8","value":109}},{"data":{"id":"110","name":"\ub1\u58def","value":110}},{"data":{"id":"105","name":"\u6c99\9d","value":105}},{"data":{"id":"106","name":"\u0\ub\u\u6","value":106}},{"data":{"id":"173","name":"\u4\u61","value":173}},{"data":{"id":"183","name":"\u5927\u59","value":183}},{"data":{"id":"121","name":"\u59","value":121}},{"data":{"id":"136","name":"\u4e3","value":136}},{"data":{"id":"172","name":"\u73","value":172}},{"data":{"id":"177","name":"\u01\90f\u89\u5c\u8","value":177}},{"data":{"id":"175","name":"\u6\u25\u6c\634\u9","value":175}},{"data":{"id":"103","name":"\u93\u7c\596\27\7","value":103}},{"data":{"id":"122","name":"\u7\u3\u7\uf8","value":122}},{"data":{"id":"120","name":"\u5\u98\u7c","value":120}}]}
}
});
var cy = cytoscape({
container: document.getElementById('cy'),
boxSelectionEnabled: true,
autounselectify: true,
motionBlur: !1,
maxZoom: 2.5,
minZoom: .4,
wheelSensitivity: .1,//滑动滚轮一次缩放大小
textureOnViewport: !1,
style: [{
selector: 'node',
style: {
"text-valign": 'center',
"width": 60,
"height": 60,
"background-color": '#2196F4',
"color": '#fff',
"border-color": '#2196F4',
"border-width": 1,
"text-wrap": "wrap",
"font-size": 10,
"font-family": "microsoft yahei",
"overlay-color": "#fff",
"overlay-opacity": 0,
"background-opacity": 1,
"shape": "ellipse",
"z-index-compare": "manual",
"z-index": 20,
"padding": 3,
"text-max-width": 60,
"text-margin-y": 4,
"label": function (a) {
a = a.data("name");
var b = a.length;
return 5 >= b ? a : 5 <= b && 9 >= b ? a.substring(0, b - 5) + "\n" + a.substring(
b - 5, b) : 9 <= b && 13 >= b ? a.substring(0, 4) + "\n" + a.substring(
4, 9) + "\n" + a.substring(9, 13) : a.substring(0, 4) + "\n" + a.substring(
4, 9) + "\n" + a.substring(9, 12) + ".."
},
}
},
{
selector: 'edge',
style: {
// 添加箭头
"line-style": "solid",
"curve-style": "bezier",
"control-point-step-size": 20,
"target-arrow-shape": "triangle",
"target-arrow-color": '#DCDCDC',
"arrow-scale": .5,
"line-color": '#DCDCDC',
"label": "流向",
"text-opacity": .8,
"font-size": 10,
"background-color": "#333",
"width": 1,
"overlay-color": "#fff",
"overlay-opacity": 0,
"font-family": "microsoft yahei"
}
},
{
selector: ':selected',
style: {
"border-width": 3,
"border-color": '#333',
"background-color": 'black',
"line-color": 'black',
"target-arrow-color": 'black',
"source-arrow-color": 'black'
}
},
{
selector: ".nodeHover", //节点变暗,有悬停效果
style: {
"shape": "ellipse",
"background-opacity": .8
}
},
{
selector: ".nodeActive",
style: {
"border-color": '#4EA2F0',
"border-width": 10,
"border-opacity": .5
}
},
{
selector: ".edgeShow",
style: {
"color": "#999",
"text-opacity": 1,
"font-weight": 400,
"label": "流向",
"font-size": 10,
"arrow-scale": .8,
"width": 1.5,
"source-text-margin-y": 20,
"target-text-margin-y": 20,
},
},
{
selector: ".edgeActive",
style: {
"arrow-scale": .8,
"width": 1.5,
"color": "#330",
"text-opacity": 1,
"font-size": 10,
"text-background-color": "#fff",
"text-background-opacity": .8,
"text-background-padding": 0,
"source-text-margin-y": 20,
"target-text-margin-y": 20,
"z-index-compare": "manual",
"z-index": 1,
"line-color": "#4EA2F0",
"target-arrow-color": "#4EA2F0",
"label": "流向"
}
},
{
selector: ".dull",
style: {
"z-index": 1,
"opacity": .2
}
}
],
elements: elements,
layout: {
name: 'cose',//用哪种方式排列,可选:breadthfirst(广度优先)、cose(缝制,乱交)、preset(预设)、circle(圆形)、grid(矩形)
idealEdgeLength: 60,
nodeOverlap: 20,
refresh: 20,
fit: true,
padding: 30,
randomize: false,
componentSpacing: 20,
nodeRepulsion: 400,
edgeElasticity: 10,
nestingFactor: 5,
animate: true,//出来动画
gravity: 80,
numIter: 1000,
initialTemp: 200,
coolingFactor: 0.95,
minTemp: 1.0
}
})
cy.collection("edge").addClass("edgeShow");
cy.on("mouseover", "node", function (a) {
$('#cy').css('cursor', 'move');
let c = a.target;
c.addClass("nodeHover");
cy.collection("edge").removeClass("edgeActive");
c.neighborhood("edge").addClass("edgeActive");
})
cy.on("mouseout", "node", function (a) {
$('#cy').css('cursor', 'default');
let c = a.target;
c.removeClass("nodeHover");
cy.collection("edge").removeClass("edgeActive");
})
cy.on("click", "node", function (a) {
let c = a.target;
c.removeClass("nodeActive");
cy.collection("edge").removeClass("edgeActive");
})
cy.on("vmousedown", "node", function (a) { //监听鼠标左键按下
let c = a.target;
cy.collection("edge").addClass('dull');
cy.collection("node").addClass('dull');
c.removeClass("dull");
c.neighborhood("edge").removeClass("dull");
c.neighborhood("edge").addClass("edgeActive");
c.neighborhood("edge").connectedNodes().removeClass("dull"); //当前节点的邻域边的边缘节点!
})
cy.on("tapend", "node", function (a) { //监听鼠标左键释放
let c = a.target;
cy.collection("edge").removeClass('dull');
cy.collection("node").removeClass('dull');
c.neighborhood("edge").removeClass("edgeActive");
c.neighborhood("node").removeClass("nodeActive");
})
//线
cy.on("mouseover", "edge", function (a) {
let c = a.target;
cy.collection("edge").removeClass("edgeActive");
c.addClass("edgeActive")
})
cy.on("mouseout", "edge", function (a) {
let c = a.target;
c.removeClass("edgeActive")
})
</script>
</body>
</html>