2017-01-29 56 views
2

我在使用v4时遇到D3中缩放功能的问题。它会抛出一个错误,指出zoom.translate没有被定义。我主要使用下面的代码从这个答案d3 focus on node on click,完美的v3工作。但是,由于我对v3有问题,因为它对源和节点以字符串(而不是索引)形式存在的数据有限制,所以我切换到了v4。v4中的d3缩放功能问题

<!DOCTYPE html> 
<meta charset="utf-8"> 
<style> 

.links line { 
    stroke: #999; 
    stroke-opacity: 0.6; 
} 

.nodes circle { 
    stroke: #fff; 
    stroke-width: 1.5px; 
} 

</style> 
<svg width="960" height="600"></svg> 
<script src="https://d3js.org/d3.v4.min.js"></script> 
<script> 


var svg = d3.select("svg"), 
    width = +svg.attr("width"), 
    height = +svg.attr("height") 
    active = d3.select(null); 

var zoom = d3.zoom() 
    .scaleExtent([1, 8]) 
    .on("zoom", zoomed);  

var color = d3.scaleOrdinal(d3.schemeCategory20); 

var simulation = d3.forceSimulation() 
    .force("link", d3.forceLink().id(function(d) { return d.id; })) 
    .force("charge", d3.forceManyBody()) 
    .force("center", d3.forceCenter(width/2, height/2)); 

d3.json("graph.json", function(error, graph) { 
    if (error) throw error; 

    var link = svg.append("g") 
     .attr("class", "links") 
    .selectAll("line") 
    .data(graph.links) 
    .enter().append("line") 
     .attr("stroke-width", function(d) { return Math.sqrt(d.value); }); 

    var node = svg.append("g") 
     .attr("class", "nodes") 
    .selectAll("circle") 
    .data(graph.nodes) 
    .enter().append("circle") 
     .attr("r", 5) 
     .attr("fill", function(d) { return color(d.group); }) 
     .call(d3.drag() 
      .on("start", dragstarted) 
      .on("drag", dragged) 
      .on("end", dragended)) 
      .on("click", clicked); 

    node.append("title") 
     .text(function(d) { return d.id; }); 

    simulation 
     .nodes(graph.nodes) 
     .on("tick", ticked); 

    simulation.force("link") 
     .links(graph.links); 

    function ticked() { 
    link 
     .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; }); 

    node 
     .attr("cx", function(d) { return d.x; }) 
     .attr("cy", function(d) { return d.y; }); 
    } 
}); 

function dragstarted(d) { 
    if (!d3.event.active) simulation.alphaTarget(0.3).restart(); 
    d.fx = d.x; 
    d.fy = d.y; 
} 

function dragged(d) { 
    d.fx = d3.event.x; 
    d.fy = d3.event.y; 
} 

function dragended(d) { 
    if (!d3.event.active) simulation.alphaTarget(0); 
    d.fx = null; 
    d.fy = null; 
} 

function clicked(d){ 
    if (active.node() === this) return reset(); 
    active.classed("active", false); 
    active = d3.select(this).classed("active", true); 

    var bbox = active.node().getBBox(), 
     bounds = [[bbox.x, bbox.y],[bbox.x + bbox.width, bbox.y + bbox.height]]; 

    var dx = bounds[1][0] - bounds[0][0], 
     dy = bounds[1][1] - bounds[0][1], 
     x = (bounds[0][0] + bounds[1][0])/2, 
     y = (bounds[0][1] + bounds[1][1])/2, 
     scale = Math.max(1, Math.min(8, 0.9/Math.max(dx/width, dy/height))), 
     translate = [width/2 - scale * x, height/2 - scale * y]; 

    svg.transition() 
     .duration(750) 
     .call(zoom.translate(translate).scale(scale).event); 
} 

function reset() { 
    active.classed("active", false); 
    active = d3.select(null); 

    svg.transition() 
     .duration(750) 
     .call(zoom.translate([0, 0]).scale(1).event); 
} 

function zoomed() { 
    console.log(d3.event) 
    g.style("stroke-width", 1.5/d3.event.scale + "px"); 
    g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); 
}   

</script> 

我改变d3.behaviour.zoom()d3.zoom()甚至改变

.call(zoom.translate(translate).scale(scale).event); 

.call(d3.zoom().on("zoom", function() { 
     svg.attr("transform", d3.event.transform) 
})); 

其抛出了一个奇怪的错误,错误:未知类型:轮

会是什么克服这种情况的最好方法是什么?

+0

在[我的回答(http://stackoverflow.com/a/41917020/5768908)你刚才的问题,我没有”不要说你应该升级到v4。其实,我向你展示了如何使用v3来做到这一点。 –

+0

不幸的是,我没有数据中的数据,而是在一个JSON文件中。它还能完成吗? – VerletIntegrator

+0

是的,它可以。 JSON只是用于存储数据的语法:您的JSON可能包含一个数组。 –

回答

3

d3版本4做到这一点,正确的做法是:

function clicked(d) { 

    if (active.node() === this){ 
     active.classed("active", false); 
     return reset(); 
    } 

    active = d3.select(this).classed("active", true); 

    svg.transition() 
     .duration(750) 
     .call(zoom.transform, 
     d3.zoomIdentity 
     .translate(width/2, height/2) 
     .scale(8) 
     .translate(-(+active.attr('cx')), -(+active.attr('cy'))) 
    ); 
    } 

如果您的变焦处理程序是:

注意,我简化了变换计算从我以前的答案。那里的界限计算并不是真的有必要。


全码:

<!DOCTYPE html> 
 
<meta charset="utf-8"> 
 
<style> 
 
    .links line { 
 
    stroke: #aaa; 
 
    } 
 
    
 
    .nodes circle { 
 
    pointer-events: all; 
 
    stroke: none; 
 
    stroke-width: 40px; 
 
    } 
 
    
 
    .active { 
 
    fill: yellow; 
 
    } 
 
</style> 
 
<svg width="960" height="600"></svg> 
 
<script src="https://d3js.org/d3.v4.min.js"></script> 
 
<script> 
 
    var svg = d3.select("svg"), 
 
    width = +svg.attr("width"), 
 
    height = +svg.attr("height"); 
 

 
    var zoom = d3.zoom() 
 
    .scaleExtent([1/2, 4]) 
 
    .on("zoom", zoomed); 
 

 
    var g = svg.append("g"); 
 

 
    var simulation = d3.forceSimulation() 
 
    .force("link", d3.forceLink().id(function(d) { 
 
     return d.id; 
 
    })) 
 
    .force("charge", d3.forceManyBody()) 
 
    .force("center", d3.forceCenter(width/2, height/2)); 
 

 
    var graph = { 
 
    "nodes": [{ 
 
     "id": "Myriel", 
 
     "group": 1 
 
    }, { 
 
     "id": "Napoleon", 
 
     "group": 1 
 
    }, { 
 
     "id": "Mlle.Baptistine", 
 
     "group": 1 
 
    }, { 
 
     "id": "Mme.Magloire", 
 
     "group": 1 
 
    }, { 
 
     "id": "CountessdeLo", 
 
     "group": 1 
 
    }, { 
 
     "id": "Geborand", 
 
     "group": 1 
 
    }, { 
 
     "id": "Champtercier", 
 
     "group": 1 
 
    }, { 
 
     "id": "Cravatte", 
 
     "group": 1 
 
    }, { 
 
     "id": "Count", 
 
     "group": 1 
 
    }, { 
 
     "id": "OldMan", 
 
     "group": 1 
 
    }, { 
 
     "id": "Labarre", 
 
     "group": 2 
 
    }, { 
 
     "id": "Valjean", 
 
     "group": 2 
 
    }, { 
 
     "id": "Marguerite", 
 
     "group": 3 
 
    }, { 
 
     "id": "Mme.deR", 
 
     "group": 2 
 
    }, { 
 
     "id": "Isabeau", 
 
     "group": 2 
 
    }, { 
 
     "id": "Gervais", 
 
     "group": 2 
 
    }, { 
 
     "id": "Tholomyes", 
 
     "group": 3 
 
    }, { 
 
     "id": "Listolier", 
 
     "group": 3 
 
    }, { 
 
     "id": "Fameuil", 
 
     "group": 3 
 
    }, { 
 
     "id": "Blacheville", 
 
     "group": 3 
 
    }, { 
 
     "id": "Favourite", 
 
     "group": 3 
 
    }, { 
 
     "id": "Dahlia", 
 
     "group": 3 
 
    }, { 
 
     "id": "Zephine", 
 
     "group": 3 
 
    }, { 
 
     "id": "Fantine", 
 
     "group": 3 
 
    }, { 
 
     "id": "Mme.Thenardier", 
 
     "group": 4 
 
    }, { 
 
     "id": "Thenardier", 
 
     "group": 4 
 
    }, { 
 
     "id": "Cosette", 
 
     "group": 5 
 
    }, { 
 
     "id": "Javert", 
 
     "group": 4 
 
    }, { 
 
     "id": "Fauchelevent", 
 
     "group": 0 
 
    }, { 
 
     "id": "Bamatabois", 
 
     "group": 2 
 
    }, { 
 
     "id": "Perpetue", 
 
     "group": 3 
 
    }, { 
 
     "id": "Simplice", 
 
     "group": 2 
 
    }, { 
 
     "id": "Scaufflaire", 
 
     "group": 2 
 
    }, { 
 
     "id": "Woman1", 
 
     "group": 2 
 
    }, { 
 
     "id": "Judge", 
 
     "group": 2 
 
    }, { 
 
     "id": "Champmathieu", 
 
     "group": 2 
 
    }, { 
 
     "id": "Brevet", 
 
     "group": 2 
 
    }, { 
 
     "id": "Chenildieu", 
 
     "group": 2 
 
    }, { 
 
     "id": "Cochepaille", 
 
     "group": 2 
 
    }, { 
 
     "id": "Pontmercy", 
 
     "group": 4 
 
    }, { 
 
     "id": "Boulatruelle", 
 
     "group": 6 
 
    }, { 
 
     "id": "Eponine", 
 
     "group": 4 
 
    }, { 
 
     "id": "Anzelma", 
 
     "group": 4 
 
    }, { 
 
     "id": "Woman2", 
 
     "group": 5 
 
    }, { 
 
     "id": "MotherInnocent", 
 
     "group": 0 
 
    }, { 
 
     "id": "Gribier", 
 
     "group": 0 
 
    }, { 
 
     "id": "Jondrette", 
 
     "group": 7 
 
    }, { 
 
     "id": "Mme.Burgon", 
 
     "group": 7 
 
    }, { 
 
     "id": "Gavroche", 
 
     "group": 8 
 
    }, { 
 
     "id": "Gillenormand", 
 
     "group": 5 
 
    }, { 
 
     "id": "Magnon", 
 
     "group": 5 
 
    }, { 
 
     "id": "Mlle.Gillenormand", 
 
     "group": 5 
 
    }, { 
 
     "id": "Mme.Pontmercy", 
 
     "group": 5 
 
    }, { 
 
     "id": "Mlle.Vaubois", 
 
     "group": 5 
 
    }, { 
 
     "id": "Lt.Gillenormand", 
 
     "group": 5 
 
    }, { 
 
     "id": "Marius", 
 
     "group": 8 
 
    }, { 
 
     "id": "BaronessT", 
 
     "group": 5 
 
    }, { 
 
     "id": "Mabeuf", 
 
     "group": 8 
 
    }, { 
 
     "id": "Enjolras", 
 
     "group": 8 
 
    }, { 
 
     "id": "Combeferre", 
 
     "group": 8 
 
    }, { 
 
     "id": "Prouvaire", 
 
     "group": 8 
 
    }, { 
 
     "id": "Feuilly", 
 
     "group": 8 
 
    }, { 
 
     "id": "Courfeyrac", 
 
     "group": 8 
 
    }, { 
 
     "id": "Bahorel", 
 
     "group": 8 
 
    }, { 
 
     "id": "Bossuet", 
 
     "group": 8 
 
    }, { 
 
     "id": "Joly", 
 
     "group": 8 
 
    }, { 
 
     "id": "Grantaire", 
 
     "group": 8 
 
    }, { 
 
     "id": "MotherPlutarch", 
 
     "group": 9 
 
    }, { 
 
     "id": "Gueulemer", 
 
     "group": 4 
 
    }, { 
 
     "id": "Babet", 
 
     "group": 4 
 
    }, { 
 
     "id": "Claquesous", 
 
     "group": 4 
 
    }, { 
 
     "id": "Montparnasse", 
 
     "group": 4 
 
    }, { 
 
     "id": "Toussaint", 
 
     "group": 5 
 
    }, { 
 
     "id": "Child1", 
 
     "group": 10 
 
    }, { 
 
     "id": "Child2", 
 
     "group": 10 
 
    }, { 
 
     "id": "Brujon", 
 
     "group": 4 
 
    }, { 
 
     "id": "Mme.Hucheloup", 
 
     "group": 8 
 
    }], 
 
    "links": [{ 
 
     "source": "Napoleon", 
 
     "target": "Myriel", 
 
     "value": 1 
 
    }, { 
 
     "source": "Mlle.Baptistine", 
 
     "target": "Myriel", 
 
     "value": 8 
 
    }, { 
 
     "source": "Mme.Magloire", 
 
     "target": "Myriel", 
 
     "value": 10 
 
    }] 
 
    } 
 

 
    var link = g.append("g") 
 
    .attr("class", "links") 
 
    .selectAll("line") 
 
    .data(graph.links) 
 
    .enter().append("line"); 
 

 
    var node = g.append("g") 
 
    .attr("class", "nodes") 
 
    .selectAll("circle") 
 
    .data(graph.nodes) 
 
    .enter().append("circle") 
 
    .attr("r", 2.5) 
 
    .on('click', clicked); 
 

 
    node.append("title") 
 
    .text(function(d) { 
 
     return d.id; 
 
    }); 
 

 
    simulation 
 
    .nodes(graph.nodes) 
 
    .on("tick", ticked); 
 

 
    simulation.force("link") 
 
    .links(graph.links); 
 

 
    function ticked() { 
 
    link 
 
     .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; 
 
     }); 
 

 
    node 
 
     .attr("cx", function(d) { 
 
     return d.x; 
 
     }) 
 
     .attr("cy", function(d) { 
 
     return d.y; 
 
     }); 
 
    } 
 

 
    var active = d3.select(null); 
 

 
    function clicked(d) { 
 

 
    if (active.node() === this){ 
 
     active.classed("active", false); 
 
     return reset(); 
 
    } 
 
    
 
    active = d3.select(this).classed("active", true); 
 

 
    svg.transition() 
 
     .duration(750) 
 
     .call(zoom.transform, 
 
     d3.zoomIdentity 
 
     .translate(width/2, height/2) 
 
     .scale(8) 
 
     .translate(-(+active.attr('cx')), -(+active.attr('cy'))) 
 
    ); 
 
    } 
 

 
    function reset() { 
 
    svg.transition() 
 
     .duration(750) 
 
     .call(zoom.transform, 
 
     d3.zoomIdentity 
 
     .translate(0, 0) 
 
     .scale(1) 
 
    ); 
 
    } 
 

 
    function zoomed() { 
 
    g.attr("transform", d3.event.transform); 
 
    } 
 
</script>