¿Hay alguna forma de hacer zoom en un gráfico de diseño de fuerza D3?

80

D3 tiene una fuerza dirigida diseño aquí . ¿Hay alguna forma de agregar zoom a este gráfico? Actualmente, pude capturar el evento de la rueda del mouse, pero no estoy realmente seguro de cómo escribir la función de redibujo en sí. ¿Alguna sugerencia?

    var vis = d3.select("#graph")
        .append("svg:svg")
        .call(d3.behavior.zoom().on("zoom", redraw)) // <-- redraw function
        .attr("width", w)
        .attr("height", h);
Leyenda
fuente
Vea también este ejemplo thisismattmiller.com/blog/add-zoom-slider-to-d3-js de Matt Miller. Solo agrega un elemento "g" al final del proceso.
Arivero
4
alguien mostró cómo combinar zui53 (una biblioteca para interfaces ampliables) y d3js: bl.ocks.org/timelyportfolio/5149102
widged

Respuestas:

97

Actualización 4/6/14

Consulte también la respuesta de Mike Bostock aquí para conocer los cambios en D3 v.3 y el ejemplo relacionado . Creo que esto probablemente reemplaza la respuesta a continuación.

Actualización 18/02/2014

Creo que la respuesta de @ahaarnos es preferible si desea que todo el SVG se mueva y se acerque. Los gelementos anidados en mi respuesta a continuación son realmente necesarios solo si tiene elementos sin zoom en el mismo SVG (no es el caso en la pregunta original). Si haces aplicar el comportamiento de un gelemento, a continuación, un fondo rectse requiere elemento o similar para asegurar que la grecibe eventos de puntero.

Respuesta original

Conseguí que esto funcionara basado en el ejemplo de zoom-pan-transform - puede ver mi jsFiddle aquí: http://jsfiddle.net/nrabinowitz/QMKm3/

Fue un poco más complejo de lo que esperaba: tienes que anidar varios gelementos para que funcione, establecer el pointer-eventsatributo SVG en ally luego agregar un rectángulo de fondo para recibir los eventos del puntero (de lo contrario, solo funciona cuando el puntero se termina un nodo o enlace). La redrawfunción es comparativamente simple, simplemente estableciendo una transformación en el más interno g:

var vis = d3.select("#chart")
  .append("svg:svg")
    .attr("width", w)
    .attr("height", h)
    .attr("pointer-events", "all")
  .append('svg:g')
    .call(d3.behavior.zoom().on("zoom", redraw))
  .append('svg:g');

vis.append('svg:rect')
    .attr('width', w)
    .attr('height', h)
    .attr('fill', 'white');

function redraw() {
  console.log("here", d3.event.translate, d3.event.scale);
  vis.attr("transform",
      "translate(" + d3.event.translate + ")"
      + " scale(" + d3.event.scale + ")");
}

Esto escala efectivamente todo el SVG, por lo que también escala el ancho del trazo, como al hacer zoom en una imagen.

Hay otro ejemplo que ilustra una técnica similar.

nrabinowitz
fuente
1
@Ogg: no estoy seguro de lo que quiere decir aquí: jsFiddle solo presenta sus resultados en un iFrame, no en algún tipo de navegador personalizado, por lo que lo que ve es el comportamiento real del navegador. jsFiddle agrega algunas cosas, por ejemplo, una bodyetiqueta, por lo que recomiendo mirar la fuente del marco y ver lo que se está perdiendo.
nrabinowitz
2
@EricStob: esa podría ser una nueva pregunta. Pero vea jsfiddle.net/56RDx/2 ; esto simplemente cambia la escala del tamaño de fuente por la inversa de la escala de zoom.
nrabinowitz
1
@ajmartin - verzoom.scaleExtent()
nrabinowitz
1
Cuando se usa la versión 3 de d3, la capacidad de arrastrar un nodo individual no funciona en este ejemplo. En su lugar, recorre todo el gráfico como si no hubiera hecho clic en un nodo. Esto funciona en la versión 2 pero necesito una función en la v3. ¿Algunas ideas?
Daryl Van Sittert
1
Aquí está la solución para D3 v3: stackoverflow.com/questions/17953106/…
David Marx
18

¿Por qué los anidados <g>?

Este código a continuación funcionó bien para mí (solo uno <g>, sin blanco grande al azar <rect>:

var svg = d3.select("body")
    .append("svg")
      .attr({
        "width": "100%",
        "height": "100%"
      })
      .attr("viewBox", "0 0 " + width + " " + height )
      .attr("preserveAspectRatio", "xMidYMid meet")
      .attr("pointer-events", "all")
    .call(d3.behavior.zoom().on("zoom", redraw));

var vis = svg
    .append('svg:g');

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

Donde todos los elementos en su svg se agregan al viselemento.

aaarónico
fuente
1
¿Podría ser que podría perder los atributos "viewBox", "preserveAspectRatio" y "pointer-events" y aún funcionaría?
notan3xit
@ notan3xit es correcto, viewBox, preserveAspectRatio y pointer-events no son necesarios. La clave es aplicar el transformationatributo en el gelemento, no en el svgelemento.
Lekensteyn
No parece funcionar con D3 v3, o más bien la función de zoom aún funciona, pero se pierde la capacidad de mover nodos individuales. La solución de @nrabinowitz presenta el mismo problema. Aquí está el violín de nrabinowitz actualizado para usar la solución de ahaarnos : jsfiddle.net/QMKm3/716 y aquí está el mismo violín actualizado para usar D3v3 para ilustrar el problema: jsfiddle.net/QMKm3/717
David Marx
Perfecta idea para agregar el comportamiento de zoom al elemento SVG, no sabía que podía hacer eso y por eso siempre recurrí al uso de molestos rectángulos de fondo. Agregar el comportamiento en el SVG funciona al menos en las versiones modernas de Chrome, FF y Opera.
rcijvat
14

Las respuestas proporcionadas funcionan en D3 v2 pero no en v3. Sinteticé las respuestas en una solución limpia y resolví el problema de v3 usando la respuesta proporcionada aquí: ¿Por qué d3.js v3 rompe mi gráfico de fuerza al implementar el zoom cuando v2 no lo hace?

Primero el código principal. Esta es una versión limpia de la respuesta de @ahaarnos:

    var svg = d3.select("body")
        .append("svg")
        .attr("width", width)
        .attr("height", height)
            .call(d3.behavior.zoom().on("zoom", redraw))
        .append('g');

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

Ahora tiene panorámica y zoom, pero no podrá arrastrar nodos porque la función de panorámica anulará la función de arrastre. Entonces tenemos que hacer esto:

var drag = force.stop().drag()
.on("dragstart", function(d) {
    d3.event.sourceEvent.stopPropagation(); // to prevent pan functionality from 
                                            //overriding node drag functionality.
    // put any other 'dragstart' actions here
});

Aquí está el violín de @nrabinowitz modificado para usar esta implementación de zoom más limpia, pero ilustrando cómo D3v3 rompe el arrastre del nodo: http://jsfiddle.net/QMKm3/718/

Y aquí está el mismo violín modificado para funcionar con D3v3: http://jsfiddle.net/QMKm3/719/

David Marx
fuente
2

Conseguí que mi gráfico funcionara sin el segundo añadido "svg: g".

[...].attr("pointer-events", "all")
     .attr("width", width2)
     .attr("height", height2)
     .append('svg:g')
     .call(d3.behavior.zoom().on("zoom", redraw));

El resto es igual.

cem
fuente
pero sin el rectángulo: no se puede desplazar (solo zoom)
user1043144
0

Obtuve una solución para el gráfico dirigido por fuerza D3 con opción de zoom.

    var m = [40, 240, 40, 240],
    width = 960,
    height = 700,
    root;
var svg = d3.select("body").append("svg")
    .attr("class", "svg_container")
    .attr("width", width)
    .attr("height", height)
    .style("overflow", "scroll")
    .style("background-color", "#EEEEEE")
    .append("svg:g")
    .attr("class", "drawarea")
    .append("svg:g")
    .attr("transform", "translate(" + m[3] + "," + m[0] + ")");

//applying zoom in&out for svg
d3.select("svg") 
.call(d3.behavior.zoom()
    .scaleExtent([0.5, 5])
    .on("zoom", zoom));

//zooming 
function zoom() { //zoom in&out function 
    var scale = d3.event.scale,
        translation = d3.event.translate,
        tbound = -height * scale,
        bbound = height * scale,
        lbound = (-width + m[1]) * scale,
        rbound = (width - m[3]) * scale;
    // limit translation to thresholds
    translation = [
        Math.max(Math.min(translation[0], rbound), lbound),
        Math.max(Math.min(translation[1], bbound), tbound)
    ];
    d3.select(".drawarea")
        .attr("transform", "translate(" + translation + ")" +
            " scale(" + scale + ")");
}
Aravind Cheekkallur
fuente
0

Si desea hacer zoom y mover el diseño de fuerza sin cambiar el tamaño del nodo, intente a continuación. También puede arrastrar nodos sin temblar. Este código se basa en el ejemplo de diseño de fuerza original. En cuanto a los datos de nodos y enlaces, consulte los datos de muestra originales. http://bl.ocks.org/mbostock/4062045

Tenga en cuenta las variables xScale y yScale, las funciones dragstarted (), dragged () y dragended (). La función tick () también se cambió.

Puede ver el resultado en http://steelblue.tistory.com/9 El idioma del sitio es coreano. Sin embargo, puede encontrar fácilmente el resultado en el tercer ejemplo de la página.

var graph = {
    "nodes": [
      { "name": "Myriel", "group": 1 },
      { "name": "Napoleon", "group": 1 },
      // ......
      { "name": "Mme.Hucheloup", "group": 8 }
    ],
    "links": [
      { "source": 1, "target": 0, "value": 1 },
      { "source": 2, "target": 0, "value": 8 },
    // .......
      { "source": 76, "target": 58, "value": 1 }
    ]
};
var width = 640,
    height = 400;
 var color = d3.scale.category20();



var xScale = d3.scale.linear()
        .domain([0, width])
         .range([0, width]);

var yScale = d3.scale.linear()
    .domain([0, height])
   .range([0, height]);
var zoomer = d3.behavior.zoom().x(xScale).y(yScale).scaleExtent([0.1, 8]).on("zoom", zoom);
function zoom() {

    tick(); 
};

var drag = d3.behavior.drag()
        .origin(function (d) { return d; })
         .on("dragstart", dragstarted)
        .on("drag", dragged)
        .on("dragend", dragended);

function dragstarted(d) {
    d3.event.sourceEvent.stopPropagation();

    d.fixed |= 2;         
}
function dragged(d) {

    var mouse = d3.mouse(svg.node());
    d.x = xScale.invert(mouse[0]);
    d.y = yScale.invert(mouse[1]);
    d.px = d.x;         
    d.py = d.y;
    force.resume();
}

function dragended(d) {

    d.fixed &= ~6;           }

var force = d3.layout.force()
    .charge(-120)
    .linkDistance(30)
    .size([width, height]);

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

svg.call(zoomer);

    force
        .nodes(graph.nodes)
        .links(graph.links)
        .start();

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

    var node = svg.selectAll(".node")
        .data(graph.nodes)
      .enter().append("circle")
        .attr("class", "node")
        .attr("r", 5)
        .style("fill", function (d) { return color(d.group); })
        .call(drag);

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

    force.on("tick",tick);

function tick(){            
        link.attr("x1", function (d) { return  xScale(d.source.x); })
            .attr("y1", function (d) { return yScale(d.source.y);  })
            .attr("x2", function (d) { return xScale(d.target.x); })
            .attr("y2", function (d) { return yScale(d.target.y); });

        node.attr("transform", function (d) {
            return "translate(" + xScale(d.x) + "," + yScale(d.y) + ")";
        });


    };

seungbum kim
fuente