I'm trying to apply the fisheye effect to a force layout with collision detection, node dragging, zoom etc but am struggling to integrate a fisheye with my tick function.
The closest existing example I found was http://bl.ocks.org/bentwonk/2514276 but it doesn't really contain a custom tick function or enable the usual things like dragging a node while fisheye is applied. For the sake of clarity, here's my tick function regardless of fisheye...
function tick(e) {
link.attr("x1", function(d) { return d.source.x;})
.attr("y1", function(d) { return d.source.y;})
.attr("x2", function(d) { return d.target.x;})
.attr("y2", function(d) { return d.target.y;})
node.each(gravity(.2 * e.alpha))
.each(collide(jitter))
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
And here's what I'm hoping to use on my svg listener to trigger fisheye...
svg.on("mousemove", function() {
fisheye.focus(d3.mouse(this));
node
.each(function(d) { d.fisheye = fisheye(d); })
.attr("cx", function(d) { return d.fisheye.x; })
.attr("cy", function(d) { return d.fisheye.y; })
.attr("r", function(d) { return d.fisheye.z * 8; });
link.attr("x1", function(d) { return d.source.fisheye.x; })
.attr("y1", function(d) { return d.source.fisheye.y; })
.attr("x2", function(d) { return d.target.fisheye.x; })
.attr("y2", function(d) { return d.target.fisheye.y; });
});
But when using both together I notice no fisheye effect - presumably tick() is overriding any changes from my svg listener (although even if I add force.stop() to my svg listener I still see no fisheye on the page or any console errors).
And of course if I replace the tick function, then the whole node layout won't compute properly to start with.
Apologies that I don't have a more specific question, but does anyone have any thoughts on the best way to approach combining these 2 behaviours so that I can use a fisheye effect without compromising on other force layout functionality?
Thanks!
EDIT:
My only thought so far is to simply use the svg listener to trigger the mouse position...
svg.on("mousemove", function() {
//force.stop();
fisheye.focus(d3.mouse(this));
});
And then have some kind of coordinate addition in the tick function...
node.each(gravity(.2 * e.alpha))
.each(collide(jitter))
.each(function(d) { d.fisheye = fisheye(d); })
.attr("r", function(d) { return d.fisheye.z * 8; })
.attr("transform", function(d) {
return "translate(" + d.x + d.fisheye.x + "," + d.y + d.fisheye.y + ")";
});
(the above doesn't work though - just produces invalid coordinates so all nodes end up at the top-left of the screen)
Related
I have been trying to append a set of SVGs inside SVG. So, in inside SVG, I want to create a function to make plot. However, this doesn't work in the way I expected as the inside SVGs don't have the dimension according to my specification. So, I'd like to know what went wrong.
svg_g_g = svg_g.append("g")
.selectAll("g")
.data(data)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate(" + xScale(d.key) + "," + lift + ")"
})
.append("svg")
.attr("width", function(d) { return xScale(d.key) })
.attr("height", height)
.append("line")
.style("stroke", "black")
.attr("x1", function(d) { return xScale(d.key) })
.attr("y1", height-100)
.attr("x2", function(d) { return xScale(d.key) + 50 } )
.attr("y2", height-100);
This is the output.
While if I bind data to g (without appending svg), the result looks more promising.
svg_g_g = svg_g.append("g");
svg_g_g.selectAll("line")
.data(data)
.enter()
.append("line")
.style("stroke", "black")
.attr("x1", function(d) { return xScale(d.key) })
.attr("y1", height-100)
.attr("x2", function(d) { return xScale(d.key) + 50 } )
.attr("y2", height-100);
and this is the result. (notice: the lines were shown in this case)
Any help will be appreciated.
edit: what I intended to do was I wanted to create a box-plot, I used "iris" dataset here as I can compared it against other data visualisation libraries. The current state I tried to create a SVG with an equal size for each category which I would use to contain box-plot. In other words, after I can create internal SVGs successfully, I will call a function to make the plot from there. Kinda same idea Mike did here.
I have a force network something similar to this :
http://jsfiddle.net/Brb29/7/
On the button press i translate the nodes to the same position :
function positionnodes(){
force.stop();
nodes.each(function(d){
d.fixed = true;
d.x = 100;
d.y = 100;
}).transition().duration(1000).attr("transform", function(d)
{
//console.log(d.hasRelationship);
//console.log(d.y);
return "translate(" + 100 + "," + 100 + ")";
});
edges.transition().duration(1000).attr("x1", function (d) {
console.log(d.source.x)
return d.source.x;
})
.attr("y1", function (d) {console.log(d.source.y)
return d.source.y;
})
.attr("x2", function (d) {console.log(d.target.x)
return d.target.x;
})
.attr("y2", function (d) {console.log(d.target.y)
return d.target.y;
});
//setTimeout(function(){ force.start();},1000);
}
My problem is, after i do this and i go to drag the nodes, the node position jumps back to its previous even though i have set it. It's as if the d.x/d.y hasn't updated.
Any ideas ?
The drag event will trigger force.start and then (x,y) of all nodes will be re-computed. To avoid triggering force.start, you need overwrite the event handler for force.drag, see
here
OK. Here is a working example: Working Example. The core part is here:
var drag = force.drag()
.origin(function(d) {
var t =
d3.transform(d3.select(this).attr("transform")).translate;
return {x: t[0], y: t[1]};
})
.on("drag.force", function(d) {
var cord = [0,0];
cord = d3.mouse(this);
d.x = cord[0];
d.y = cord[1];
tick();
});
I've got a scatter plot chart that consists of several hundred points, each point represented by an image. The coordinates as well as the image filenames are loaded via AJAX. It works as intended, but with this approach every single image is loaded by itself, resulting in several hundred requests. This is the code I'm using right now and that's working fine:
function position(dot) {
dot .attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.attr("width", function(d) { return d.r * 5; })
.attr("height", function(d) { return d.r * 10; })
.attr("xlink:href", function(d) { return d.ImageFile })
;
}
So, to speed things up, I dynamically create a sprite image file on the server. This also works well. But I can't find out how to load that sprite and access the single images within that sprite. I came across clipPath, but I'm obviously using it incorrectly:
function position(dot) {
dot .attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.attr("width", function(d) { return d.r * 5; })
.attr("height", function(d) { return d.r * 10; })
.attr("clipPath", function(d) { return d.i*140 + " 0 140 200"; })
.attr("xlink:href", function(d) { return 'mySprite.jpg'; })
;
}
So, how can I use a sprite image created dynamically on the server within my D3.js code?
My overall code is somewhat based on this example: https://github.com/mbostock/bost.ocks.org/blob/gh-pages/mike/nations/index.html
I'm workng with d3.js and don't haveso many experiences with it. I have a grouped bar chart with the values written on each bar (horizontal). How can I rotate them so that they are vertical (rotated 90°)? My code:
bars.append("text")
.attr("x", function(d) { return x(d.name); })
.attr("y", function(d) { return y0(d.value1) + 3; })
.attr("dy", ".75em")
.text(function(d) { return d.value1; });
bars.append("text")
.attr("x", function(d) { return x(d.name) + barWidth / (2.25); })
.attr("y", function(d) { return y1(d.value2) + 3; })
.attr("dy", ".75em")
.text(function(d) { return d.value2; });
bars.append("text")
.attr("x", function(d) { return x(d.name) + barWidth / (4.0); })
.attr("y", function(d) { return y2(d.value3) + 3; })
.attr("dy", ".75em")
.text(function(d) { return d.value3; });
bars.append("text")
.attr("x", function(d) { return x(d.name) + barWidth / (1.5); })
.attr("y", function(d) { return y3(d.value4) + 3; })
.attr("dy", ".75em")
.text(function(d) { return d.value4; });
Take a look at my answer to a similar question.
I believe you are coming across the same problem as described in the linked answer (and in the question itself) - your rotation is around a corner of a bar, not around the center! And that's why labels disappear!
Just want to highlight what was the solution to the linked problem: use a composite transformation that consists of a translation and a rotation. The code from example looks like this: (and yours should look similar; you just have to understand the code from linked jsfiddles before you apply the same strategy to your situation)
node.selectAll("text.nodeValue")
.text(function (d) { return formatNumber(d.value); })
.attr("text-anchor", "middle")
.attr("transform", function (d) {
return "rotate(-90) translate(" +
(-d.dy / 2) +
", " +
(sankey.nodeWidth() / 2 + 5) +
")";});
Before/after pictures:
BEFORE
AFTER
In svg it is quite easy to move/rotate an element using the transform attribute. Hence you can do (as suggested by Lars Kotthoff):
bars.selectAll("text").attr("transform","rotate(90)")
Also, from the code you posted, I would recommend you to have a look at selections in order to not generate each bar legend by hand. This tutorial can help you for this: Let's Make a Bar Chart
so, here I am with another of my questions:
I have a network set up in D3. Its nodes are all unfixed but one. All unfixed can be dragged wherever you want, but within the svg. To provide this I test the new coordinates at the end of the script. But if I move the fixed node all the other are dragged together with this one.
My problem is, that the nodes seem to get their coordinates relative to the parent object, which, in this case, is a g-Elements that provides the group-dragging. So after dragging they still have the restrictions, but they are shifted together with the group. Any useful hints welcome :)
And here are parts of my code:
var node = group.selectAll(".node")
.data(reqNodes)
.enter().append("g")
.attr("transform", function(d) { return "translate(0,0)"; })
.attr("class", function(d) {
return d.fixed ? 'node fixed' : 'node not-fixed';
})
var CurrentGTransformX = 0;
var CurrentGTransformY = 0;
group.selectAll('.not-fixed')
.call(force.drag);
// attach a different drag handler to the fixed node
var groupDrag = d3.behavior.drag()
.on("drag", function(d) {
// mouse pos offset by starting node pos
var x = window.event.clientX - 420,
y = window.event.clientY - 260;
group.attr("transform", function(d) { return "translate(" + x + "," + y + ")"; });
//that was the line getting in conflict with the part in the end
CurrentGTransformX = x;
CurrentGTransformY = y;
});
group.call(groupDrag);
force.on("tick", function() {
node.attr("cx", function(d) { return d.x = Math.max(15, Math.min(width - 15, d.x)); })
.attr("cy", function(d) { return d.y = Math.max(15, Math.min(height - 15, d.y)); });
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
});
edit: the transformation of the group is the reason why the restrictions in the end get other data than displayed. Any idea how to serve both functions in the proper way?