简体   繁体   中英

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

First sorry for my question, because I think this is because I don't understand very well D3 library, especially the working of Selections.

Here's what I want to do:

  1. Showing a graph that links publications with people who tweet about the publications.
  2. Update the graph when the data change (in this case when use clicks the paragraph above that says "click to update").

Here I don't want to do complete redraw of the graph; I want the existing nodes and links stay where they are, and new nodes and links fly in to the scene. That's why I keep the instance of force layout outside of the drawGraph function (as global variable).

Task #1 done without a problem.

The problem is with task #2; I could make the new node enter the scene..., but for some reason I cannot drag the existing nodes. I can only drag the new nodes (Eduardo).

I have debugged it in Chrome, and saw this "var a" has 9 elements (after clicking). So, I suppose the function "force.layout" is supposed to be invoked by D3 for all those 9 elements. So, if I undestand correctly, I should've been able to drag all the 9 circles.

But that's not the case, so something is wrong with my code. Can anybody please point out where I got it wrong?

Here is the code. And after that comes the JSON (two separate jsons, news.json and news3.json).

Additional question:

I don't quite understand why this block (the key function) was executed 17 times (8 + 9) on the second invocation of drawGraph function (when the json is updated to news3.json)? My expectation is 9 times.

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

Thanks in advance!,

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}
    ]
}

I got it working, after I modified the function assigned to the tick event on force.on('tick', ...); . Small modif: instead of using groupSelection.selectAll('circle') and groupSelection.selectAll('text') , now I use groupSelection.select('circle') and groupSelection.select('text') .

You can see a working demo here .

Here is the code:

<!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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM