Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

D3 Zoom with Scrollbars used as panning, Scrollbar width and height adjusts to zoom scale

I want to create the following:

  1. Make a dynamic graph

  2. It is Zoomable (Zooms at the center of currently seen display) (Zoom when certain buttons are clicked, mouse wheel are disabled for zoom)

  3. Elements are draggable (When dragged it is not affected by force graph arrangement) (When elements are dragged outside of the svg the svg grows in size)

  4. It has Scrollbar used as pan

So far I am already successful with

  1. Creating a force graph
  2. Creating zoom
  3. Elements are already draggable and not included in force after dragged
  4. Scrollbar also

I have two problems with these combination items:

  1. Having dragged elements, it is not included in force graph anymore. Which would lead to possible overlap of other elements if new ones.
  2. Scrollbar with zoom is not working wonders, when you zoom->scroll->zoom it zooms at the old location where the first zoom happened.

I would really need help for these two problems. I have not seen any example for zoom and scrollbar combination.

Here is the code.

function drawGraph(Data){
setDefault();

svg = d3.select("#graphingArea").append("svg")
  .attr("width", width + margin.left + margin.right)
  .attr("height", height + margin.top + margin.bottom)
  .call(zoom)
  .on("dblclick.zoom", false)
  .on("mousewheel.zoom", false)
  .on("DOMMouseScroll.zoom", false) // disables older versions of Firefox
  .on("wheel.zoom", false); // disables newer versions of Firefox;

//Needed for canvas to be dragged
rect = svg.append("rect")
  .attr("width", width)
  .attr("height", height)
  .style("fill", "none")
  .style("pointer-events", "all");

//Holds all that is to be dragged by the canvas
container = svg.append("g");

//Call zoom before drawing
svg.call(zoomUpdate);

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

//Creating data that is drawn
populateD3Data(container, drag);

// Set data to be Force Arranged
force = self.force = d3.layout.force()
.nodes(nodes)
.links(links)
.distance(150)
.charge(-1000)
.size([width,height])
.start();

//Event to call arrange
force.on("tick", tick);
}

Zooming js:

var zoom = d3.behavior.zoom()
    .scaleExtent([zoom_min_scale, zoom_max_scale])
    .on("zoom", zoomed);

function zoomed() {
  if(container != null && container != undefined) {
      var translate = zoom.translate(),
      scale = zoom.scale();

      tx = Math.min(0, Math.max(width * (1 - scale), translate[0]));
      ty = Math.min(0, Math.max(height * (1 - scale), translate[1]));

      zoom.translate([tx, ty]);
      container.attr("transform", "translate(" + [0,0] + ")scale(" + zoom.scale() + ")");

      svg.attr("width", AreaWidth_ * zoom.scale());
      svg.attr("height", AreaHeight_ * zoom.scale());

      $("#graphingArea").scrollLeft(Math.abs(zoom.translate()[0]));
      $("#graphingArea").scrollTop(Math.abs(zoom.translate()[1]));
  }
}

//Button event for zoom in
d3.select("#zoom_in")
    .on("click", zoomInOrOut);

//Button event for zoom out
d3.select("#zoom_out")
    .on("click", zoomInOrOut);

//Gets the center of currently seen display
function interpolateZoom (translate, scale) {
    return d3.transition().duration(1).tween("zoom", function () {
        var iTranslate = d3.interpolate(zoom.translate(), translate),
            iScale = d3.interpolate(zoom.scale(), scale);
        return function (t) {
            zoom
                //Round number to nearest int because expected scale for now is whole number
                .scale(Math.floor(iScale(t)))
                .translate(iTranslate(t));
            zoomed();
        };
    });
}

function zoomInOrOut() {
    var direction = 1,
        target_zoom = 1,
        center = [graph_area_width / 2, graph_area_height / 2],
        extent = zoom.scaleExtent(),
        translate = zoom.translate(),
        translate0 = [],
        l = [],
        view = {x: translate[0], y: translate[1], k: zoom.scale()};

    d3.event.preventDefault();
    direction = (this.id === 'zoom_in') ? 1 : -1;
    target_zoom = zoom.scale() + (direction * zoom_scale);

    if (target_zoom < extent[0] || target_zoom > extent[1]) { return false; }

    translate0 = [(center[0] - view.x) / view.k, (center[1] - view.y) / view.k];
    view.k = target_zoom;
    l = [translate0[0] * view.k + view.x, translate0[1] * view.k + view.y];

    view.x += center[0] - l[0];
    view.y += center[1] - l[1];

    interpolateZoom([view.x, view.y], view.k);
}

function zoomUpdate() {
    var target_zoom = 1,
    center = [graph_area_width / 2, graph_area_height / 2],
    extent = zoom.scaleExtent(),
    translate = zoom.translate(),
    translate0 = [],
    l = [],
    view = {x: translate[0], y: translate[1], k: zoom.scale()};

    target_zoom = zoom.scale();

    if (target_zoom < extent[0] || target_zoom > extent[1]) { return false; }

    translate0 = [(center[0] - view.x) / view.k, (center[1] - view.y) / view.k];
    view.k = target_zoom;
    l = [translate0[0] * view.k + view.x, translate0[1] * view.k + view.y];

    view.x += center[0] - l[0];
    view.y += center[1] - l[1];

    interpolateZoom([view.x, view.y], view.k);
}
like image 677
erwin lim Avatar asked Oct 31 '22 16:10

erwin lim


1 Answers

Here is my take on combining d3-zoom with scrollbars: https://stackblitz.com/edit/d3-pan-and-zoom

Apart from handling d3's zoom to update scrollbar positions, you also need to handle scrolling with scrollbars to update d3's internal zoom representation by calling translateTo().

like image 175
PeWu Avatar answered Nov 09 '22 07:11

PeWu