Force layout node rotation - d3.js

Ok, first - look at this fiddle.
You should see shapes rotating back and forth like crazy.
This is what is going on:
force.on("tick", function(e) {
vis.selectAll("path")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"
// this is the thing
+"rotate(" + Math.random() * 50 + ")";
});
});
On every tick I'm changing the transform: rotate() to Math.random() * 50 in this case.
Now what I want is a smooth rotation. Not this jerky stuff.
See this to better understand what I mean. Imagine the height as the rotation. The gray box represents what I have now, the blue - what I want.
I tried applying 'transition: all 1s ease' CSS to that element, but it just ignores it, I'm obviously doing it wrong.
So how do I make this infinite back and forth rotation smooth as if I was using CSS3 transitions?

Every tick you are randomly setting the rotation to something between 0 and 50 degrees of rotation. You need to maintain the current rotation, calculate an offset, and then set the rotation to the current + offset.
Here's an updated tick function:
force.on("tick", function(e) {
vis.selectAll("path")
.attr("transform", function(d) {
if(!d.rotate) {
d.rotate = Math.random() * 50;
} else {
d.rotate = d.rotate + 1;
}
return "translate(" + d.x + "," + d.y + ")"
+"rotate(" + d.rotate + ")";
});
});
Here's the updated working example: https://jsfiddle.net/1aLc7x4j/

Related

How to move a point from d3 arc start to end without wrapping?

I am attempting to use d3.js to move a point along an arc from 0 to PI, say, without the point moving back along the innerRadius as seen here http://bl.ocks.org/mbostock/1705868.
I removed innerRadius hoping (unsuccessfully) that would work (http://jsfiddle.net/klin/23c5476v/). I had also tried setting the innerRadius with the same value as outerRadius.
Fragment I changed (changes marked with //) ...
var path = svg.append("svg:path")
.datum({endAngle: Math.PI}) //
.attr("d", d3.svg.arc()
// .innerRadius(h / 4) // Hoping removal would prevent inner transition
.outerRadius(h / 3)
.startAngle(0)
);//.endAngle(Math.PI));
Entire code ...
var svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
var path = svg.append("svg:path")
.datum({endAngle: Math.PI}) //
.attr("d", d3.svg.arc()
// .innerRadius(h / 4) // Hoping removal would prevent inner transition
.outerRadius(h / 3)
.startAngle(0)
);//.endAngle(Math.PI));
var circle = svg.append("svg:circle")
.attr("r", 6.5)
.attr("transform", "translate(0," + -h / 3 + ")");
function transition() {
circle.transition()
.duration(5000)
.attrTween("transform", translateAlong(path.node()))
.each("end", transition);
}
transition();
// Returns an attrTween for translating along the specified path element.
function translateAlong(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
};
};
}
The problem I think is that the arc shape has area, so the path must be closed, while the line shape does not. Eventually I'd like to be able to separately animate object movement along a series of consecutive arcs similar to the answer to Interpolating along consecutive paths with D3.js, but first I need to avoid the loop back movement.
Is a simple solution maybe to not use d3's arc generator, but instead use another where the end point actually is the terminus of the path?
Paul is right.
You can do next
var arc = d3.svg.arc(); //plus params
$path.attr('d',function(){
var d = arc();
return d.split('L')[0]; //will return half of arc without lines
});

How to make d3 milestone Shape

I'm trying to align a down triangle with a rectangle to make a group that can be used to represent a milestone. Any ideas why this code only shows 2 triangles and how to move them to centre bottom of rectangle or any other methods to achieve the same goal?
http://jsfiddle.net/sjp700/Pej4M/
tri.enter().append("path")
.attr("d", d3.svg.symbol().type("triangle-down"))
.style("fill", "black")
.attr("transform", function (d) { return "translate(" + xRange(d.start) + "," + yRange(d.Duration) + ")"; });
As pointed out in the comments, the reason you're seeing only two rectangles is that some of the data is bound to existing paths. To fix, assign a special class to the symbols that you can select by:
var tri = vis.selectAll("path.tri").data(datar);
For the positioning of the symbols, you need to use the same values you use for the rectangles. The y position needs to be offset by a constant so that the symbols appear at the bottom and the x position by half the duration -- I'm guessing that this is what you really want to show as you're currrently hardcoding everything to length 50.
.attr("transform", function (d) { return "translate(" + (xRange(d.start) + 25) + "," + (yRange(d.start) + 15) + ")"; });
Complete demo here.

Using the zoom and pan functionality of d3

I'm trying to use the pan/zoom ability of d3 to draw boxes on the screen so that when you click on a box a new box appears and shifts the rest of the boxes to the right so that the new box is on the center of the canvas. The panning would allow me to scroll through all the boxes I've drawn.
Here is my jsfiddle: http://jsfiddle.net/uUTBE/1/
And here is my code for initializing the zoom/pan:
svg.call(d3.behavior.zoom().on("zoom", redraw));
function redraw() {
d3.select(".canvas").attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
And here is my code for drawing the boxes:
function drawBox(x, y) {
var boxGroup = canvas.append("g");
boxGroup.append("rect")
.attr("x", x)
.attr("y", y)
.attr("height", 100)
.attr("width", 100)
.attr("fill", function () {
var i = Math.floor(Math.random() * 4);
if (i === 1) return "red";
else if (i === 2) return "blue";
else if (i === 3) return "yellow";
else return "green";
})
.on("click", function () {
counter++;
d3.select(".canvas")
.transition()
.duration(1000)
.attr('transform', "translate(300,0)");
drawBox(x - counter * 120, y);
});
}
I have multiple problems with this fiddle, but two of my main concerns is:
1) How do I make it so that when I click on a new box a second time the boxes move accordingly (i.e. when I click on the box initially the old box shifts to the right and a new box appears, but when I click on the new box, the older boxes doesnn't shift to the right).
2)Why is it that when I click on the new box, the newer box has a big spacing between it? (only happens after trying to put 3 boxes on the screen).
Thanks any hints are appreciated!
I think there's some confusion here around transform. The transform attribute is static, not cumulative, for a single element - so setting .attr('transform', "translate(300,0)") more than once will have no effect after the first time. It also looks like your placement logic for the new boxes is off.
The positioning logic required here is pretty straightforward if you take a step back (assuming I understand what you're trying to do):
Every time a new box is added, the frame all boxes are in moves right 120px, so it needs a x-translation of 120 * counter.
New boxes need to be offset from the new frame position, so they need an x setting of -120 * counter.
Zoom needs to take the current canvas offset into account.
(1) above can be done in your click handler:
canvas
.transition()
.duration(1000)
.attr('transform', "translate(" + (offset * counter) + ",0)");
(2) is pretty easily applied to the g element you're wrapping boxes in:
var boxGroup = canvas.append("g")
.attr('transform', 'translate(' + (-offset * counter) + ',0)');
(3) can be added to your redraw handler:
function redraw() {
var translation = d3.event.translate,
newx = translation[0] + offset * counter,
newy = translation[1];
canvas.attr("transform",
"translate(" + newx + "," + newy + ")" + " scale(" + d3.event.scale + ")");
}
See the working fiddle here: http://jsfiddle.net/nrabinowitz/p3m8A/

d3 on brush triangle symbols disappears

The grey strip in the lower area allows the user to spread the data across x axis .fiddle Everything is resizing apart from the triangle symbols which are generated initially but disappear as soon as I redraw using the brush (ps: this happen only when i run the code from the same code from the browser and not jsfiddle!)
My axis increases in length but 0min is repeated 3 to 4 times instead of showing 0.1 0.2 0.3 etc
milestone_marks.selectAll(".milestone_markers")
.data(milestones)
.enter().append("path")
.attr("d", d3.svg.symbol().type('triangle-down'))
.attr('class', 'milestone_markers')
.attr("transform", function (d) {
return "translate(" + x__axis(d.processing_time) + "," + -7 + ")";})
and in the brushed function I write the following:
d3.select('.milestone_marks').selectAll('path').data(milestones).attr("d", d3.svg.symbol().type('triangle-down')).attr("transform", function (d) {
return "translate(" + x__axis(d.processing_time) + "," + -7 + ")";})
Any inputs will be appreciated.

Arc Based Text Alignment in D3

I'm trying to create a Radial Hub and Spoke diagram (http://bl.ocks.org/3169420) with D3 that uses arcs of a pie to represent relationships to and/or from a node of context. In other words, I'm trying to create a 360 degree view of a node's relationships.
In the example above, "Node 1" is in the center. I can't get the textual node names for related nodes #8 - #18 to align "outside" the wheel. Would anyone know what the best way is to do so?
The code that I'm using looks as follows...
// Add node names to the arcs, translated to the arc centroid and rotated.
arcs3.filter(function(d) {
return d.endAngle - d.startAngle > .2; }).append("svg:text")
.attr("dy", "5px")
.attr("angle", function(d) { return angle(d); })
.attr("text-anchor", function(d) { return angle(d) < 0 ? "start" : "end"; })
.attr("transform", function(d) { //set text'ss origin to the centroid
// Make sure to set these before calling arc.centroid
d.innerRadius = (height/2); // Set Inner Coordinate
d.outerRadius = (height/2); // Set Outer Coordinate
return "translate(" + arc3.centroid(d) + ")rotate(" + angle(d) + ")";
})
.style("fill", "Black")
.style("font", "normal 12px Arial")
.text(function(d) { return d.data.name; });
// Computes the angle of an arc, converting from radians to degrees.
function angle(d) {
var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
return a > 90 ? a - 180 : a;
}
The issue seems to be that I need to evaluate the X and Y coordinates of where the Node labels are placed. However, I can't seem to correctly find/access those X and Y values.
Thanks, in advance, for any help you can offer.
My Best,
Frank
Thanks to Ger Hobbelt for this... In short, the angle function needs to be parameterized:
function angle(d, offset, threshold) {
var a = (d.startAngle + d.endAngle) * 90 / Math.PI + offset;
return a > threshold ? a - 180 : a;
}
The complete answer can be found d3.js Google Group: https://groups.google.com/forum/?fromgroups#!topic/d3-js/08KVfNmlhRo
My Best,
Frank

Resources