如何在 D3/dagre-D3/javascript 中创建决策树/流程图?
How to create a decision tree / flow chart in D3/dagre-D3/javascript?
所以我想创建一个问题流程图,如下所示:
不确定从哪里开始最好...这是有向图吗?
其中一些最终真的被间隔开并且看起来不适合 'flows' 像这样:https://observablehq.com/@d3/force-directed-graph
我见过的最好的例子是非 D3 库 (yworks),但它似乎要花费 15,000 美元:
这是我见过的唯一相关的 Whosebug,它只引用了 yworks:Can I create a flow chart (no tree chart) using D3.js
也许这个 dagre-d3 示例也是如此:
http://jsfiddle.net/armyofda12mnkeys/9L50of2c/2/
var g = new dagreD3.graphlib.Graph().setGraph({});
我想添加一些很酷的可选内容:
*我还希望能够控制 Circles 上的 css,例如根据该节点的数据,某些情况下某些情况下会变绿或变红。
*每个边缘箭头我还想添加 onHovers 事件,因此会出现一个工具提示来显示实际规则,例如 'if(Question1 == A || B)'
*使 nodes/edges 可拖动或 'bouncy'(如果拖动,它们会弹出回到原始位置)。听起来很花哨,但有时如果规则太拥挤(因为智能自动布局),用户可能会使用此功能,他们想拖动东西以查看箭头指向何处。
我想我是用 dagre-d3 搞定的。
这是我最初的 jsfiddle:
http://jsfiddle.net/armyofda12mnkeys/4gv90qhx/2/
这也是同样的例子,边缘也有弹出窗口(虽然我不像节点弹出窗口那样喜欢这个实现)
http://jsfiddle.net/armyofda12mnkeys/4gv90qhx/37/
这里是一个完整的例子,说明我如何在我的项目中使用糖尿病问卷(我将代码升级到最新的 d3.v5+dagre,并使节点+边缘可拖动......很多初始JSON 解析代码以进入我实际上可以循环的格式,抱歉):
https://jsfiddle.net/armyofda12mnkeys/1burht5j/44/
注意:如果我使用的 'cors-anywhere' 网站已关闭,最后一个 link 可能无法工作。那就下载试试吧
// Create a new directed graph
var g = new dagreD3.graphlib.Graph().setGraph({});
var nodes = [
{'qs_code':"QS1", 'hovertext': 'This is QS1', 'proto_logic_type': 'none' },
{'qs_code':"QS2", 'hovertext': 'This is QS2', 'proto_logic_type': 'disqualify'},
{'qs_code':"QS3", 'hovertext': 'This is QS3', 'proto_logic_type': 'qualify'},
{'qs_code':"QS4", 'hovertext': 'This is QS4', 'proto_logic_type': 'both'},
{'qs_code':"QS5", 'hovertext': 'This is QS5', 'proto_logic_type': 'none'},
{'qs_code':"QS6", 'hovertext': 'This is QS6', 'proto_logic_type': 'none'}
];
// Automatically label each of the nodes
nodes.forEach(function(node) {
g.setNode(node.qs_code, { label: node.qs_code, shape: "circle", class: [node.proto_logic_type], hovertext: node.hovertext }); //style: 'fill: red'
});
// Set up the edges
g.setEdge("QS1", "QS2", { label: "<u onmouseover='(function(){ return $(\"#tooltip_template\").css(\"visibility\", \"visible\"); })()' onmouseout='(function(){ return $(\"#tooltip_template\").css(\"visibility\", \"hidden\"); })()' onmousemove='(function(){ $(\"#tooltip_template\").html(\"AAA&gt;BBB\").css(\"top\", (event.pageY-10)+\"px\").css(\"left\",(event.pageX+10)+\"px\"); })()'>Rule1</u>", hovertext:"A>B", labelType: "html" });
g.setEdge("QS1", "QS3", { label: "<u onmouseover='(function(){ return $(\"#tooltip_template\").css(\"visibility\", \"visible\"); })()' onmouseout='(function(){ return $(\"#tooltip_template\").css(\"visibility\", \"hidden\"); })()' onmousemove='(function(){ $(\"#tooltip_template\").html(\"AAA&lt;BBB\").css(\"top\", (event.pageY-10)+\"px\").css(\"left\",(event.pageX+10)+\"px\"); })()'>Rule2</u>", hovertext:"A<B", labelType: "html" });
g.setEdge("QS1", "QS4", { label: "<u onmouseover='(function(){ return $(\"#tooltip_template\").css(\"visibility\", \"visible\"); })()' onmouseout='(function(){ return $(\"#tooltip_template\").css(\"visibility\", \"hidden\"); })()' onmousemove='(function(){ $(\"#tooltip_template\").html(\"AAA==BBB\").css(\"top\", (event.pageY-10)+\"px\").css(\"left\",(event.pageX+10)+\"px\"); })()'>Rule3</u>", hovertext:"A==B", labelType: "html" });
g.setEdge("QS2", "QS5", { label: "Rule1", arrowhead: "vee", hovertext:"(A+B)>1" });
g.setEdge("QS3", "QS5", { label: "Rule1", hovertext:"(A-B)<2" });
g.setEdge("QS3", "QS6", { label: "Rule2", hovertext:"(A*B)>=3" });
g.setEdge("QS4", "QS6", { label: "Rule2", arrowhead: "vee", hovertext:"(A>10)||(B<20)" });
var svg = d3.select("svg"),
inner = svg.select("g");
// Set the rankdir
g.graph().rankdir = 'TB';//'LR';
g.graph().nodesep = 50;
// Set up zoom support
var zoom = d3.behavior.zoom().on("zoom", function() {
inner.attr("transform", "translate(" + d3.event.translate + ")" +
"scale(" + d3.event.scale + ")");
});
svg.call(zoom);
// Create the renderer
var render = new dagreD3.render();
// Run the renderer. This is what draws the final graph.
render(inner, g);
var tooltip = d3.select("body")
.append("div")
.attr('id', 'tooltip_template')
.style("position", "absolute")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "2px")
.style("border-radius", "5px")
.style("padding", "5px")
.style("z-index", "10")
.style("visibility", "hidden")
.text("Simple Tooltip...");
inner.selectAll('g.node')
.attr("data-hovertext", function(v) {
return g.node(v).hovertext
})
.on("mouseover", function(){return tooltip.style("visibility", "visible");})
.on("mousemove", function(){
tooltip.text( this.dataset.hovertext)
.style("top", (event.pageY-10)+"px")
.style("left",(event.pageX+10)+"px");
})
.on("mouseout", function(){return tooltip.style("visibility", "hidden");});
inner.selectAll('g.edgePath')
//inner.selectAll('path')
.append('title').text('This is a line.');
// Center the graph
var initialScale = 0.75;
zoom
.translate([(svg.attr("width") - g.graph().width * initialScale) / 2, 20])
.scale(initialScale)
.event(svg);
svg.attr('height', g.graph().height * initialScale + 40);
所以我想创建一个问题流程图,如下所示:
我见过的最好的例子是非 D3 库 (yworks),但它似乎要花费 15,000 美元:
var g = new dagreD3.graphlib.Graph().setGraph({});
我想添加一些很酷的可选内容:
*我还希望能够控制 Circles 上的 css,例如根据该节点的数据,某些情况下某些情况下会变绿或变红。
*每个边缘箭头我还想添加 onHovers 事件,因此会出现一个工具提示来显示实际规则,例如 'if(Question1 == A || B)'
*使 nodes/edges 可拖动或 'bouncy'(如果拖动,它们会弹出回到原始位置)。听起来很花哨,但有时如果规则太拥挤(因为智能自动布局),用户可能会使用此功能,他们想拖动东西以查看箭头指向何处。
我想我是用 dagre-d3 搞定的。 这是我最初的 jsfiddle: http://jsfiddle.net/armyofda12mnkeys/4gv90qhx/2/
这也是同样的例子,边缘也有弹出窗口(虽然我不像节点弹出窗口那样喜欢这个实现) http://jsfiddle.net/armyofda12mnkeys/4gv90qhx/37/
这里是一个完整的例子,说明我如何在我的项目中使用糖尿病问卷(我将代码升级到最新的 d3.v5+dagre,并使节点+边缘可拖动......很多初始JSON 解析代码以进入我实际上可以循环的格式,抱歉): https://jsfiddle.net/armyofda12mnkeys/1burht5j/44/ 注意:如果我使用的 'cors-anywhere' 网站已关闭,最后一个 link 可能无法工作。那就下载试试吧
// Create a new directed graph
var g = new dagreD3.graphlib.Graph().setGraph({});
var nodes = [
{'qs_code':"QS1", 'hovertext': 'This is QS1', 'proto_logic_type': 'none' },
{'qs_code':"QS2", 'hovertext': 'This is QS2', 'proto_logic_type': 'disqualify'},
{'qs_code':"QS3", 'hovertext': 'This is QS3', 'proto_logic_type': 'qualify'},
{'qs_code':"QS4", 'hovertext': 'This is QS4', 'proto_logic_type': 'both'},
{'qs_code':"QS5", 'hovertext': 'This is QS5', 'proto_logic_type': 'none'},
{'qs_code':"QS6", 'hovertext': 'This is QS6', 'proto_logic_type': 'none'}
];
// Automatically label each of the nodes
nodes.forEach(function(node) {
g.setNode(node.qs_code, { label: node.qs_code, shape: "circle", class: [node.proto_logic_type], hovertext: node.hovertext }); //style: 'fill: red'
});
// Set up the edges
g.setEdge("QS1", "QS2", { label: "<u onmouseover='(function(){ return $(\"#tooltip_template\").css(\"visibility\", \"visible\"); })()' onmouseout='(function(){ return $(\"#tooltip_template\").css(\"visibility\", \"hidden\"); })()' onmousemove='(function(){ $(\"#tooltip_template\").html(\"AAA&gt;BBB\").css(\"top\", (event.pageY-10)+\"px\").css(\"left\",(event.pageX+10)+\"px\"); })()'>Rule1</u>", hovertext:"A>B", labelType: "html" });
g.setEdge("QS1", "QS3", { label: "<u onmouseover='(function(){ return $(\"#tooltip_template\").css(\"visibility\", \"visible\"); })()' onmouseout='(function(){ return $(\"#tooltip_template\").css(\"visibility\", \"hidden\"); })()' onmousemove='(function(){ $(\"#tooltip_template\").html(\"AAA&lt;BBB\").css(\"top\", (event.pageY-10)+\"px\").css(\"left\",(event.pageX+10)+\"px\"); })()'>Rule2</u>", hovertext:"A<B", labelType: "html" });
g.setEdge("QS1", "QS4", { label: "<u onmouseover='(function(){ return $(\"#tooltip_template\").css(\"visibility\", \"visible\"); })()' onmouseout='(function(){ return $(\"#tooltip_template\").css(\"visibility\", \"hidden\"); })()' onmousemove='(function(){ $(\"#tooltip_template\").html(\"AAA==BBB\").css(\"top\", (event.pageY-10)+\"px\").css(\"left\",(event.pageX+10)+\"px\"); })()'>Rule3</u>", hovertext:"A==B", labelType: "html" });
g.setEdge("QS2", "QS5", { label: "Rule1", arrowhead: "vee", hovertext:"(A+B)>1" });
g.setEdge("QS3", "QS5", { label: "Rule1", hovertext:"(A-B)<2" });
g.setEdge("QS3", "QS6", { label: "Rule2", hovertext:"(A*B)>=3" });
g.setEdge("QS4", "QS6", { label: "Rule2", arrowhead: "vee", hovertext:"(A>10)||(B<20)" });
var svg = d3.select("svg"),
inner = svg.select("g");
// Set the rankdir
g.graph().rankdir = 'TB';//'LR';
g.graph().nodesep = 50;
// Set up zoom support
var zoom = d3.behavior.zoom().on("zoom", function() {
inner.attr("transform", "translate(" + d3.event.translate + ")" +
"scale(" + d3.event.scale + ")");
});
svg.call(zoom);
// Create the renderer
var render = new dagreD3.render();
// Run the renderer. This is what draws the final graph.
render(inner, g);
var tooltip = d3.select("body")
.append("div")
.attr('id', 'tooltip_template')
.style("position", "absolute")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "2px")
.style("border-radius", "5px")
.style("padding", "5px")
.style("z-index", "10")
.style("visibility", "hidden")
.text("Simple Tooltip...");
inner.selectAll('g.node')
.attr("data-hovertext", function(v) {
return g.node(v).hovertext
})
.on("mouseover", function(){return tooltip.style("visibility", "visible");})
.on("mousemove", function(){
tooltip.text( this.dataset.hovertext)
.style("top", (event.pageY-10)+"px")
.style("left",(event.pageX+10)+"px");
})
.on("mouseout", function(){return tooltip.style("visibility", "hidden");});
inner.selectAll('g.edgePath')
//inner.selectAll('path')
.append('title').text('This is a line.');
// Center the graph
var initialScale = 0.75;
zoom
.translate([(svg.attr("width") - g.graph().width * initialScale) / 2, 20])
.scale(initialScale)
.event(svg);
svg.attr('height', g.graph().height * initialScale + 40);