4

Basically, I am attempting to apply the d3 fisheye distortion algorithm to a radial tree. I believe the issues I am encountering revolve around the fact that the coords being fed to the fisheye distortion are the coords computed by the d3.layout.tree. But the actual coords have been adjusted by the g transform. So, the coords resulting from the fisheye distortion need to be adjusted back to the g transform.

For example:

// re-setting the projection according to fisheye coords
diagonal.projection(function(d) { d.fisheye = fisheye(d); return [d.fisheye.y, d.fisheye.x / 180 * Math.PI]; })

I have been attempting this...here is the fiddle.

I am somewhat close...help is appreciated.

FernOfTheAndes
  • 4,975
  • 2
  • 17
  • 25
  • By not setting the coordinates and letting the g-transform do its business, I was able to get closer to what I wanted. Mouse coords, however, are only active in quadrant IV (southeast) and when the distortion occurs, its radius appears greater than specified. Updated fiddle [here](http://jsfiddle.net/Nivaldo/7TPhq/4/). – FernOfTheAndes Dec 31 '13 at 18:40
  • I haven't looked at fisheye distortion too closely, but I suspect adapting it to the radial coordinates will be more complicated than just converting your radial coordinates to Cartesian (x,y) points using basic trigonometry when you position your nodes. – AmeliaBR Jan 14 '14 at 16:52
  • Thanks for looking @AmeliaBR. Yep, this one is not going to be a walk in the park. – FernOfTheAndes Jan 14 '14 at 19:02

1 Answers1

5

Following the direction I'd suggested in the comments, this is the result:

https://jsfiddle.net/xdk5ehcr/

Instead of using rotations and translations to position the nodes, I created two trigonometry-based functions to calculate horizontal and vertical position from the data (x,y) values, which are treated as polar coordinates.

Then I had to set the fisheye function to use my positioning functions as "accessor" functions instead of reading d.x and d.y directly. Unfortunately, the basic plug-in you were using for the fisheye didn't include a way to get and set x/y accessor functions, so I had to modify that too. I was surprised it wasn't already in the code; it's a standard functionality on most d3 layout objects.

(When I get github set up, I will have to make a pull request to add it in. I'll need to figure out how the fisheye scale/zoom function works, though -- I took that out of this example since you weren't using it.)

The positioning functions were as follows:

function getHPosition(d){
    //calculate the transformed (Cartesian) position (H, V)
    //(without fisheye effect)
    //from the polar coordinates (x,y) where 
    //x is the angle
    //y is the distance from (radius,radius)
    //See http://www.engineeringtoolbox.com/converting-cartesian-polar-coordinates-d_1347.html

    return (d.y)*Math.cos(d.x);
}
function getVPosition(d){
    return (d.y)*Math.sin(d.x);
};

The functions are used to set the original position of the nodes and links, and then once the fisheye kicks in it uses these functions internally, returning the results (with distortion if appropriate) as d.fisheye.x and d.fisheye.y.

For example, for links that means the projection setting the d3.svg.diagonal function like this for initialization:

var diagonal = d3.svg.diagonal()
    .projection(function(d) { 
        return [getHPosition(d), getVPosition(d)]; 
});

But like this for update:

diagonal.projection(function(d) { 
    d.fisheye = fisheye(d); 
    return [d.fisheye.x, d.fisheye.y]; 
});

There are a couple other little changes:

I simplified the dimensions of the plotting area a bit.

I added a background rectangle with pointer-events:all; so that the fisheye doesn't turn on and off as the mouse moves between nodes and empty background.

I didn't bother rotating the text (since the node groups are no longer rotating, it doesn't happen by default), but you could easily add in a rotate transformation on the individual text elements.

Finally, and this one stumped me for longer than I'd like to admit, the angles have to be in radians for the Javascript trig functions. Couldn't figure out why my layouts were so ugly, with overlapping lines. I thought it was something to do with switching between d3.svg.diagonal() and d3.svg.diagonal.radial() and spent a lot of time trying to do inverse-trig and all sorts of things...

Hat
  • 540
  • 8
  • 25
AmeliaBR
  • 27,344
  • 6
  • 86
  • 119
  • 2
    Here's [the version with rotated text](http://fiddle.jshell.net/7TPhq/8/). Turned out to be more complicated than I thought, since of course the CSS functions want angles in degrees, and you have to specify a centre of rotation other than (0,0) to not have the labels circle around the entire image. I ended up changing `d.x` back to degrees, and did the conversion to radians in the trig functions. A lot of code for ugly angled text -- I personally recommend just sticking with the horizontal text, anyway! – AmeliaBR Jan 14 '14 at 21:03
  • First of all, a **huge thanks!**. When you meet someone you care about today or tomorrow, please ask him/her to give you a warm hug for me! And your efforts at the rotated text version were certainly not in vain. One of the first things I did after seeing your response was to plug a huge set of data I have into both versions. After playing with the power and radius of the distortion, I concluded that the rotated text version scales better. – FernOfTheAndes Jan 14 '14 at 22:15
  • I ran out of word space in my previous comment. @AmeliaBR, I humbly suggest that you make this available at bl.ocks.org. Other folks with large datasets for the radial tree would appreciate this feature. Thanks again. – FernOfTheAndes Jan 14 '14 at 22:24
  • As I mentioned above, I'm been meaning to set up a github account. When I do, I'll bl.ock-ify this and a couple other of the more complex examples that currently only exist in fiddles linked to SO questions. – AmeliaBR Jan 14 '14 at 22:52
  • P.S. @FernOfTheAndes: If you're going with rotated text because of the size of your dataset, you might want to group the attribute statements for the text's x,y, and rotation into an `each()` call so that you can save the H and V positions in a variable and then reuse the value instead of recalculating it. – AmeliaBR Jan 14 '14 at 22:53