首页 > 解决方案 > D3 策略检查中的交互式节点图

问题描述

我是一个孤独的开发者,很想在这里对我的 WIP 策略有所了解。D3 带来了所有令人敬畏的 DG 功能,感觉有点像我在重新发明轮子。这是预期的用户流程:

  1. 用户创建一个可拖动的节点(矩形)
  2. 用户创建第二个可拖动节点(矩形)
  3. 用户拖动路径以连接两个节点

当前的痛点包括:

我希望有人告诉我我不是按照 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>

标签: d3.js

解决方案


推荐阅读