2

I am using the D3 node link tree, and I am having trouble trying to get semantic zoom applied to it.

I have already spent a bit of time fussing, trying to get it to work - so I thought I would ask here, to see if it is even possible before I spend more time... I am not sure if semantic zoom is a linear-only sort of thing.

Edit - Working Solution

Here is my working solution. I didnt clean up the code here - but it should give you an idea.

http://codepen.io/toadums/pen/wjovC

Toadums
  • 2,772
  • 8
  • 44
  • 67
  • 1
    Have you seen [this question](http://stackoverflow.com/questions/14666543/d3-js-semantic-zoom-and-pan-example-not-working)? – Lars Kotthoff Jan 13 '14 at 19:51
  • Yeah, @LarsKotthoff, I have read that one. It is easy for me to implement on this sort of tree (I call it linear..). But when I try it on the radial tree, it goes to hell..Thansk! – Toadums Jan 13 '14 at 19:57
  • 1
    Could you make a jsfiddle or something like that which demonstrates the problem you're experiencing? – Lars Kotthoff Jan 13 '14 at 20:01
  • I posted a link to a **Javascript version** of this solution in the comment section of the answer below. – FernOfTheAndes Jan 14 '14 at 11:51

1 Answers1

7

Because of the different way d.x and d.y are interpreted in the radial tree, the simple "semantic zoom" approach, where you let the behaviour object and the scales do all the calculations, doesn't work.

The (x,y) coordinates calculated by the tree layout are used to plot angles and distance from the centre in a radial tree. Those values don't directly convert into the x and y coordinates that the zoom behaviour creates. So you can't just attach your scales to your zoom behaviour, have the scale domain automatically adjusted on zoom, and then replot your points using the scales.

(P.S. I'm assuming you just want a normal zoom to magnify a part of the graph, and not a re-calculation of angles like Jason Davies' zoomable sunburst example.)

A recap of how your elements are transformed for the initial layout:

  • A translate on the <g> element that contains the graph positions the (0,0) coordinate in the centre of the plotting area.

  • A rotation on each node group, calculated based on the d.x value from the layout (which is always between 0 and 360), sets the angle.

  • And then (order is important), a translate on each node group moves it away from the centre, along the rotated baseline, according to the d.y value from the layout which is scaled based on the desired radius of the circle.

Note that the radial tree example does all the scaling inside the tree layout function (by calling the size() method) -- no scales are used.

In order to zoom in on a section of the circle, you are going to need to:

  • Translate the centre of the layout to somewhere other than the centre of the plotting by applying a transform to the <g> element.

  • Either Change the scale of the entire image (this will also scale text, circle size, etc.) with a second transform on the <g> element

  • or change the horizontal translation of each node according to the scale factor.

Note that you leave the rotational angle of the nodes alone!

If you don't mind scaling text and the size of your nodes, applying the transforms to the <g> element is fairly straightforward.

If you want to zoom the layout without increasing the size of the individual elements, you're going to need to change how the distance from the centre of the layout is calculated.

To get the translation of the centre correct:

  • In your zoom function, use d3.event.translate to access the (x,y) array of the desired translation for the entire graph.

  • Use this to over-ride the transform on the <g> element, remembering that for a zoom translation of (0,0) you still want the <g> element to have a translation of (width/2, height/2). So you need something like:

    svg.attr("transform", "translate(" + (width/2 + d3.event.translate[0]) +
    "," + (height/2 + d3.event.translate[1]) + ")" );

  • If you were going to scale the <g> element directly, you would have to set both transformations in the same attribute call, as one long string, and you would want to do some tests to see if it worked more naturally to apply the scale before or after the translation (scale(2) translate(50) is equal to translate(100) scale(2), because the translation coordinates get scaled, too). The scale factor is available as d3.event.scale.

To get the scaling of the distance from the centre ("y" variable) correct:

  • Create a radial scale with domain [0,1] and range [0,radius].

  • Set the size of the tree layout to have "height" of 1 instead of using the radius directly.

  • When positioning your nodes, use the scale to convert the d.y value from the layout (which will be between 0 and 1) into the actual radial distance used in the transformation.

    node.attr("transform", function(d) { return "rotate(" + (d.x - 90)
    + ")translate(" + radialScale(d.y) + ")"; })

  • On your zoom behaviour, don't attach any scales! Although you'll need to adjust the radial scale based on zooming, you only want to adjust the scale and not translate it as well -- all translations will be applied directly to the <g> element that defines the centre of the circle.

  • In your zoom function, set the domain of the radial scale to [0, 1/d3.event.scale]. In other words, if the zoom behaviour tells us to scale the image by 2, we want the distance between the centre and edge of the plotting area (the range, which you don't change) to represent 1/2 the distance between the centre and the edge of the tree (the domain).

  • Then, reset the transformation attribute on the individual node groups using the same syntax as above. (You'll want to make that anonymous function a named function so you can just pass in the function name in both parts of your code.)

So, I don't know if all that extra code counts as "semantic zoom" any more. But it should zoom your graph. If you get your fiddle working, come back and leave a link!

AmeliaBR
  • 27,344
  • 6
  • 86
  • 119
  • Thank you again SO MUCH for your help. I don't think I would have gotten it working without it! I posted my solution up top if you want to have a look :) THANKS AGAIN TIMES A MILLION!!!!!! – Toadums Jan 14 '14 at 02:00
  • @Toadums Glad it worked -- your demo looks great. One of those times where you had to step away from the "almost right but not quite" examples and figure it out from scratch. – AmeliaBR Jan 14 '14 at 05:19
  • @AmeliaBR - such great explanations indeed. I was wondering if you had seen this question I posed a little while ago [here](http://stackoverflow.com/questions/20840226/transformation-issues-when-implementing-fisheye-distortion-in-a-radial-tree). Any thoughts? – FernOfTheAndes Jan 14 '14 at 08:43
  • 1
    In the meantime, the **JAVASCRIPT version** of the solution above is in this [fiddle](http://jsfiddle.net/Nivaldo/SW76w/). – FernOfTheAndes Jan 14 '14 at 11:47