Автоматическое масштабирование/масштабирование d3.js после загрузки

Я использую эту красивую силовую схему от Flowingdata.com для создания сетевой диаграммы.

enter image description here

enter image description here

enter image description here

В моей диаграмме в настоящее время отображается от 5 до 750 узлов с их отношениями. Он отлично работает с некоторыми пользовательскими изменениями, соответствующими моим потребностям. Как бы то ни было, я не могу работать. У меня есть viewBox с preserveAspectRatio для автоматической установки контейнера, в котором он находится. Но в зависимости от количества узлов всегда есть некоторые узлы вокруг краев (главным образом верх и buttom), которые обрезаются. И если узлов очень мало, они показывают их посередине с огромным пустым пространством вокруг него (это большой контейнер в нем).

Есть ли способ автоматического масштабирования или масштабирования макета для автоматической установки? Так что большой макет несколько уменьшился, а малый макет увеличился. У меня есть настройка события масштабирования, поэтому прокрутка и панорамирование работают как шарм. Но может ли он автоматически сделать это, чтобы соответствовать содержимому?

Код запуска d3.js:

        vis = d3.select(selection)
        .append("svg")
        .attr("viewBox", "0 0 " + width + " " + height )
        .attr("preserveAspectRatio", "xMidYMid meet")
        .attr("pointer-events", "all")
        .call(d3.behavior.zoom().scaleExtent([.1, 3])
                        .on("zoom", redraw)).append('g');

Ответ 1

Все остальные ответы на сегодняшний день требуют доступа к данным и повторяются через него, поэтому сложность не менее O(nodes). Я продолжал искать и нашел способ, основанный исключительно на визуализированном размере getBBox(), который, надеюсь, O(1). Неважно, что в нем или как оно выложено, просто его размер и размер родительского контейнера. Мне удалось взломать это на основе http://bl.ocks.org/mbostock/9656675:

var root = // any svg.select(...) that has a single node like a container group by #id

function lapsedZoomFit(ticks, transitionDuration) {
    for (var i = ticks || 100; i > 0; --i) force.tick();
    force.stop();
    zoomFit(transitionDuration);
}

function zoomFit(transitionDuration) {
    var bounds = root.node().getBBox();
    var parent = root.node().parentElement;
    var fullWidth = parent.clientWidth || parent.parentNode.clientWidth,
        fullHeight = parent.clientHeight || parent.parentNode.clientHeight;
    var width = bounds.width,
        height = bounds.height;
    var midX = bounds.x + width / 2,
        midY = bounds.y + height / 2;
    if (width == 0 || height == 0) return; // nothing to fit
    var scale = 0.85 / Math.max(width / fullWidth, height / fullHeight);
    var translate = [fullWidth / 2 - scale * midX, fullHeight / 2 - scale * midY];

    console.trace("zoomFit", translate, scale);

    root
        .transition()
        .duration(transitionDuration || 0) // milliseconds
        .call(zoom.translate(translate).scale(scale).event);
}

Ответ 2

Вы можете выполнять итерацию по узлам; получить значения max и min x и y для всех узлов и рассчитать необходимый масштаб и перевод, чтобы охватить все ваши данные в размере SVG.

У меня есть фрагмент кода, который центрирует график для данного node; это может помочь вам получить представление.

zs = zoom.scale()
zt = zoom.translate();
dx = (w/2.0/zs) - d.x;
dy = (h/2.0/zs) - d.y;
zoom.translate([dx, dy]);
zoom.scale(zs);

Где w, h - ширина и ширина моего холста SVG, а d node Я хочу сосредоточиться.

Вместо центрирования на d.x, d.y вы должны вычислить средние x и y. И вычислите масштаб масштабирования, который сделает ширину (и высоту) вашего графика в соответствии с шириной и размером SVG. Увеличьте масштаб изображения.

Ответ 3

Ваш код должен быть похож на этот

   vis = d3.select(selection)
        .append("svg")
        .attr("viewBox", "0 0 " + width + " " + height )
        .attr("preserveAspectRatio", "xMidYMid meet")
        .attr("pointer-events", "all")
        .call(zoomListener)
        .on("zoom", redraw));

   var mainGroup = vis.append('g');

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

    var zoomListener = d3.behavior.zoom().on("zoom", zoom);

    var xArray = YOUR_NODES.map(function(d) { //YOUR_NODES is something like json.nodes after forse.end()
        return d.x
    });

    var minX = d3.min(xArray);
    var maxX = d3.max(xArray);

    var scaleMin = Math.abs(width / (maxX - minX));
    var startX = (minX) / scaleMin;
    var startY = 50  / scaleMin;

    // Same as in the zoom function
    mainGroup.attr("transform", "translate(" + [startX, startY] + ")scale(" + scaleMin + ")");

    // Initialization start param of zoomListener
    zoomListener.translate([startX, startY]);
    zoomListener.scale(scaleMin);
    zoomListener.scaleExtent([scaleMin, 1])
    vis.call(zoomListener);

Этот код работает только для xAxis. Потому что "глобальный круг" svg RX === RY. Если это не было для вас, вы можете добавить такую ​​же логику для yAxis var startY. Также вам нужно отрегулировать начальные координаты с учетом cr узлов окружности.

Ответ 4

Вот два примера:

Силовой макет с автоматическим масштабированием (холст):

https://vida.io/documents/pT3RDxrjKHLQ8CQxi

Силовая компоновка с автоматическим масштабированием (SVG)

https://vida.io/documents/9q6B62xQgnZcYYen2

Функция, выполняющая масштабирование и рисование для SVG:

function scaleAndDraw() {
  var xExtent = d3.extent(d3.values(nodes), function(n) { return n.x; }),
      yExtent = d3.extent(d3.values(nodes), function(n) { return n.y; });

  if ((xExtent[1] - xExtent[0]) > config.width) {
    scaleX = (xExtent[1] - xExtent[0]) / config.width;
    scaleY = (yExtent[1] - yExtent[0]) / config.height;

    scale = 1 / Math.max(scaleX, scaleY);

    translateX = Math.abs(xExtent[0]) * scale;
    translateY = Math.abs(yExtent[0]) * scale,
    svg.attr("transform", "translate(" +
      translateX + "," + translateY + ")" +
      " scale(" + scale + ")");
  }

  draw();
}