因为公司的某个系统比较古老,里面的 job 的管理几乎都是直接通过操作数据库来实现的,对于一般的操作还可以忍受,但是每次想知道 job 之间的依赖关系的时候,就会相当难受,于是便脑袋很抽地一定要写一个查询系统能快速查询 job 之间的依赖关系。
分析
在项目开始之前,大概稍微估算了一下难点,大概可以分为以下两点:
如何在前端显示该依赖图
如何提高查询效率
此篇文章只看第一个问题,由于是本人是前端渣,于是只能去网上搜相应图形显示插件。
前端插件选择
找了很久,终于在 echarts 上找到了一个稍微适合自己的图形插件了,
http://echarts.baidu.com/examples/editor.html?c=graph-simple
不过,这个插件只是一个简单图的插件,也就是说这个插件并不会帮你进行自动定位相应的点,我们需要自己确定各个点的坐标。
解决方案
通过观察发现,该插件上的坐标只需要确定相对坐标就行了,它会根据相对位置进行自动定位,于是感觉瞬间轻松了许多。
仔细思考,发现 job 之间的依赖关系是一个有向无环图,于是开始构思。
要求:
将点按层次显示,保证前面层的点只依赖后面层的点 (同一层也禁止存在依赖关系)。
使得图尽可能整洁,尽量减少线段之间的交叉
解决方案:
通过拓扑排序来实现点的层次选择,每次将所有入度为 0 的点分为同一层即可
通过与前面层的点之间的关系设置相应的权重,同层之间根据权重来排序。我记得好像可以通过动态规划得到最优解的,不过实现起来太麻烦了,就算了
代码如下:
import java.util.*;
* 作用: 绘制有向无环图(DAG图)
*
* 说明:
* 配合Echarts的simple graph使用,地址见http://echarts.baidu.com/examples/editor.html?c=graph-simple
* 用来进行点的坐标定位
*
* 使用条件: 图必须是一个有向无环图(DAG图)
*
* 调用方式: return new DagGraphUtil(points, links).drawDagGraph()
*
* 参数:
* points的格式为 ['点1','点2','点3','点4']
* links的格式为 [{"source":"点1","target":"点2"},{"source":"点2","target":"点3"},{"source":"点3","target":"点4"}]
*
* 返回结果:
* {
* "points": [{"name": "点1","x":100,"y":100},{"name":"点2","x":200,"y":200}, ...],
* "links": [{"source":"点1","target":"点2"},{"source":"点2","target":"点3"}, ...]
* }
*
*
* 主要算法: 拓扑排序
*/
public class {
private final int MAX_LENGTH_X = 900;
private final int MAX_LENGTH_Y = 1000;
private int maxX;
private Map<String, Integer> toNum;
private Map<String, Integer> xPosition;
private Map<String, Integer> yPosition;
private Map<String, Integer> pointWeight;
private List<Integer> ySize;
private List<String> points;
private List<Map<String, String>> links;
public (List<String> points, List<Map<String, String>> links) {
this.points = points;
this.links = links;
toNum = new HashMap<>(points.size());
xPosition = new HashMap<>(points.size());
yPosition = new HashMap<>(points.size());
pointWeight = new HashMap<>(points.size());
maxX = 0;
ySize = new ArrayList<>();
for (String point : points) {
toNum.put(point, 0);
pointWeight.put(point, 0);
}
for (Map<String, String> link : links) {
String toPoint = link.get("target");
addMapValue(toNum, toPoint, 1);
}
}
public Map<String, Object> drawDagGraph() {
Map<String, Object> result = new HashMap<>(3);
dfs(0);
List<Map<String, Object>> pList = new ArrayList<>(points.size());
for (String point : points) {
int xUnitLength = MAX_LENGTH_X / maxX;
int x = xPosition.get(point) * xUnitLength + xUnitLength / 2;
int yUnitLength = MAX_LENGTH_Y / ySize.get(xPosition.get(point));
int y = yPosition.get(point) * yUnitLength + yUnitLength / 2;
Map<String, Object> map = new HashMap<>(3);
map.put("name", point);
map.put("x", x);
map.put("y", y);
pList.add(map);
}
result.put("points", pList);
result.put("links", links);
return result;
}
private void dfs(int depth) {
List<String> currentPoints = new ArrayList<>();
for (String key : toNum.keySet()) {
if (toNum.get(key) == 0) {
currentPoints.add(key);
addMapValue(toNum, key, -1);
}
}
if (currentPoints.isEmpty()) {
return;
}
maxX = depth + 1;
ySize.add(currentPoints.size());
Collections.sort(currentPoints, new Comparator<String>() {
public int compare(String o1, String o2) {
return pointWeight.get(o1) - pointWeight.get(o2);
}
});
for (int i = 0, len = currentPoints.size(); i < len; i++) {
String point = currentPoints.get(i);
xPosition.put(point, depth);
yPosition.put(point, i);
for (Map<String, String> link : links) {
if (link.get("source").equals(point)) {
maxMapValue(pointWeight, link.get("target"), i + 1);
addMapValue(toNum, link.get("target"), -1);
}
}
}
dfs(depth + 1);
}
private void addMapValue(Map<String, Integer> map, String key, Integer value) {
if (map.containsKey(key)) {
map.put(key, map.get(key) + value);
} else {
map.put(key, value);
}
}
private void maxMapValue(Map<String, Integer> map, String key, Integer value) {
if (!map.containsKey(key) || map.get(key) == 0 || map.get(key) < value) {
map.put(key, value);
}
}
}