d3.js - D3 策略检查中的交互式节点图
问题描述
我是一个孤独的开发者,很想在这里对我的 WIP 策略有所了解。D3 带来了所有令人敬畏的 DG 功能,感觉有点像我在重新发明轮子。这是预期的用户流程:
- 用户创建一个可拖动的节点(矩形)
- 用户创建第二个可拖动节点(矩形)
- 用户拖动路径以连接两个节点
当前的痛点包括:
- 拖动已可拖动节点的输出端口以创建新路径(当前使用 just
mousedown
) - 如何点击测试输入端口(目前
mousedown
在输入端口上添加事件但感觉马虎)。我如何解释没有击中任何东西?
我希望有人告诉我我不是按照 D3 的方式来做的……或者这是规定的方式。
工作jsfiddle
{% load static %}
<link rel="stylesheet" href="{% static 'cost_calculator/styles/styles.css' %}">
<script src={% static 'cost_calculator/js/d3.v5.8.2.min.js' %}></script>
<script src={% static 'cost_calculator/js/svg_helpers.js' %}></script>
<script src={% static 'cost_calculator/js/uuid_v1.min.js' %}></script>
<h3>{{ words }}</h3>
<div class="properties-container">
<button type="button" name="button" onClick="makeNode();">Create Node</button>
<input type="hidden" id="properties-id" value="">
<!-- <input placeholder="Value..." type="text" name="value" value="" id="properties-value" onchange="PropertiesPanel.setNodeValue()" /> -->
<!-- <input placeholder="Label..." type="text" name="label" value="" id="properties-label" onchange="PropertiesPanel.setNodeLabel()" /> -->
</div>
<div id="canvas" style="border: 1px solid #D9D9D9; width: 100%; height: 600px; margin-top: 6px"></div>
<script>
var svg = d3.select("div#canvas")
.append("svg")
.attr("width", '100%')
.attr("height", '100%');
let NodeData = [];
function makeNode() {
NodeData.push({
'x': 20,
'y': 120,
'id': uuidv1(),
'fill': '#FFFFFF',
'value': '100',
'label': 'myLabel'
});
render();
}
function render() {
var nodes = svg.selectAll('g').data(NodeData)
.join('g')
.attr('node-id', function (d) { return d.id; })
.attr('transform', function (d) { return `translate(${d.x}, ${d.y})` })
.call(d3.drag()
.on('start', dragStart)
.on('drag', dragActive)
.on('end', dragEnd)
);
nodes.selectAll('[elementType=value-cont]')
.data(function (d) { return [d]; })
.join('rect')
.attr('elementType', 'value-cont')
.attr('x', 0)
.attr('y', 0)
.attr('width', 80)
.attr('height', 20)
.attr('stroke', '#666666')
.attr('stroke-width', '1')
.attr('fill', function (d) { return d.fill; });
nodes.selectAll('[elementType=value-text]')
.data(function (d) { return [d]; })
.join('text')
.attr('elementType', 'value-text')
.attr('x', 76)
.attr('y', 16)
.attr("font-family", "sans-serif")
.attr("font-size", "14px")
.attr("text-anchor", "end")
.text(function (d) { return d.value; })
.on('dblclick', function (d) {
var newVal = prompt("Enter a new value:", d.value);
if (newVal == null || newVal == "") {
console.log('canceled');
} else {
d.value = newVal;
render();
}
});
nodes.selectAll('[elementType=label-text]')
.data(function (d) { return [d]; })
.join('text')
.attr('elementType', 'label-text')
.attr('x', 85)
.attr('y', 16)
.attr("font-family", "sans-serif")
.attr("font-size", "14px")
.text(function (d) { return d.label; })
.on('dblclick', function (d) {
var newLabel = prompt("Enter a new label:", d.label);
if (newLabel == null || newLabel == "") {
console.log('canceled');
} else {
d.label = newLabel;
render();
}
});
nodes.selectAll('[elementType=outport-rect]')
.data(function (d) { return [d]; })
.join('rect')
.attr('elementType', 'outport-rect')
.attr('x', 30)
.attr('y', 20)
.attr('width', 20)
.attr('height', 10)
.attr('fill', '#999999')
.style('opacity', 0.15)
.on('mousedown', newConnectorStart);
nodes.selectAll('[elementType=inport-rect]')
.data(function (d) { return [d]; })
.join('rect')
.attr('elementType', 'inport-rect')
.attr('x', 30)
.attr('y', -10)
.attr('width', 20)
.attr('height', 10)
.attr('fill', '#999999')
.style('opacity', 0.15)
.on('mousedown', function(d,i) {
console.log(tempConnector);
if (tempConnector) {
var m = d3.mouse(this);
tempConnectorData.target = {x: m[0], y: m[1]};
tempConnector.attr('d', lineFunction(tempConnectorData));
svg.on("mousemove", null).on("mouseup", null);
}
});
}
function dragStart(d) {}
function dragActive(d) {
// console.log(d);
d.x = d3.event.x;
d.y = d3.event.y;
render();
}
function dragEnd(d) {}
let tempConnectorData;
let tempConnector;
var lineFunction = d3.linkVertical()
.x(function (d) { return d.x; })
.y(function (d) { return d.y; });
function newConnectorStart() {
d3.event.preventDefault();
var parent = d3.select(this.parentNode);
var offset = getTransformation(parent.attr('transform'));
var rect = parent.select('[elementType=value-cont]');
var transform = {
x: offset.translateX + rect.attr('width') / 2,
y: offset.translateY + rect.attr('height') / 2,
}
tempConnectorData = {
source: {x: transform.x, y: transform.y},
target: {x: transform.x, y: transform.y},
};
tempConnector = svg.append("path")
.attr("fill", "none")
.attr("stroke", "blue")
.style("stroke-width", "2px")
.attr('d', lineFunction(tempConnectorData))
.lower();
function newConnectorActive() {
var m = d3.mouse(this);
tempConnectorData.target = {x: m[0], y: m[1]};
tempConnector.attr('d', lineFunction(tempConnectorData));
}
function newConnectorEnd() {
console.log('end');
svg.on("mousemove", null).on("mouseup", null);
}
svg.on("mousemove", newConnectorActive)
.on("mouseup", newConnectorEnd);
}
render();
</script>
解决方案
推荐阅读
- angular - 逐行读取角度文本文件
- android - Android - 使用 MPAndroidChart 在两行之间填充不同的颜色
- mysql - 以一行/水平显示 MySQL 查询的输出
- r - ggplot2 position_stack(vjust) 具有绝对单位而不是比例
- .net - 无法将 Reporting Services 项目升级到 Visual Studio 2019
- node.js - 在 gremlin_node 中更新属性值而不添加附加值
- java - 获取元素直到某个字符并使用 RxJava 将它们分组
- vmware-clarity - 移动响应式顶部菜单
- windows - 使用 Selenium 访问代理服务器后面的网站,使用 Google Chrome 无头,在使用 Fiddler 时有效
- c# - System.TypeInitializationException:“'CRM.Models.SiteSettings' 的类型初始化程序引发了异常。”