2017-10-11 16 views
0

J'essaie donc de créer un arbre en D3 (Adapté de here) pour montrer une série de nœuds qui sont une couleur spécifique en fonction de leur valeur. Le problème est que j'obtiens de nouvelles données à des intervalles définis qui peuvent changer ces valeurs. Je veux que l'arbre mette à jour les couleurs en conséquence lorsqu'il reçoit de nouvelles informations. J'ai essayé un certain nombre de méthodes différentes qui permettent à l'arbre entier de se redessiner, de voler sur l'écran et d'étendre automatiquement tous les nœuds enfants. L'effet que je recherche est que la couleur des nœuds mis à jour change, alors que l'arbre respecte l'état des nœuds réduits/non effondrés que l'utilisateur ne voit pas l'arbre entier se réinitialiser lui-même. Est-ce possible?D3 Mise à jour dynamique de l'arborescence des nœuds

Voici ce que j'ai jusqu'à présent:

// Get JSON data 
 

 
var treeData = { 
 
    "name": "rootAlert", 
 
    "alert": "true", 
 
    "children": [{ 
 
    "name": "Child1", 
 
    "alert": "true", 
 
    "children": [{ 
 
     "name": "Child1-1", 
 
     "alert": "false" 
 
    }, { 
 
     "name": "Child1-2", 
 
     "alert": "false" 
 
    }, { 
 
     "name": "Child1-3", 
 
     "alert": "true" 
 
    }] 
 
    }, { 
 
    "name": "Child2", 
 
    "alert": "false", 
 
    "children": [{ 
 
     "name": "Child2-1", 
 
     "alert": "false" 
 
    }, { 
 
     "name": "Child2-2", 
 
     "alert": "false" 
 
    }, { 
 
     "name": "Child2-3", 
 
     "alert": "false" 
 
    }] 
 
    }, { 
 
    "name": "Child3", 
 
    "alert": "false" 
 
    }] 
 
} 
 

 

 

 

 

 

 
// Calculate total nodes, max label length 
 
var totalNodes = 0; 
 
var maxLabelLength = 0; 
 
// variables for drag/drop 
 
var selectedNode = null; 
 
var draggingNode = null; 
 
// panning variables 
 
var panSpeed = 200; 
 
var panBoundary = 20; // Within 20px from edges will pan when dragging. 
 
// Misc. variables 
 
var i = 0; 
 
var duration = 750; 
 
var root; 
 

 
// size of the diagram 
 
var viewerWidth = $(document).width(); 
 
var viewerHeight = $(document).height(); 
 

 
var tree = d3.layout.tree() 
 
    .size([viewerHeight, viewerWidth]); 
 

 
// define a d3 diagonal projection for use by the node paths later on. 
 
var diagonal = d3.svg.diagonal() 
 
    .projection(function(d) { 
 
    return [d.y, d.x]; 
 
    }); 
 

 
// A recursive helper function for performing some setup by walking through all nodes 
 

 
function visit(parent, visitFn, childrenFn) { 
 
    if (!parent) return; 
 

 
    visitFn(parent); 
 

 
    var children = childrenFn(parent); 
 
    if (children) { 
 
    var count = children.length; 
 
    for (var i = 0; i < count; i++) { 
 
     visit(children[i], visitFn, childrenFn); 
 
    } 
 
    } 
 
} 
 

 
function visit2(parent, visitFn, childrenFn) { 
 
    if (!parent) return; 
 

 
    visitFn(parent); 
 

 
    var children = childrenFn(parent); 
 
    if (children) { 
 
    var count = children.length; 
 
    for (var i = 0; i < count; i++) { 
 
     visit(children[i], visitFn, childrenFn); 
 
    } 
 
    } 
 
} 
 

 
// Call visit function to establish maxLabelLength 
 
visit(treeData, function(d) { 
 
    totalNodes++; 
 
    maxLabelLength = Math.max(d.name.length, maxLabelLength); 
 

 
}, function(d) { 
 
    return d.children && d.children.length > 0 ? d.children : null; 
 
}); 
 

 
// TODO: Pan function, can be better implemented. 
 

 
function pan(domNode, direction) { 
 
    var speed = panSpeed; 
 
    if (panTimer) { 
 
    clearTimeout(panTimer); 
 
    translateCoords = d3.transform(svgGroup.attr("transform")); 
 
    if (direction == 'left' || direction == 'right') { 
 
     translateX = direction == 'left' ? translateCoords.translate[0] + speed : translateCoords.translate[0] - speed; 
 
     translateY = translateCoords.translate[1]; 
 
    } else if (direction == 'up' || direction == 'down') { 
 
     translateX = translateCoords.translate[0]; 
 
     translateY = direction == 'up' ? translateCoords.translate[1] + speed : translateCoords.translate[1] - speed; 
 
    } 
 
    scaleX = translateCoords.scale[0]; 
 
    scaleY = translateCoords.scale[1]; 
 
    scale = zoomListener.scale(); 
 
    svgGroup.transition().attr("transform", "translate(" + translateX + "," + translateY + ")scale(" + scale + ")"); 
 
    d3.select(domNode).select('g.node').attr("transform", "translate(" + translateX + "," + translateY + ")"); 
 
    zoomListener.scale(zoomListener.scale()); 
 
    zoomListener.translate([translateX, translateY]); 
 
    panTimer = setTimeout(function() { 
 
     pan(domNode, speed, direction); 
 
    }, 50); 
 
    } 
 
} 
 

 
// Define the zoom function for the zoomable tree 
 

 
function zoom() { 
 
    svgGroup.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); 
 
} 
 

 

 
// define the zoomListener which calls the zoom function on the "zoom" event constrained within the scaleExtents 
 
var zoomListener = d3.behavior.zoom().scaleExtent([0.1, 3]).on("zoom", zoom); 
 

 
// define the baseSvg, attaching a class for styling and the zoomListener 
 
var baseSvg = d3.select("#tree-container").append("svg") 
 
    .attr("width", viewerWidth) 
 
    .attr("height", viewerHeight) 
 
    .attr("class", "overlay") 
 
    .call(zoomListener); 
 

 

 
// Function to center node when clicked/dropped so node doesn't get lost when collapsing/moving with large amount of children. 
 

 
function centerNode(source) { 
 
    scale = zoomListener.scale(); 
 
    x = -source.y0; 
 
    y = -source.x0; 
 
    x = x * scale + viewerWidth/2; 
 
    y = y * scale + viewerHeight/2; 
 
    d3.select('g').transition() 
 
    .duration(duration) 
 
    .attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")"); 
 
    zoomListener.scale(scale); 
 
    zoomListener.translate([x, y]); 
 
} 
 

 
function leftAlignNode(source) { 
 
    scale = zoomListener.scale(); 
 
    x = -source.y0; 
 
    y = -source.x0; 
 
    x = (x * scale) + 100; 
 
    y = y * scale + viewerHeight/2; 
 
    d3.select('g').transition() 
 
    .duration(duration) 
 
    .attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")"); 
 
    zoomListener.scale(scale); 
 
    zoomListener.translate([x, y]); 
 
} 
 

 
// Toggle children function 
 

 
function toggleChildren(d) { 
 
    if (d.children) { 
 
    d._children = d.children; 
 
    d.children = null; 
 
    } else if (d._children) { 
 
    d.children = d._children; 
 
    d._children = null; 
 
    } 
 
    return d; 
 
} 
 

 
function toggle(d) { 
 
    if (d.children) { 
 
    d._children = d.children; 
 
    d.children = null; 
 
    } else { 
 
    d.children = d._children; 
 
    d._children = null; 
 
    } 
 
} 
 

 
// Toggle children on click. 
 

 
function click(d) { 
 
    if (d3.event.defaultPrevented) return; // click suppressed 
 

 
    if (d._children != null) { 
 
    var isCollapsed = true 
 
    } else { 
 
    var isCollapsed = false; 
 
    } 
 

 
    d = toggleChildren(d); 
 
    update(d); 
 

 
    if (isCollapsed) { 
 
    leftAlignNode(d); 
 
    } else { 
 
    centerNode(d); 
 
    } 
 

 
} 
 

 
function update(source) { 
 
    // Compute the new height, function counts total children of root node and sets tree height accordingly. 
 
    // This prevents the layout looking squashed when new nodes are made visible or looking sparse when nodes are removed 
 
    // This makes the layout more consistent. 
 
    var levelWidth = [1]; 
 
    var childCount = function(level, n) { 
 

 
    if (n.children && n.children.length > 0) { 
 
     if (levelWidth.length <= level + 1) levelWidth.push(0); 
 

 
     levelWidth[level + 1] += n.children.length; 
 
     n.children.forEach(function(d) { 
 
     childCount(level + 1, d); 
 
     }); 
 
    } 
 
    }; 
 
    childCount(0, root); 
 
    var newHeight = d3.max(levelWidth) * 25; // 25 pixels per line 
 
    tree = tree.size([newHeight, viewerWidth]); 
 

 
    // Compute the new tree layout. 
 
    var nodes = tree.nodes(root).reverse(), 
 
    links = tree.links(nodes); 
 

 
    // Set widths between levels based on maxLabelLength. 
 
    nodes.forEach(function(d) { 
 
    d.y = (d.depth * (maxLabelLength * 5)); //maxLabelLength * 10px 
 
    // alternatively to keep a fixed scale one can set a fixed depth per level 
 
    // Normalize for fixed-depth by commenting out below line 
 
    // d.y = (d.depth * 500); //500px per level. 
 
    }); 
 

 
    // Update the nodes… 
 
    node = svgGroup.selectAll("g.node") 
 
    .data(nodes, function(d) { 
 
     return d.id || (d.id = ++i); 
 
    }); 
 

 
    // Enter any new nodes at the parent's previous position. 
 
    var nodeEnter = node.enter().append("g") 
 
    //.call(dragListener) 
 
    .attr("class", "node") 
 
    .attr("transform", function(d) { 
 
     return "translate(" + source.y0 + "," + source.x0 + ")"; 
 
    }) 
 
    .on('click', click); 
 

 
    nodeEnter.append("circle") 
 
    .attr('class', 'nodeCircle') 
 
    .attr("r", 0) 
 
    .style("fill", function(d) { 
 
     return d._children ? "lightsteelblue" : "#fff"; 
 
    }); 
 

 
    nodeEnter.append("text") 
 
    .attr("x", function(d) { 
 
     return d.children || d._children ? -10 : 10; 
 
    }) 
 
    .attr("dy", ".35em") 
 
    .attr('class', 'nodeText') 
 
    .attr("text-anchor", function(d) { 
 
     return d.children || d._children ? "end" : "start"; 
 
    }) 
 
    .text(function(d) { 
 
     return d.name; 
 
    }) 
 
    .style("fill-opacity", 0); 
 

 

 
    // Update the text to reflect whether node has children or not. 
 
    node.select('text') 
 
    .attr("x", function(d) { 
 
     return d.children || d._children ? -10 : 10; 
 
    }) 
 
    .attr("text-anchor", function(d) { 
 
     return d.children || d._children ? "end" : "start"; 
 
    }) 
 
    .text(function(d) { 
 
     return d.name; 
 
    }); 
 

 
    // Change the circle fill depending on whether it has children and is collapsed 
 
    node.select("circle.nodeCircle") 
 
    .attr("r", 4.5) 
 
    .style("fill", function(d) { 
 
     return d._children ? "lightsteelblue" : "#fff"; 
 
    }); 
 

 
    // Transition nodes to their new position. 
 
    var nodeUpdate = node.transition() 
 
    .duration(duration) 
 
    .attr("transform", function(d) { 
 
     return "translate(" + d.y + "," + d.x + ")"; 
 
    }); 
 
    nodeUpdate.select("circle") 
 
    .attr("r", 4.5) 
 
    .style("fill", function(d) { // alert(d.alert); 
 
     //console.log(d.name + ' is ' + d.alert) 
 
     if (d.alert == 'true') //if alert == true 
 
     return "red"; 
 
     else return d._children ? "green" : "green"; 
 
    }); 
 

 
    // Fade the text in 
 
    nodeUpdate.select("text") 
 
    .style("fill-opacity", 1); 
 

 
    // Transition exiting nodes to the parent's new position. 
 
    var nodeExit = node.exit().transition() 
 
    .duration(duration) 
 
    .attr("transform", function(d) { 
 
     return "translate(" + source.y + "," + source.x + ")"; 
 
    }) 
 
    .remove(); 
 

 
    nodeExit.select("circle") 
 
    .attr("r", 0); 
 

 
    nodeExit.select("text") 
 
    .style("fill-opacity", 0); 
 

 
    // Update the links… 
 
    var link = svgGroup.selectAll("path.link") 
 
    .data(links, function(d) { 
 
     return d.target.id; 
 
    }); 
 

 
    // Enter any new links at the parent's previous position. 
 
    link.enter().insert("path", "g") 
 
    .attr("class", "link") 
 
    .attr("d", function(d) { 
 
     var o = { 
 
     x: source.x0, 
 
     y: source.y0 
 
     }; 
 
     return diagonal({ 
 
     source: o, 
 
     target: o 
 
     }); 
 
    }); 
 

 
    // Transition links to their new position. 
 
    link.transition() 
 
    .duration(duration) 
 
    .attr("d", diagonal); 
 

 
    // Transition exiting nodes to the parent's new position. 
 
    link.exit().transition() 
 
    .duration(duration) 
 
    .attr("d", function(d) { 
 
     var o = { 
 
     x: source.x, 
 
     y: source.y 
 
     }; 
 
     return diagonal({ 
 
     source: o, 
 
     target: o 
 
     }); 
 
    }) 
 
    .remove(); 
 

 
    // Stash the old positions for transition. 
 
    nodes.forEach(function(d) { 
 
    d.x0 = d.x; 
 
    d.y0 = d.y; 
 
    }); 
 
} 
 

 
// Append a group which holds all nodes and which the zoom Listener can act upon. 
 
var svgGroup = baseSvg.append("g"); 
 

 
// Define the root 
 
root = treeData; 
 
root.x0 = viewerHeight/2; 
 
root.y0 = 0; 
 

 
// Layout the tree initially and center on the root node. 
 
tree.nodes(root).forEach(function(n) { 
 
    toggle(n); 
 
}); 
 
update(root); 
 
leftAlignNode(root); 
 

 

 
setInterval(function() { 
 
    //update the color of each node 
 
}, 2000);
.node { 
 
    cursor: pointer; 
 
} 
 

 
.overlay { 
 
    background-color: #EEE; 
 
} 
 

 
.node circle { 
 
    fill: #fff; 
 
    stroke: gray; 
 
    stroke-width: 1.5px; 
 
} 
 

 
.node text { 
 
    font: 10px sans-serif; 
 
} 
 

 
.link { 
 
    fill: none; 
 
    stroke: #ccc; 
 
    stroke-width: 1.5px; 
 
} 
 

 
.templink { 
 
    fill: none; 
 
    stroke: red; 
 
    stroke-width: 3px; 
 
} 
 

 
.ghostCircle.show { 
 
    display: block; 
 
} 
 

 
.ghostCircle, 
 
.activeDrag .ghostCircle { 
 
    display: none; 
 
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> 
 
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script> 
 
<div id="tree-container"></div>

J'ai trouvé un excellent exemple here

Il ne fond exactement ce que je veux. Cependant, au lieu de mettre à jour l'arbre quand une sélection est faite, je veux qu'il se mette à jour automatiquement quand de nouvelles données sont reçues. Je voudrais aussi le voir pas dans AngularJS. J'ai essayé d'implémenter le même type de fonction de mise à jour à partir de cet exemple, mais le mien est toujours positionné en haut à gauche, alors que l'exemple est si lisse!

Répondre

0

Il est peut-être préférable de restituer la visualisation lorsque votre pipeline de données est mis à jour.

Vous devrez sauvegarder l'état de votre visualisation, en conservant toutes les informations qui seront nécessaires pour re-rendre la visualisation, afin que vos utilisateurs n'en soient pas plus sages.

+0

Oui, c'est ce que je cherche à faire. J'ai inclus un exemple de ceci fonctionnant dans le poteau original, mais je ne peux pas sembler le reproduire par moi-même. J'ai mis en œuvre la plupart des changements que l'exemple a (moins les choses angulaires) et il apparaît encore et redessine le tout – jzeef