简体   繁体   中英

Using D3, can semantic zoom be applied to a radial tree?

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

Because of the different way dx and dy 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.

(PS 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 dx 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 dy 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 dy 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(" + (dx - 90)
    + ")translate(" + radialScale(dy) + ")"; })

  • 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!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM