Gibt es eine Möglichkeit, in ein D3-Force-Layout-Diagramm zu zoomen?

80

D3 hat eine gerichtete Kraft Layout hier . Gibt es eine Möglichkeit, diesem Diagramm das Zoomen hinzuzufügen? Derzeit konnte ich das Mausradereignis erfassen, bin mir aber nicht sicher, wie ich die Neuzeichnungsfunktion selbst schreiben soll. Irgendwelche Vorschläge?

    var vis = d3.select("#graph")
        .append("svg:svg")
        .call(d3.behavior.zoom().on("zoom", redraw)) // <-- redraw function
        .attr("width", w)
        .attr("height", h);
Legende
quelle
Siehe auch dieses Beispiel thisismattmiller.com/blog/add-zoom-slider-to-d3-js von Matt Miller. Es wird nur am Ende des Prozesses ein "g" -Element hinzugefügt.
Arivero
4
Jemand zeigte, wie man zui53 (eine Bibliothek für zoombare Benutzeroberflächen) und d3js kombiniert: bl.ocks.org/timelyportfolio/5149102
erweitert am

Antworten:

97

Update 04.06.14

Siehe auch die Antwort von Mike Bostock hier für Änderungen in D3 v.3 und dem zugehörigen Beispiel . Ich denke, dies ersetzt wahrscheinlich die Antwort unten.

Update 18.02.2014

Ich denke, die Antwort von @ ahaarnos ist vorzuziehen, wenn Sie möchten, dass das gesamte SVG schwenkt und zoomt. Die verschachtelten gElemente in meiner Antwort unten sind wirklich nur erforderlich, wenn Sie nicht zoomende Elemente in derselben SVG haben (in der ursprünglichen Frage nicht der Fall). Wenn Sie tun , um das Verhalten zu einem anwenden gElemente, dann ein Hintergrund rectoder ein ähnliches Element erforderlich , um sicherzustellen , dass die gZeiger - Ereignisse empfängt.

Ursprüngliche Antwort

Ich habe dies basierend auf dem Zoom-Pan-Transform- Beispiel zum Laufen gebracht - Sie können meine jsFiddle hier sehen: http://jsfiddle.net/nrabinowitz/QMKm3/

Es war etwas komplexer als ich gehofft hatte - Sie müssen mehrere gElemente verschachteln , damit es funktioniert, das SVG- pointer-eventsAttribut auf setzen allund dann ein Hintergrundrechteck anhängen, um die Zeigerereignisse zu empfangen (andernfalls funktioniert es nur, wenn der Zeiger vorbei ist ein Knoten oder eine Verbindung). Die redrawFunktion ist vergleichsweise einfach und setzt lediglich eine Transformation auf das Innerste 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 + ")");
}

Dadurch wird das gesamte SVG effektiv skaliert, sodass auch die Strichbreite skaliert wird, wie beim Vergrößern eines Bildes.

Es gibt ein anderes Beispiel , das eine ähnliche Technik veranschaulicht.

nrabinowitz
quelle
1
@Ogg - Ich bin mir nicht sicher, was Sie hier meinen - jsFiddle präsentiert Ihre Ergebnisse nur in einem iFrame, nicht in einem benutzerdefinierten Browser. Sie sehen also echtes Browserverhalten. jsFiddle fügt einige Dinge hinzu, z. B. ein bodyTag. Ich empfehle daher, sich die Frame-Quelle anzusehen und zu sehen, was Ihnen fehlt.
Nrabinowitz
2
@EricStob - das könnte eine neue Frage sein. Aber siehe jsfiddle.net/56RDx/2 - dies skaliert einfach die Schriftgröße um die Umkehrung der Zoomskala.
Nrabinowitz
1
@ajmartin - siehezoom.scaleExtent()
nrabinowitz
1
Bei Verwendung von Version 3 von d3 funktioniert die Möglichkeit, einen einzelnen Knoten zu ziehen, in diesem Beispiel nicht. Stattdessen wird das gesamte Diagramm so verschoben, als hätten Sie nicht auf einen Knoten geklickt. Dies funktioniert auf Version 2, aber ich brauche eine Funktion auf Version 3 . Irgendwelche Ideen?
Daryl Van Sittert
1
Hier ist die Lösung für D3 v3: stackoverflow.com/questions/17953106/…
David Marx
18

Warum die verschachtelten <g>?

Dieser Code unten hat bei mir gut funktioniert (nur einer <g>ohne zufälliges großes Weiß <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 + ")");
}

Wobei alle Elemente in Ihrem SVG an das visElement angehängt werden .

aaaronic
quelle
1
Könnte es sein, dass Sie die Attribute "viewBox", "generateAspectRatio" und "pointer-events" verlieren könnten und es trotzdem funktionieren würde?
notan3xit
@ notan3xit ist richtig, viewBox ,erveAspectRatio und Zeigerereignisse sind nicht erforderlich. Der Schlüssel besteht darin, das transformationAttribut auf das gElement anzuwenden , nicht auf das svgElement.
Lekensteyn
Scheint nicht mit D3 v3 zu funktionieren, oder besser gesagt, die Zoomfunktion funktioniert immer noch, aber die Fähigkeit, einzelne Knoten zu verschieben, geht verloren. @nrabinowitz Lösung zeigt das gleiche Problem. Hier ist nrabinowitz 'Geige aktualisiert, um die Lösung von ahaarnos zu verwenden: jsfiddle.net/QMKm3/716 und hier ist dieselbe Geige, die aktualisiert wurde, um D3v3 zu verwenden, um das Problem zu veranschaulichen: jsfiddle.net/QMKm3/717
David Marx
Perfekte Idee, um das Zoomverhalten zum SVG-Element hinzuzufügen. Ich wusste nicht, dass Sie das tun können, und habe daher immer auf lästige Hintergrundrechtecke zurückgegriffen. Das Hinzufügen des Verhaltens in der SVG funktioniert zumindest in modernen Versionen von Chrome, FF und Opera.
Rcijvat
14

Die bereitgestellten Antworten funktionieren in D3 v2, jedoch nicht in v3. Ich habe die Antworten zu einer sauberen Lösung zusammengefasst und das v3-Problem mithilfe der hier angegebenen Antwort behoben : Warum bricht d3.js v3 mein Force-Diagramm, wenn das Zoomen implementiert wird, wenn v2 dies nicht tut?

Zuerst der Hauptcode. Dies ist eine bereinigte Version der Antwort von @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 + ")");
    }   

Jetzt können Sie schwenken und zoomen, aber Sie können keine Knoten ziehen, da die Schwenkfunktion die Ziehfunktion überschreibt. Also müssen wir das tun:

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
});

Hier ist @nrabinowitz 'Geige, die geändert wurde, um diese sauberere Zoom-Implementierung zu verwenden, aber veranschaulicht, wie D3v3 das Ziehen von Knoten unterbricht: http://jsfiddle.net/QMKm3/718/

Und hier ist dieselbe Geige, die für D3v3 geändert wurde: http://jsfiddle.net/QMKm3/719/

David Marx
quelle
2

Ich habe mein Diagramm ohne das zweite "svg: g" -Anhängen zum Laufen gebracht.

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

Der Rest ist der gleiche.

cem
quelle
aber ohne das Rechteck: Sie können nicht schwenken (nur zoomen)
user1043144
0

Ich habe eine Lösung für D3 Force Graced Graph mit Zoom-Option.

    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
quelle
0

Wenn Sie das Layout zoomen und schwenken möchten, ohne die Knotengröße zu ändern, versuchen Sie es unten. Sie können Knoten auch ziehen, ohne zu zittern. Dieser Code basiert auf dem ursprünglichen Force-Layout-Beispiel. Informationen zu Knoten- und Linkdaten finden Sie in den Originalbeispieldaten. http://bl.ocks.org/mbostock/4062045

Bitte beachten Sie die Variablen xScale und yScale, die Funktionen dragstarted (), dragged () und dragended (). Die Funktion tick () wurde ebenfalls geändert.

Sie können das Ergebnis unter http://steelblue.tistory.com/9 sehen. Die Sprache auf der Website ist Koreanisch. Das Ergebnis finden Sie jedoch leicht im dritten Beispiel auf der Seite.

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
quelle