【问题标题】:D3 directed graph editor - v5 to v6 migration issueD3 有向图编辑器 - v5 到 v6 迁移问题
【发布时间】:2021-03-11 11:16:28
【问题描述】:

我从 bl.ocks.org 找到了这个 http://bl.ocks.org/rkirsling/5001347 图表,并希望迁移到 D3 v6。

我设法在没有任何语法错误的情况下运行,但如果您单击一个节点,结果似乎是错误的。

我猜的功能:

function mousedown(event) {
        // because :active only works in WebKit?
        svg.classed('active', true);

        if (event.ctrlKey || mousedownNode || mousedownLink) return;

        // insert new node at point
        const point = d3.pointer(this);
        const node = { id: ++lastNodeId, reflexive: false, x: point[0], y: point[1] };
        nodes.push(node);

        restart();
    }

触发不需要的行为。只要我将d3.mouse(this) 更改为d3.pointer(this),源节点就会开始不规律地跳舞。

有什么提示可以在下面修复我当前的代码吗?

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>D3v6 dyn link</title>
    <!-- call external d3.js framework -->
    <script src="https://d3js.org/d3.v6.js"></script>
</head>

<style>
    svg {
        background-color: #FFF;
        cursor: default;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        -o-user-select: none;
        user-select: none;
    }

    svg:not(.active):not(.ctrl) {
        cursor: crosshair;
    }

    path.link {
        fill: none;
        stroke: #000;
        stroke-width: 4px;
        cursor: default;
    }

    svg:not(.active):not(.ctrl) path.link {
        cursor: pointer;
    }

    path.link.selected {
        stroke-dasharray: 10, 2;
    }

    path.link.dragline {
        pointer-events: none;
    }

    path.link.hidden {
        stroke-width: 0;
    }

    circle.node {
        stroke-width: 1.5px;
        cursor: pointer;
    }

    circle.node.reflexive {
        stroke: #000 !important;
        stroke-width: 2.5px;
    }

    text {
        font: 12px sans-serif;
        pointer-events: none;
    }

    text.id {
        text-anchor: middle;
        font-weight: bold;
    }
</style>

<body>

    <svg id="svg"> </svg>

    <script>
        // set up SVG for D3
        const width = window.innerWidth
        const height = window.innerHeight
        const colors = d3.scaleOrdinal(d3.schemeCategory10);

        const svg = d3.select('body')
            .append('svg')
            .on('contextmenu', (event) => { event.preventDefault(); })
            .attr('width', width)
            .attr('height', height);

        // set up initial nodes and links
        //  - nodes are known by 'id', not by index in array.
        //  - reflexive edges are indicated on the node (as a bold black circle).
        //  - links are always source < target; edge directions are set by 'left' and 'right'.
        const nodes = [
            { id: 0, reflexive: false },
            { id: 1, reflexive: true },
            { id: 2, reflexive: false }
        ];
        let lastNodeId = 2;
        const links = [
            { source: nodes[0], target: nodes[1], left: false, right: true },
            { source: nodes[1], target: nodes[2], left: false, right: true }
        ];

        // init D3 force layout
        const force = d3.forceSimulation()
            .force('link', d3.forceLink().id((d) => d.id).distance(150))
            .force('charge', d3.forceManyBody().strength(-500))
            .force('x', d3.forceX(width / 2))
            .force('y', d3.forceY(height / 2))
            .on('tick', tick);

        // init D3 drag support
        const drag = d3.drag()
            // Mac Firefox doesn't distinguish between left/right click when Ctrl is held... 
            .filter((event) => event.button === 0 || event.button === 2)
            .on('start', (event, d) => {
                if (!event.active) force.alphaTarget(0.3).restart();

                d.fx = d.x;
                d.fy = d.y;
            })
            .on('drag', (event, d) => {
                d.fx = event.x;
                d.fy = event.y;
            })
            .on('end', (event, d) => {
                if (!event.active) force.alphaTarget(0);

                d.fx = null;
                d.fy = null;
            });

        // define arrow markers for graph links
        svg.append('svg:defs').append('svg:marker')
            .attr('id', 'end-arrow')
            .attr('viewBox', '0 -5 10 10')
            .attr('refX', 6)
            .attr('markerWidth', 3)
            .attr('markerHeight', 3)
            .attr('orient', 'auto')
            .append('svg:path')
            .attr('d', 'M0,-5L10,0L0,5')
            .attr('fill', '#000');

        svg.append('svg:defs').append('svg:marker')
            .attr('id', 'start-arrow')
            .attr('viewBox', '0 -5 10 10')
            .attr('refX', 4)
            .attr('markerWidth', 3)
            .attr('markerHeight', 3)
            .attr('orient', 'auto')
            .append('svg:path')
            .attr('d', 'M10,-5L0,0L10,5')
            .attr('fill', '#000');

        // line displayed when dragging new nodes
        const dragLine = svg.append('svg:path')
            .attr('class', 'link dragline hidden')
            .attr('d', 'M0,0L0,0');

        // handles to link and node element groups
        let path = svg.append('svg:g').selectAll('path');
        let circle = svg.append('svg:g').selectAll('g');

        // mouse event vars
        let selectedNode = null;
        let selectedLink = null;
        let mousedownLink = null;
        let mousedownNode = null;
        let mouseupNode = null;

        function resetMouseVars() {
            mousedownNode = null;
            mouseupNode = null;
            mousedownLink = null;
        }

        // update force layout (called automatically each iteration)
        function tick() {
            // draw directed edges with proper padding from node centers
            path.attr('d', (d) => {
                const deltaX = d.target.x - d.source.x;
                const deltaY = d.target.y - d.source.y;
                const dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
                const normX = deltaX / dist;
                const normY = deltaY / dist;
                const sourcePadding = d.left ? 17 : 12;
                const targetPadding = d.right ? 17 : 12;
                const sourceX = d.source.x + (sourcePadding * normX);
                const sourceY = d.source.y + (sourcePadding * normY);
                const targetX = d.target.x - (targetPadding * normX);
                const targetY = d.target.y - (targetPadding * normY);

                return `M${sourceX},${sourceY}L${targetX},${targetY}`;
            });

            circle.attr('transform', (d) => `translate(${d.x},${d.y})`);
        }

        // update graph (called when needed)
        function restart() {
            // path (link) group
            path = path.data(links);

            // update existing links
            path.classed('selected', (d) => d === selectedLink)
                .style('marker-start', (d) => d.left ? 'url(#start-arrow)' : '')
                .style('marker-end', (d) => d.right ? 'url(#end-arrow)' : '');

            // remove old links
            path.exit().remove();

            // add new links
            path = path.enter().append('svg:path')
                .attr('class', 'link')
                .classed('selected', (d) => d === selectedLink)
                .style('marker-start', (d) => d.left ? 'url(#start-arrow)' : '')
                .style('marker-end', (d) => d.right ? 'url(#end-arrow)' : '')
                .on('mousedown', (d) => {
                    if (d3.event.ctrlKey) return;

                    // select link
                    mousedownLink = d;
                    selectedLink = (mousedownLink === selectedLink) ? null : mousedownLink;
                    selectedNode = null;
                    restart();
                })
                .merge(path);

            // circle (node) group
            // NB: the function arg is crucial here! nodes are known by id, not by index!
            circle = circle.data(nodes, (d) => d.id);

            // update existing nodes (reflexive & selected visual states)
            circle.selectAll('circle')
                .style('fill', (d) => (d === selectedNode) ? d3.rgb(colors(d.id)).brighter().toString() : colors(d.id))
                .classed('reflexive', (d) => d.reflexive);

            // remove old nodes
            circle.exit().remove();

            // add new nodes
            const g = circle.enter().append('svg:g');

            g.append('svg:circle')
                .attr('class', 'node')
                .attr('r', 12)
                .style('fill', (d) => (d === selectedNode) ? d3.rgb(colors(d.id)).brighter().toString() : colors(d.id))
                .style('stroke', (d) => d3.rgb(colors(d.id)).darker().toString())
                .classed('reflexive', (d) => d.reflexive)
                .on('mouseover', function (d) {
                    if (!mousedownNode || d === mousedownNode) return;
                    // enlarge target node
                    d3.select(this).attr('transform', 'scale(1.1)');
                })
                .on('mouseout', function (d) {
                    if (!mousedownNode || d === mousedownNode) return;
                    // unenlarge target node
                    d3.select(this).attr('transform', '');
                })
                .on('mousedown', (event, d) => {
                    if (event.ctrlKey) return;

                    // select node
                    mousedownNode = d;
                    selectedNode = (mousedownNode === selectedNode) ? null : mousedownNode;
                    selectedLink = null;

                    // reposition drag line
                    dragLine
                        .style('marker-end', 'url(#end-arrow)')
                        .classed('hidden', false)
                        .attr('d', `M${mousedownNode.x},${mousedownNode.y}L${mousedownNode.x},${mousedownNode.y}`);

                    restart();
                })
                .on('mouseup', function (d) {
                    if (!mousedownNode) return;

                    // needed by FF
                    dragLine
                        .classed('hidden', true)
                        .style('marker-end', '');

                    // check for drag-to-self
                    mouseupNode = d;
                    if (mouseupNode === mousedownNode) {
                        resetMouseVars();
                        return;
                    }

                    // unenlarge target node
                    d3.select(this).attr('transform', '');

                    // add link to graph (update if exists)
                    // NB: links are strictly source < target; arrows separately specified by booleans
                    const isRight = mousedownNode.id < mouseupNode.id;
                    const source = isRight ? mousedownNode : mouseupNode;
                    const target = isRight ? mouseupNode : mousedownNode;

                    const link = links.filter((l) => l.source === source && l.target === target)[0];
                    if (link) {
                        link[isRight ? 'right' : 'left'] = true;
                    } else {
                        links.push({ source, target, left: !isRight, right: isRight });
                    }

                    // select new link
                    selectedLink = link;
                    selectedNode = null;
                    restart();
                });

            // show node IDs
            g.append('svg:text')
                .attr('x', 0)
                .attr('y', 4)
                .attr('class', 'id')
                .text((d) => d.id);

            circle = g.merge(circle);

            // set the graph in motion
            force
                .nodes(nodes)
                .force('link').links(links);

            force.alphaTarget(0.3).restart();
        }

        function mousedown(event) {
            // because :active only works in WebKit?
            svg.classed('active', true);

            if (event.ctrlKey || mousedownNode || mousedownLink) return;

            // insert new node at point
            const point = d3.pointer(this);
            const node = { id: ++lastNodeId, reflexive: false, x: point[0], y: point[1] };
            nodes.push(node);

            restart();
        }

        function mousemove() {
            if (!mousedownNode) return;

            // update drag line
            dragLine.attr('d', `M${mousedownNode.x},${mousedownNode.y}L${d3.pointer(this)[0]},${d3.pointer(this)[1]}`);
        }

        function mouseup() {
            if (mousedownNode) {
                // hide drag line
                dragLine
                    .classed('hidden', true)
                    .style('marker-end', '');
            }

            // because :active only works in WebKit?
            svg.classed('active', false);

            // clear mouse event vars
            resetMouseVars();
        }

      
      
        // app starts here
        svg.on('mousedown', mousedown)
            .on('mousemove', mousemove)
            .on('mouseup', mouseup);
        restart();
    </script>
</body>

</html>

【问题讨论】:

    标签: javascript d3.js


    【解决方案1】:

    我认为你在正确的轨道上:

    • 凡是引用d3.event 的地方,都让该函数将event 作为参数,然后在逻辑中引用event(即丢失d3.)。这适用于所有 dragmousedownmouseovermouseoutmouseupkeydownkeyup 处理程序。

    • d3.mouse 变为 d3.pointer,除了...

    • mousemove 函数中使用event.xevent.y 而不是d3.mouse(this)[0]d3.mouse(this)[1]

    在下面的示例中,我使用:

    dragLine.attr('d', `M${mousedownNode.x},${mousedownNode.y}L${event.x},${event.y}`);
    

    诚然,这似乎与 v6 release notes 使用 d3.pointer(event) 而不是 d3.mouse(this) 不一致,但 event 具有您需要的 xy 属性。

    考虑:

    const p = d3.pointer(event);
    dragLine.attr('d', `M${mousedownNode.x},${mousedownNode.y}L${p.x},${p.y}`);
    

    在附加到目标节点之前,这对我来说并不适合我可以操作和查看的 dragLine

    从 v5 到 v6 的示例端口:

    // set up SVG for D3
    const width = 600;
    const height = 180;
    const colors = d3.scaleOrdinal(d3.schemeCategory10);
    
    const svg = d3.select('body')
      .append('svg')
      .on('contextmenu', () => { d3.event.preventDefault(); })
      .attr('width', width)
      .attr('height', height);
    
    // set up initial nodes and links
    //  - nodes are known by 'id', not by index in array.
    //  - reflexive edges are indicated on the node (as a bold black circle).
    //  - links are always source < target; edge directions are set by 'left' and 'right'.
    const nodes = [
      { id: 0, reflexive: false },
      { id: 1, reflexive: true },
      { id: 2, reflexive: false }
    ];
    let lastNodeId = 2;
    const links = [
      { source: nodes[0], target: nodes[1], left: false, right: true },
      { source: nodes[1], target: nodes[2], left: false, right: true }
    ];
    
    // init D3 force layout
    const force = d3.forceSimulation()
      .force('link', d3.forceLink().id((d) => d.id).distance(150))
      .force('charge', d3.forceManyBody().strength(-500))
      .force('x', d3.forceX(width / 2))
      .force('y', d3.forceY(height / 2))
      .on('tick', tick);
    
    // init D3 drag support
    const drag = d3.drag(event)
      // Mac Firefox doesn't distinguish between left/right click when Ctrl is held... 
      .filter(() => event.button === 0 || event.button === 2)
      .on('start', (event, d) => {
        if (!event.active) force.alphaTarget(0.3).restart();
    
        d.fx = d.x;
        d.fy = d.y;
      })
      .on('drag', (event, d) => {
        d.fx = event.x;
        d.fy = event.y;
      })
      .on('end', (event, d) => {
        if (!event.active) force.alphaTarget(0);
    
        d.fx = null;
        d.fy = null;
      });
    
    // define arrow markers for graph links
    svg.append('svg:defs').append('svg:marker')
        .attr('id', 'end-arrow')
        .attr('viewBox', '0 -5 10 10')
        .attr('refX', 6)
        .attr('markerWidth', 3)
        .attr('markerHeight', 3)
        .attr('orient', 'auto')
      .append('svg:path')
        .attr('d', 'M0,-5L10,0L0,5')
        .attr('fill', '#000');
    
    svg.append('svg:defs').append('svg:marker')
        .attr('id', 'start-arrow')
        .attr('viewBox', '0 -5 10 10')
        .attr('refX', 4)
        .attr('markerWidth', 3)
        .attr('markerHeight', 3)
        .attr('orient', 'auto')
      .append('svg:path')
        .attr('d', 'M10,-5L0,0L10,5')
        .attr('fill', '#000');
    
    // line displayed when dragging new nodes
    const dragLine = svg.append('svg:path')
      .attr('class', 'link dragline hidden')
      .attr('d', 'M0,0L0,0');
    
    // handles to link and node element groups
    let path = svg.append('svg:g').selectAll('path');
    let circle = svg.append('svg:g').selectAll('g');
    
    // mouse event vars
    let selectedNode = null;
    let selectedLink = null;
    let mousedownLink = null;
    let mousedownNode = null;
    let mouseupNode = null;
    
    function resetMouseVars() {
      mousedownNode = null;
      mouseupNode = null;
      mousedownLink = null;
    }
    
    // update force layout (called automatically each iteration)
    function tick() {
      // draw directed edges with proper padding from node centers
      path.attr('d', (d) => {
        const deltaX = d.target.x - d.source.x;
        const deltaY = d.target.y - d.source.y;
        const dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
        const normX = deltaX / dist;
        const normY = deltaY / dist;
        const sourcePadding = d.left ? 17 : 12;
        const targetPadding = d.right ? 17 : 12;
        const sourceX = d.source.x + (sourcePadding * normX);
        const sourceY = d.source.y + (sourcePadding * normY);
        const targetX = d.target.x - (targetPadding * normX);
        const targetY = d.target.y - (targetPadding * normY);
    
        return `M${sourceX},${sourceY}L${targetX},${targetY}`;
      });
    
      circle.attr('transform', (d) => `translate(${d.x},${d.y})`);
    }
    
    // update graph (called when needed)
    function restart() {
      // path (link) group
      path = path.data(links);
    
      // update existing links
      path.classed('selected', (d) => d === selectedLink)
        .style('marker-start', (d) => d.left ? 'url(#start-arrow)' : '')
        .style('marker-end', (d) => d.right ? 'url(#end-arrow)' : '');
    
      // remove old links
      path.exit().remove();
    
      // add new links
      path = path.enter().append('svg:path')
        .attr('class', 'link')
        .classed('selected', (d) => d === selectedLink)
        .style('marker-start', (d) => d.left ? 'url(#start-arrow)' : '')
        .style('marker-end', (d) => d.right ? 'url(#end-arrow)' : '')
        .on('mousedown', (event, d) => {
          if (event.ctrlKey) return;
    
          // select link
          mousedownLink = d;
          selectedLink = (mousedownLink === selectedLink) ? null : mousedownLink;
          selectedNode = null;
          restart();
        })
        .merge(path);
    
      // circle (node) group
      // NB: the function arg is crucial here! nodes are known by id, not by index!
      circle = circle.data(nodes, (d) => d.id);
    
      // update existing nodes (reflexive & selected visual states)
      circle.selectAll('circle')
        .style('fill', (d) => (d === selectedNode) ? d3.rgb(colors(d.id)).brighter().toString() : colors(d.id))
        .classed('reflexive', (d) => d.reflexive);
    
      // remove old nodes
      circle.exit().remove();
    
      // add new nodes
      const g = circle.enter().append('svg:g');
    
      g.append('svg:circle')
        .attr('class', 'node')
        .attr('r', 12)
        .style('fill', (d) => (d === selectedNode) ? d3.rgb(colors(d.id)).brighter().toString() : colors(d.id))
        .style('stroke', (d) => d3.rgb(colors(d.id)).darker().toString())
        .classed('reflexive', (d) => d.reflexive)
        .on('mouseover', function (event, d) {
          if (!mousedownNode || d === mousedownNode) return;
          // enlarge target node
          d3.select(this).attr('transform', 'scale(1.1)');
        })
        .on('mouseout', function (event, d) {
          if (!mousedownNode || d === mousedownNode) return;
          // unenlarge target node
          d3.select(this).attr('transform', '');
        })
        .on('mousedown', (event, d) => {
          if (event.ctrlKey) return;
    
          // select node
          mousedownNode = d;
          selectedNode = (mousedownNode === selectedNode) ? null : mousedownNode;
          selectedLink = null;
    
          // reposition drag line
          dragLine
            .style('marker-end', 'url(#end-arrow)')
            .classed('hidden', false)
            .attr('d', `M${mousedownNode.x},${mousedownNode.y}L${mousedownNode.x},${mousedownNode.y}`);
    
          restart();
        })
        .on('mouseup', function (event, d) {
          if (!mousedownNode) return;
    
          // needed by FF
          dragLine
            .classed('hidden', true)
            .style('marker-end', '');
    
          // check for drag-to-self
          mouseupNode = d;
          if (mouseupNode === mousedownNode) {
            resetMouseVars();
            return;
          }
    
          // unenlarge target node
          d3.select(this).attr('transform', '');
    
          // add link to graph (update if exists)
          // NB: links are strictly source < target; arrows separately specified by booleans
          const isRight = mousedownNode.id < mouseupNode.id;
          const source = isRight ? mousedownNode : mouseupNode;
          const target = isRight ? mouseupNode : mousedownNode;
    
          const link = links.filter((l) => l.source === source && l.target === target)[0];
          if (link) {
            link[isRight ? 'right' : 'left'] = true;
          } else {
            links.push({ source, target, left: !isRight, right: isRight });
          }
    
          // select new link
          selectedLink = link;
          selectedNode = null;
          restart();
        });
    
      // show node IDs
      g.append('svg:text')
        .attr('x', 0)
        .attr('y', 4)
        .attr('class', 'id')
        .text((d) => d.id);
    
      circle = g.merge(circle);
    
      // set the graph in motion
      force
        .nodes(nodes)
        .force('link').links(links);
    
      force.alphaTarget(0.3).restart();
    }
    
    function mousedown(event) {
      // because :active only works in WebKit?
      svg.classed('active', true);
    
      if (event.ctrlKey || mousedownNode || mousedownLink) return;
    
      // insert new node at point
      const point = d3.pointer(this);
      const node = { id: ++lastNodeId, reflexive: false, x: point[0], y: point[1] };
      nodes.push(node);
    
      restart();
    }
    
    function mousemove(event) {
      if (!mousedownNode) return;
    
      // update drag line
      dragLine.attr('d', `M${mousedownNode.x},${mousedownNode.y}L${event.x},${event.y}`);
    }
    
    function mouseup(event) {
      if (mousedownNode) {
        // hide drag line
        dragLine
          .classed('hidden', true)
          .style('marker-end', '');
      }
    
      // because :active only works in WebKit?
      svg.classed('active', false);
    
      // clear mouse event vars
      resetMouseVars();
    }
    
    function spliceLinksForNode(node) {
      const toSplice = links.filter((l) => l.source === node || l.target === node);
      for (const l of toSplice) {
        links.splice(links.indexOf(l), 1);
      }
    }
    
    // only respond once per keydown
    let lastKeyDown = -1;
    
    function keydown(event) {
      event.preventDefault();
    
      if (lastKeyDown !== -1) return;
      lastKeyDown = event.keyCode;
    
      // ctrl
      if (event.keyCode === 17) {
        circle.call(drag);
        svg.classed('ctrl', true);
        return;
      }
    
      if (!selectedNode && !selectedLink) return;
    
      switch (event.keyCode) {
        case 8: // backspace
        case 46: // delete
          if (selectedNode) {
            nodes.splice(nodes.indexOf(selectedNode), 1);
            spliceLinksForNode(selectedNode);
          } else if (selectedLink) {
            links.splice(links.indexOf(selectedLink), 1);
          }
          selectedLink = null;
          selectedNode = null;
          restart();
          break;
        case 66: // B
          if (selectedLink) {
            // set link direction to both left and right
            selectedLink.left = true;
            selectedLink.right = true;
          }
          restart();
          break;
        case 76: // L
          if (selectedLink) {
            // set link direction to left only
            selectedLink.left = true;
            selectedLink.right = false;
          }
          restart();
          break;
        case 82: // R
          if (selectedNode) {
            // toggle node reflexivity
            selectedNode.reflexive = !selectedNode.reflexive;
          } else if (selectedLink) {
            // set link direction to right only
            selectedLink.left = false;
            selectedLink.right = true;
          }
          restart();
          break;
      }
    }
    
    function keyup(event) {
      lastKeyDown = -1;
    
      // ctrl
      if (event.keyCode === 17) {
        circle.on('.drag', null);
        svg.classed('ctrl', false);
      }
    }
    
    // app starts here
    svg.on('mousedown', mousedown)
      .on('mousemove', mousemove)
      .on('mouseup', mouseup);
    d3.select(window)
      .on('keydown', keydown)
      .on('keyup', keyup);
    restart();
    svg {
      background-color: #FFF;
      cursor: default;
      -webkit-user-select: none;
      -moz-user-select: none;
      -ms-user-select: none;
      -o-user-select: none;
      user-select: none;
    }
    
    svg:not(.active):not(.ctrl) {
      cursor: crosshair;
    }
    
    path.link {
      fill: none;
      stroke: #000;
      stroke-width: 4px;
      cursor: default;
    }
    
    svg:not(.active):not(.ctrl) path.link {
      cursor: pointer;
    }
    
    path.link.selected {
      stroke-dasharray: 10,2;
    }
    
    path.link.dragline {
      pointer-events: none;
    }
    
    path.link.hidden {
      stroke-width: 0;
    }
    
    circle.node {
      stroke-width: 1.5px;
      cursor: pointer;
    }
    
    circle.node.reflexive {
      stroke: #000 !important;
      stroke-width: 2.5px;
    }
    
    text {
      font: 12px sans-serif;
      pointer-events: none;
    }
    
    text.id {
      text-anchor: middle;
      font-weight: bold;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.5.0/d3.min.js"></script>
    
    <!-- 
    Copyright (c) 2013 Ross Kirsling
    
    Permission is hereby granted, free of charge, to any person obtaining
    a copy of this software and associated documentation files (the
    "Software"), to deal in the Software without restriction, including
    without limitation the rights to use, copy, modify, merge, publish,
    distribute, sublicense, and/or sell copies of the Software, and to
    permit persons to whom the Software is furnished to do so, subject to
    the following conditions:
    
    The above copyright notice and this permission notice shall be
    included in all copies or substantial portions of the Software.
    
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
    LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
    -->

    【讨论】:

    • 感谢您抽出宝贵时间 Robin,只有一个错误。新节点不是在鼠标位置创建的。它们一次又一次地出现在几乎相同的位置,这也解释了第二个问题,即拖动线也不完全在鼠标位置。
    • 需要将point.x和point.y改为point[0]和point[1]。对于 mousedown 和 mousemove。现在,新节点已在正确的位置创建,并且 dragLine 已修复。干杯罗宾,你帮了我很多。
    猜你喜欢
    • 2022-10-11
    • 1970-01-01
    • 2017-05-26
    • 1970-01-01
    • 2019-12-12
    • 2022-08-16
    • 2022-08-02
    • 2016-12-27
    • 2022-01-17
    相关资源
    最近更新 更多