D3, JSON,图形,强制布局,数据更新,未完成重绘,拖动

D3, JSON, graph, force layout, data update, not complete redraw, drag

本文关键字:未完成 更新 拖动 数据 布局 JSON 图形 D3      更新时间:2023-09-26

首先抱歉我的问题,因为我认为这是因为我不太了解D3库,特别是选择的工作。

我想这样做:

  1. 显示将出版物与发布该出版物的人联系起来的图表。
  2. 当数据发生变化时更新图表(在本例中,当用户点击上面"点击更新"的段落时)。

这里我不想完全重画图形;我希望现有的节点和链接留在原地,新的节点和链接飞到现场。这就是为什么我把force layout的实例放在drawGraph函数之外(作为全局变量)。

任务#1顺利完成。

问题出在任务#2;我可以让新节点进入场景…,但由于某种原因,我无法拖动现有的节点。我只能拖动新的节点(Eduardo)。

我在Chrome中调试了它,看到这个"var a"有9个元素(点击后)。所以,我假设函数"力"。布局"应该是由D3为所有这9个元素调用。如果我没理解错的话,我应该能够拖动所有的9个圆。

但事实并非如此,所以我的代码有问题。谁能指出我哪里说错了吗?

下面是代码。之后是JSON(两个独立的JSON, news。Json和news3.json).

附加问题:

我不太明白为什么这个块(关键函数)被执行17次(8 + 9)在drawGraph函数的第二次调用(当json更新到news3.json)?我的期望是9倍。

var lineSelections = svg.selectAll('line')
  .data(dataset.edges, function(d){
    console.log(d);
    return d.source.index + '.' + d.target.index;
  });

提前感谢!

Raka

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>D3: Force layout</title>
        <script type="text/javascript" src="../d3/d3.v3.js"></script>
        <style type="text/css">
            #tooltip {
                position: absolute;
                width: 200px;
                height: auto;
                padding: 10px;
                background-color: white;
                -webkit-border-radius: 10px;
                -moz-border-radius: 10px;
                border-radius: 10px;
                -webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
                -moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
                box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
                pointer-events: none;
            }
            #tooltip.hidden {
                display: none;
            }
            #tooltip p {
                margin: 0;
                font-family: sans-serif;
                font-size: 16px;
                line-height: 20px;
            }
        </style>    
    </head>
    <body>
        <div id="tooltip" class="hidden">
            <p><span id="type"></span></p>
            <p><span id="name"></span></p>
        </div>  
        <p id="refresh">Click on this text to update the chart with new data values (once).</p>
        <script type="text/javascript">
            //Width and height
            var w = 500;
            var h = 300;
            //http://stackoverflow.com/questions/16455194/how-to-store-a-json-object-loaded-from-a-file
            var force = d3.layout.force()
                .size([w, h])
                .linkDistance([20])
                .charge([-50]);         
            var svg = d3.select('body')
                .append('svg')
                .attr('width', w)
                .attr('height', h);         
            drawGraph = function(dataset) {
                force
                    .nodes(dataset.nodes)
                    .links(dataset.edges)
                    .start();
                var lineSelections = svg.selectAll('line')
                    .data(dataset.edges, function(d){
                        console.log(d);
                        return d.source.index + '.' + d.target.index;
                    });
                //Create edges as lines
                var edges = lineSelections
                    .enter()
                    .append('line')
                    .style('stroke', function(d) {                  
                        if (d.target.type === 'publication') {
                            return 'red';
                        } else {
                            return 'blue';
                        }
                    })
                    .style('stroke-width', function(d) {
                        return d.weight;
                    });     
                var groups = svg.selectAll('g')
                    .data(dataset.nodes, function(d) {
                        console.log(d.name);
                        return d.name;
                    })
                    .enter()
                    .append('g');
                var nodes = groups
                    .append('circle')
                    .attr('r', function(d) {
                        if (d.type === 'publication') {
                            var radius = d.weight / 6;
                            if (radius < 5) {
                                radius = 5;
                            } 
                            return radius;
                        } else {
                            return d.weight * 3;
                        }
                    })
                    .style('fill', function(d, i) {
                        if (d.type === 'publication') {
                            return 'black';
                        } else {
                            return 'green';
                        }
                    });
                var a = svg.selectAll('g circle');
                a.call(force.drag);
                //Create labels
                var text = groups
                   .append('text')
                   .text(function(d) {
                        if (d.type === 'publication') {
                            return d.name;
                        } else {
                            return '';
                        }
                   })
                   .attr('x', function(d, i) {
                        d.x;
                   })
                   .attr('y', function(d) {
                        d.x;
                   })
                   .attr('font-family', 'sans-serif')
                   .attr('font-size', '24px')
                   .attr('fill', 'orange');             
                groups
                    .on("mouseover", function(d) {
                        //Get this bar's x/y values, then augment for the tooltip
                        //var hmm = d3.select(this).select('circle').attr('cx');
                        var xPosition = d3.select(this).select('circle').attr('cx');
                        var yPosition = d3.select(this).select('circle').attr('cy');
                        //Update the tooltip position and value
                        d3.select("#tooltip")
                            .style("left", xPosition + "px")
                            .style("top", yPosition + "px")
                            .select("#type")
                            .text(d.type);
                        d3.select("#tooltip")
                            .style("left", xPosition + "px")
                            .style("top", yPosition + "px")
                            .select("#name")
                            .text(d.name);                          
                        //Show the tooltip
                        //d3.select("#tooltip").classed("hidden", false);
                    })
                    .on("mouseout", function() {
                        //Hide the tooltip
                        d3.select("#tooltip").classed("hidden", true);
                    })

                //Every time the simulation "ticks", this will be called
                force.on('tick', function() {
                    edges.attr('x1', function(d) { return d.source.x; })
                         .attr('y1', function(d) { return d.source.y; })
                         .attr('x2', function(d) { return d.target.x; })
                         .attr('y2', function(d) { return d.target.y; });
                    nodes.attr('cx', function(d) { return d.x; })
                         .attr('cy', function(d) { return d.y; });

                    //Update all labels
                    svg.selectAll('text')
                       .data(dataset.nodes)
                       .attr('x', function(d, i) {
                            return d.x;
                       })
                       .attr('y', function(d) {
                            return d.y;
                       });                   
                });
            }
            d3.json('news.json', function(dataset) {
                drawGraph(dataset);
            });
            d3.select("p")
                .on("click", function() {
            });
            d3.select("#refresh")
                .on("click", function() {
                d3.json('news3.json', function(dataset) {
                    console.log(dataset);
                    drawGraph(dataset);
                });
            });
        </script>
    </body>
</html>

news.json

{
    "nodes": [
        { "name": "El Universal", "type": "publication", "weight": 10 },
        { "name": "Milenio", "type": "publication", "weight": 4},
        { "name": "Proceso", "type": "publication", "weight": 4},
        { "name": "Paco", "type": "person", "weight": 12},
        { "name": "Juan", "type": "person", "weight": 5},
        { "name": "Alberto", "type": "person", "weight": 5 },
        { "name": "Xochitl", "type": "person", "weight": 3 },
        { "name": "Reforma", "type": "publication", "weight": 2}
    ],
    "edges": [
        { "source": 3, "target": 0, "weight": 9},
        { "source": 3, "target": 1, "weight": 3},
        { "source": 4, "target": 2, "weight": 4},
        { "source": 4, "target": 0, "weight": 1},
        { "source": 5, "target": 3, "weight": 5},
        { "source": 6, "target": 1, "weight": 1},
        { "source": 6, "target": 7, "weight": 2},
        { "source": 6, "target": 1, "weight": 1},
        { "source": 3, "target": 5, "weight": 4},
        { "source": 4, "target": 5, "weight": 1}
    ]
}

news3.json

{
    "nodes": [
        { "name": "El Universal", "type": "publication", "weight": 10 },
        { "name": "Milenio", "type": "publication", "weight": 4},
        { "name": "Proceso", "type": "publication", "weight": 4},
        { "name": "Paco", "type": "person", "weight": 12},
        { "name": "Juan", "type": "person", "weight": 5},
        { "name": "Alberto", "type": "person", "weight": 5 },
        { "name": "Xochitl", "type": "person", "weight": 3 },
        { "name": "Reforma", "type": "publication", "weight": 2},
        { "name": "Eduardo", "type": "person", "weight": 2}
    ],
    "edges": [
        { "source": 3, "target": 0, "weight": 9},
        { "source": 3, "target": 1, "weight": 3},
        { "source": 4, "target": 2, "weight": 4},
        { "source": 4, "target": 0, "weight": 1},
        { "source": 5, "target": 3, "weight": 5},
        { "source": 6, "target": 1, "weight": 1},
        { "source": 6, "target": 7, "weight": 2},
        { "source": 6, "target": 1, "weight": 1},
        { "source": 3, "target": 5, "weight": 4},
        { "source": 4, "target": 5, "weight": 1},
        { "source": 8, "target": 7, "weight": 2}
    ]
}

在我修改了分配给force.on('tick', ...);上的tick事件的函数之后,我得到了它的工作。小修改:原来用groupSelection.selectAll('circle')groupSelection.selectAll('text'),现在用groupSelection.select('circle')groupSelection.select('text')

你可以在这里看到一个工作演示。

代码如下:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>D3: Force layout</title>
        <script type="text/javascript" src="../d3/d3.v3.js"></script>
        <style type="text/css">
            #tooltip {
                position: absolute;
                width: 200px;
                height: auto;
                padding: 10px;
                background-color: white;
                -webkit-border-radius: 10px;
                -moz-border-radius: 10px;
                border-radius: 10px;
                -webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
                -moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
                box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
                pointer-events: none;
            }
            #tooltip.hidden {
                display: none;
            }
            #tooltip p {
                margin: 0;
                font-family: sans-serif;
                font-size: 16px;
                line-height: 20px;
            }
        </style>    
    </head>
    <body>
        <div id="tooltip" class="hidden">
            <p><span id="type"></span></p>
            <p><span id="name"></span></p>
        </div>  
        <p id="refresh">Click on this text to update the chart with new data values (once).</p>
        <script type="text/javascript">
            //Width and height
            var w = 300;
            var h = 200;
            //http://stackoverflow.com/questions/16455194/how-to-store-a-json-object-loaded-from-a-file
            var force = d3.layout.force()
                .size([w, h])
                .linkDistance([20])
                .charge([-50]);         
            var svg = d3.select('body')
                .append('svg')
                .attr('width', w)
                .attr('height', h);         
            drawGraph = function(dataset) {
                force.nodes(dataset.nodes);
                force.links(dataset.edges);
                force.start();
                var lineSelections = svg.selectAll('line')
                    .data(dataset.edges, function(d){
                        return '.'.concat(d.source.name, '.', d.target.name);
                    });
                lineSelections
                    .enter()
                    .append('line')
                    .style('stroke', function(d) {                  
                        if (d.target.type === 'publication') {
                            return 'red';
                        } else {
                            return 'blue';
                        }
                    })
                    .style('stroke-width', function(d) {
                        return d.weight;
                    });     
                var groupSelection = svg.selectAll('g')
                    .data(dataset.nodes, function(d) {
                        return d.name;
                    })
                    .call(force.drag);
                var groups = groupSelection
                    .enter()
                    .append('g')
                    .call(force.drag);
                groups
                    .append('circle')
                    .attr('r', function(d) {
                        if (d.type === 'publication') {
                            var radius = d.weight / 6;
                            if (radius < 5) {
                                radius = 5;
                            } 
                            return radius;
                        } else {
                            return d.weight * 3;
                        }
                    })
                    .style('fill', function(d, i) {
                        if (d.type === 'publication') {
                            return 'black';
                        } else {
                            return 'green';
                        }
                    });
                //Create labels
                groups
                   .append('text')
                   .text(function(d) {
                        if (d.type === 'publication') {
                            return d.name;
                        } else {
                            return d.name;
                        }
                   })
                   .attr('x', function(d, i) {
                        d.x;
                   })
                   .attr('y', function(d) {
                        d.x;
                   })
                   .attr('font-family', 'sans-serif')
                   .attr('font-size', '14px')
                   .attr('fill', 'orange');         
                //Every time the simulation "ticks", this will be called
                force.on('tick', function() {           
                    lineSelections
                        .attr('x1', function(d) { 
                            return d.source.x; 
                        })
                        .attr('y1', function(d) { 
                            return d.source.y; 
                        })
                        .attr('x2', function(d) { 
                            return d.target.x; 
                        })
                        .attr('y2', function(d) { 
                            return d.target.y; 
                        });
                    groupSelection
                        .select('circle')
                        .attr('cx', function(d) { 
                            return d.x; 
                        })
                        .attr('cy', function(d) {
                            return d.y; 
                        });
                    //Update all labels
                    groupSelection
                        .select('text')
                        .attr('x', function(d, i) {
                            return d.x;
                        })
                        .attr('y', function(d) {
                            return d.y;
                        });
                });
            }
            d3.json('news.json', function(dataset) {
                drawGraph(dataset);
            });
            d3.select("p")
                .on("click", function() {
            });
            d3.select("#refresh")
                .on("click", function() {
                console.log('==================');
                d3.json('news3.json', function(dataset) {
                    drawGraph(dataset);
                });
            });
        </script>
    </body>
</html>