Czy istnieje sposób na powiększenie wykresu układu sił D3?

80

D3 ma tutaj układ ukierunkowany na siłę . Czy istnieje sposób na dodanie powiększenia do tego wykresu? Obecnie udało mi się uchwycić zdarzenie kółka myszy, ale nie jestem pewien, jak napisać samą funkcję przerysowywania. Jakieś sugestie?

    var vis = d3.select("#graph")
        .append("svg:svg")
        .call(d3.behavior.zoom().on("zoom", redraw)) // <-- redraw function
        .attr("width", w)
        .attr("height", h);
Legenda
źródło
Zobacz także ten przykład thisismattmiller.com/blog/add-zoom-slider-to-d3-js autorstwa Matta Millera. Na końcu procesu dodaje tylko element „g”.
arivero
4
ktoś pokazał jak połączyć zui53 (bibliotekę dla interfejsów z możliwością powiększania) i d3js: bl.ocks.org/timelyportfolio/5149102
widged

Odpowiedzi:

97

Aktualizacja 6/4/14

Zobacz także odpowiedź Mike'a Bostocka na zmiany w D3 v.3 i powiązany przykład . Myślę, że to prawdopodobnie zastępuje odpowiedź poniżej.

Aktualizacja 18.02.2014

Myślę, że odpowiedź @ ahaarnos jest lepsza, jeśli chcesz przesuwać i powiększać cały plik SVG. Zagnieżdżone gelementy w mojej odpowiedzi poniżej są naprawdę konieczne tylko wtedy, gdy masz elementy nie powiększające się w tym samym SVG (nie tak w oryginalnym pytaniu). Jeśli zrobić zastosować zachowanie do gelementu, to tło rectjest wymagane lub podobny element, który zapewnia, że greaguje na zdarzenia wskaźnika.

Oryginalna odpowiedź

Mam to działające w oparciu o przykład zoom-pan-transform - możesz zobaczyć moje jsFiddle tutaj: http://jsfiddle.net/nrabinowitz/QMKm3/

Było to trochę bardziej złożone, niż się spodziewałem - musisz zagnieżdżać kilka gelementów, aby działał, ustawić pointer-eventsatrybut SVG na all, a następnie dołączyć prostokąt tła, aby otrzymać zdarzenia wskaźnika (w przeciwnym razie działa tylko wtedy, gdy wskaźnik jest nad węzeł lub łącze). redrawFunkcja ta jest stosunkowo prosta, wystarczy ustawienie transformacji na głębszą 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 + ")");
}

To skutecznie skaluje cały plik SVG, a więc skaluje również szerokość obrysu, podobnie jak powiększanie obrazu.

Jest inny przykład ilustrujący podobną technikę.

nrabinowitz
źródło
1
@Ogg - nie jestem pewien, co masz na myśli - jsFiddle po prostu prezentuje twoje wyniki w ramce iFrame, a nie w jakiejś niestandardowej przeglądarce, więc to, co widzisz, to prawdziwe zachowanie przeglądarki. jsFiddle dodaje kilka rzeczy, np. bodytag, więc polecam zajrzeć do źródła ramki i zobaczyć, czego brakuje.
nrabinowitz
2
@EricStob - to może być nowe pytanie. Ale zobacz jsfiddle.net/56RDx/2 - to po prostu przeskalowuje rozmiar czcionki przez odwrotność skali powiększenia.
nrabinowitz
1
@ajmartin - zobaczzoom.scaleExtent()
nrabinowitz
1
W przypadku korzystania z wersji 3 d3 możliwość przeciągania pojedynczego węzła nie działa w tym przykładzie. Zamiast tego przesuwa cały wykres tak, jakbyś nie kliknął węzła. Działa to w wersji 2, ale potrzebuję funkcji w wersji 3 . Jakieś pomysły?
Daryl Van Sittert
1
Oto rozwiązanie dla D3 v3: stackoverflow.com/questions/17953106/ ...
David Marx
18

Dlaczego zagnieżdżone <g>?

Poniższy kod działał dobrze dla mnie (tylko jeden <g>, bez losowego dużego białego <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 + ")");
}

Gdzie wszystkie elementy w svg są następnie dołączane do viselementu.

aaaronic
źródło
1
Może być tak, że stracisz atrybuty „viewBox”, „preserveAspectRatio” i „pointer-events”, a to nadal będzie działać?
notan3xit
@ notan3xit ma rację, viewBox, preserveAspectRatio i pointer-events nie są konieczne. Kluczem jest zastosowanie transformationatrybutu do gelementu, a nie do svgelementu.
Lekensteyn
Wydaje się, że nie działa z D3 v3, a raczej funkcja powiększania nadal działa, ale możliwość przenoszenia poszczególnych węzłów zostaje utracona. Rozwiązanie @nrabinowitz przedstawia ten sam problem. Oto skrzypce nrabinowitza zaktualizowane, aby używać rozwiązania ahaarnos : jsfiddle.net/QMKm3/716, a tutaj te same skrzypce zaktualizowane do użycia D3v3 w celu zilustrowania problemu: jsfiddle.net/QMKm3/717
David Marx
Idealny pomysł, aby dodać zachowanie zoomu do elementu SVG, nie wiedziałem, że możesz to zrobić i dlatego zawsze uciekałem się do używania irytujących prostokątów tła. Dodanie zachowania do SVG działa przynajmniej w nowoczesnych wersjach Chrome, FF i Opera.
rcijvat
14

Podane odpowiedzi działają w D3 v2, ale nie w v3. Zsyntetyzowałem odpowiedzi w czyste rozwiązanie i rozwiązałem problem z wersją 3, korzystając z odpowiedzi podanej tutaj: Dlaczego d3.js v3 przerywa mój wykres siły podczas wdrażania powiększania, a wersja 2 nie?

Najpierw główny kod. To jest poprawiona wersja odpowiedzi @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 + ")");
    }   

Teraz możesz przesuwać i powiększać, ale nie będziesz w stanie przeciągać węzłów, ponieważ funkcja przesuwania zastąpi funkcję przeciągania. Więc musimy to zrobić:

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

Oto skrzypce @nrabinowitz zmodyfikowane tak, aby korzystały z tej czystszej implementacji powiększenia, ale ilustrują, jak D3v3 przerywa przeciąganie węzłów: http://jsfiddle.net/QMKm3/718/

A oto te same skrzypce zmodyfikowane do pracy z D3v3: http://jsfiddle.net/QMKm3/719/

David Marx
źródło
2

Mój wykres działał bez drugiego dodania „svg: g”.

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

Reszta jest taka sama.

cem
źródło
ale bez prostokąta: nie można przesuwać (tylko powiększać)
user1043144
0

Dostałem rozwiązanie dla wykresu zorientowanego siłą D3 z opcją powiększania.

    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
źródło
0

Jeśli chcesz powiększyć i przesunąć układ siły bez zmiany rozmiaru węzła, spróbuj poniżej. Możesz także przeciągać węzły bez drżenia. Ten kod jest oparty na oryginalnym przykładzie układu siły. Jeśli chodzi o dane dotyczące węzłów i łączy, zapoznaj się z oryginalnymi przykładowymi danymi. http://bl.ocks.org/mbostock/4062045

Proszę zwrócić uwagę na zmienne xScale i yScale, funkcje dragstarted (), dragged () i dragended (). Funkcja tick () również została zmieniona.

Możesz zobaczyć wynik na http://steelblue.tistory.com/9 Językiem na tej stronie jest koreański. Jednak wynik można łatwo znaleźć w trzecim przykładzie na stronie.

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
źródło