Simultaneous cursors - javascript

I'm trying to emulate this graph: http://www.nytimes.com/interactive/2012/11/30/us/tax-burden.html
Here is the bare-bones rendition: http://jsfiddle.net/jd5Ym/6/
I can't get the different cursors to each follow the data for their own city. I can only do one at a time. My code depends on this function:
function mousemove() {
// p is the fraction of a graph traversed. decimalize strips integers.
var p=decimilize((x0.rangeBand()-d3.mouse(this)[0]+margin.left)/(x0.rangeBand()));
var u=data[Math.round(data.length-p*data.length)];
var v=cities[1].values[Math.round(data.length-p*data.length)];
cursor.data(data).attr("transform", "translate(" + (x1(u.date)) +","+y(v.temperature)+")");
}
Where it says v=cities[1], the index decides which city's data to follow. I want it to index each city itself, but when I try using the function (d,i) {...} setup, it doesn't work out, and I tried appending the mousemovefunction within a transform attribute in the declaration of city, and that didn't work either.
I am a beginning programmer so maybe this is easy. The data structure and parsing come out of Mike Bostock's examples.

You should use selectAll('.cities').each(...) to step over all the cities and update their cursors independently.
function mousemove() {
// the proportion of the way across any given graph that the mouse is
var mouseX = d3.mouse(this)[0]
var graph_width = x0.rangeBand()
// the corresponding data
var index = Math.floor( ( mouseX / graph_width ) * data.length );
d3.selectAll('.city')
.each(function(d, i){
var u = cities[i].values[index];
d3.select(this).select('.cursor')
.attr('transform', 'translate(' + x1(u.date) + ',' + y(u.temperature) + ')')
})
}
See here for the full working example: http://jsfiddle.net/jd5Ym/9/

Related

How to move a node along a vector?

I have a bunch of nodes in a circle around a centre point. I got these positions by drawing arcs first then using the arcs [X,Y] position, populated an array which was used for the positions of the nodes. Using the forcelayout from the javascript library D3.
What I want to do now, if the nodes meet a certain criteria, for example, name starts with L, move them out to the outline of a bigger circle. I have made a simple diagram to explain.
I wish to be able to move from [X2,Y2] to [X3,Y3]. I labelled [X1,Y1] as I am sure you would need this to work out the vector from x1y2 to x2,y2 wish would then be used to calculate the movement along that vector, but I'm unsure how to do this movement
I don't know if the problem is still active, but I'll answer anyway. Since the Problem has a cylindrical symmetry it is best to use polar coordinates. So x,y become r,phi whereas r = sqrt(x^2+y^2) and phi=arctan(y/x). If you want to move a point X(r,phi) in the radial direction by lets say r' you do it by simple adding it to the existing radius. Thus X'=X(r+r',phi)
Here's the way I solved it. I had a variable moveOutso I could toggle between the original node position and the one I move to. So depending on the value of moveOut I alter the scale of movement away from center.
var thisNode = circleViewNode.filter(function(d){
//console.log(d)
return d.origin != 'EquivalenceSets' && d.hasRelationship != true;
});
thisNode.each(function(d){
thisNodeSize = d.thisRadius;
});
if(!moveOut){
thisScale = innerModelRadius - thisNodeSize*1.5;
moveOut = true;
} else {
thisScale = innerModelItemRadius + (outerModelItemRadius - innerModelItemRadius)/2;
moveOut = false;
}
thisNode.each(function(d){
//console.log(d);
var centerOfCircle = [width/2,height/2]; //get center
//var centerOfCircle = [arcCenter.x, arcCenter.y];
var thisPosition = [d.x, d.y]; //get position of current node
//thisVector = [center[0]-thisPosition[0], center[1]-thisPosition[1]],
var thisVector = [thisPosition[0] - centerOfCircle[0], thisPosition[1] - centerOfCircle[1]];
var thisVectorX = thisVector[0];
var thisVectorY = thisVector[1];
var xSquared = Math.pow(thisVector[0],2);
var ySquared = Math.pow(thisVector[1],2);
var normalVector = Math.sqrt(xSquared + ySquared); //using pythagoras theorum to work out length from node pos to center
//console.log(d);
thisVectorX= thisVectorX/normalVector;
thisVectorY= thisVectorY/normalVector;
// d.x = centerOfCircle[0]+(thisVectorX*thisScale);// + -38.5;
// d.y = centerOfCircle[1]+(thisVectorY*thisScale);// + -20;
d.x = centerOfCircle[0]+(thisVectorX*thisScale); //thisScale gives the ability to move back to original pos
d.y = centerOfCircle[1]+(thisVectorY*thisScale);
//}
})
.transition().duration(1000)
.attr("transform", function(d)
{
//console.log(d.hasRelationship);
//console.log(d.y);
return "translate(" + d.x + "," + d.y + ")"; //transition nodes
});

d3 force layout with click-to-zoom into specific node

I'm trying to combine click-to-zoom functionality (e.g. http://bl.ocks.org/mbostock/2206590) with d3's force layout (e.g. http://bl.ocks.org/mbostock/1093130). The idea being that after clicking on a node in a force layout diagram, a zoom transition is applied with the node clicked moving to the centre (while any other force-related "tick-ing" is also allowed to continue).
Does anyone know of existing examples combining these pieces of functionality? I've been trying to adapt the click-to-zoom example myself but although the clicked() function seems pretty simple, I'm stuck on the role of the "path" variable and how it seems to be linked to here...
g.append("path")
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
.attr("id", "state-borders")
.attr("d", path);
For example, I have a force layout where all nodes/links are appended to a parent g-element, do I really need an equivalent datum line above? And what is the function of the d attribute that the path variable is being fed into? I know I'm doing something wrong because at the moment when the clicked function evaluates "path.centroid(d)" it's always NaN.
Thanks for any thoughts at all!
Adding my current simplified implementation of automated click/zoom (triggered from within my nodeClick function) that works perfectly until the force layout moves nodes further (due to paramaters like charge, gravity, friction etc)...
var k;
if (d && centered !== d) {
k = 4;
centered = d;
} else {
k = 1;
centered = null;
}
svg.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -d.x + "," + -d.y + ")");

d3 steady horizontal transition along an SVG path

I'm using a d3 attrTween to translate a circle over a path smoothly, similar to this example and as shown in the picture below:
The circle's transition is defined here:
function transition() {
circle.transition()
.duration(2051)
.ease("linear")
.attrTween("transform", translateAlong(path.node()))
}
And the attribute tween is shown here:
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 + ")";
};
};
}
This works well thanks to the SVG method getPointAtLength, which allows us to retrieve coordinates at different lengths of the path. However, I need a different kind of behavior and I've been unable to come up with a solution so far.
I need the circle to animate along the path, but at a steady horizontal speed. Meaning that the circle ought to take as much time to navigate this slice:
As it does with this slice:
Because both slices encompass the same width. On a low level, what I need is to be able to translate any X coordinate with its corresponding Y coordinate along the path. I've looked at all the SVG path methods and I haven't found anything particularly useful here. I'm hoping there's some way in D3 to feed an X coordinate to a d3 line and retrieve its corresponding Y coordinate.
Here's a JSFiddle working as described above. I'd really appreciate any help I can get on this. Thanks!
I ended up creating a lookup array for all my points along the line using getPointAtLength:
var lookup = [];
var granularity = 1000;
var l = path.node().getTotalLength();
for(var i = 1; i <= granularity; i++) {
var p = path.node().getPointAtLength(l * (i/granularity))
lookup.push({
x: p.x,
y: p.y
})
}
Once I had all those points in my lookup table, I used a bisector in my translate tween:
var xBisect = d3.bisector(function(d) { return d.x; }).left;
function translateAlong(path) {
var l = path.getTotalLength();
return function (d, i, a) {
return function (t) {
var index = xBisect(lookup, l * t);
var p = lookup[index];
return "translate(" + p.x + "," + p.y + ")";
};
};
}
And it works as expected! Yahoo!
Fiddle

Implementing complex Processing pushMatrix/popMatrix logic with d3js

I want to reimplement the following Processing sketch with d3js.
Recursion Thing
This wonderful sketch recursively builds up the graph, using a complex pushMatrix, popMatrix hierarchy.
How could this be implemented in d3.js as we there always work on the DOM immediately when appending a shape or transformation. But in a logic like in this sketch the appending part seems to have to be held back for the respective popMatrix to come. It feels like I have to implement my own transform and shape stack to temporary remember the transformation and shapes to be added until the popMatrix comes but that seems so not d3.js.
Any suggestion highly appreciated
ps:
i dont want to use processing.js as i want to work with svg, not canvas.
Interesting problem! Here's my take on it: http://jsfiddle.net/Y48BL/
This is more a proof of concept; I didn't do all the different colours and such. Nevertheless, it demonstrates the general approach. The general idea is to use g elements instead of the matrices that processing uses. Both are used for local transformations of the coordinate system; in the case of the g elements by setting transform accordingly. New gs (matrices) are created inside the recursive function and then passed on to the next level of the recursion. This would correspond to pushMatrix(). Coming back up, we continue to use the original g, corresponding to popMatrix().
The translation of the drawing of the circles and lines is fairly straightforward -- I find the D3 code much easier to read.
So I came up with this helper "class" to get this done, maybe a bit of a overkill but I will have more use cases for this.
var TransformStack = (function () {
function TransformStack() {
this.stack = [];
}
TransformStack.prototype.getCurrentElement = function () {
return this.stack[this.stack.length - 1];
};
TransformStack.prototype.setCurrentElement = function (element) {
this.stack[this.stack.length - 1] = element;
};
TransformStack.prototype.push = function (transformElement) {
this.stack.push(transformElement);
};
TransformStack.prototype.pushAndTransform = function (transformAttr) {
this.push(this.getCurrentElement().append("g").attr("transform", transformAttr));
};
TransformStack.prototype.transform = function (transformAttr) {
this.setCurrentElement(this.getCurrentElement().append("g").attr("transform", transformAttr));
};
TransformStack.prototype.pop = function () {
return this.stack.pop();
};
return TransformStack;
})();
Basically a stack to push/pop g elements which replaces the matrices approach in processing as Lars already pointed out. With this the main routine looks something like
var svg = d3.select("body").append("svg").attr("width", width).attr("height", height)
.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + gScale + ")");
var tstack = new TransformStack();
tstack.push(svg);
doIt(nRecursions);
function doIt(n) {
// Circle
tstack.getCurrentElement()
.append("circle")
.attr("r", theSize)
.style("fill", "#fe6b0c")
.style("stroke", "0")
.style("stroke-width", "2")
.style("opacity", 0.3);
if (n != nRecursions) {
for (var i = 0; i < 4; i++) {
tstack.getCurrentElement().append("line")
.style("stroke", "red")
.style("opacity", 0.16)
.attr("x1", Math.random() * 4 - 2)
.attr("x2", Math.random() * 4 - 2)
.attr("y1", theSize / 2.0 + Math.random() * 4 - 2)
.attr("y2", distance - (theSize * theScale / 2.0) - 8.0 + Math.random() * 4 - 2);
}
}
var rot = 0;
tstack.pushAndTransform("scale(" + theScale + ")");
for (var i = 0; i < n; i++) {
if (n > 0) {
tstack.pushAndTransform("translate(0," + distance + ")");
doIt(n - 1);
tstack.pop();
rot = 360 / n;
tstack.transform('rotate(' + rot + ')');
}
}
tstack.pop();
}
}
Just wanted to share this, maybe of some use for some. The main point was given by Lars already.

Updating Text Labels in D3JS

I have been struggling with this issue for the past couple days: I have a force directed graph that labels its edges just like this example does it. The problem I am facing is that when the graph updates (ie: a node on the graph is added upon a user's click) it updates the graph but it leaves the old edge labels that I wrote previously behind:
BEFORE & AFTER A NEW GRAPH IS APPENDED:
As you can see, my edge labels are hanging around after an update. I have a function that is called everytime new data comes in, and in this function I have the following code that draws the labels:
path_text = svg.selectAll(".path")
.data(force.links(), function(d){ return d.name;})
.enter().append("svg:g");
path_text.append("svg:text")
.attr("class","path-text")
.text(function(d) { return d.data.label; });
The svg variable is declared once at a top level closure like so:
var svg = d3.select("body").append("svg:svg")
.attr("viewBox", "0 0 " + width + " " + height)
.attr("preserveAspectRatio", "xMidYMid meet");
My graph has a tick() function that calculates the location of each label like so:
function tick()
{
// Line label
path_text.attr("transform", function(d)
{
var dx = (d.target.x - d.source.x),
dy = (d.target.y - d.source.y);
var dr = Math.sqrt(dx * dx + dy * dy);
var sinus = dy/dr;
var cosinus = dx/dr;
var l = d.data.label.length * 6;
var offset = (1 - (l / dr )) / 2;
var x=(d.source.x + dx*offset);
var y=(d.source.y + dy*offset);
return "translate(" + x + "," + y + ") matrix("+cosinus+", "+sinus+",
"+-sinus+", "+cosinus+", 0 , 0)";
});
.
.
.
I have tried moving this svg declaration down into the update function, so that this is instantiated each time there is a graph change. This actually works - but it makes an entire duplicate of the entire graph. The first, original copy still keeps the old labels - but the second copy acts exactly how I want it to. Is there a way, perhaps, instead of appending svg, there is a way of replacing? I have also tried calling exit().remove() without any luck as well.
Thank you so much for your time. This has been killing me as to how I'm supposed to do this.
I placed the svg declaration inside my graph update function, attached it to a div, and clear the div before appending it again:
jQuery('#v').empty();
var svg = d3.select("#v").append("svg:svg")
.attr("viewBox", "0 0 " + width + " " + height)
.attr("preserveAspectRatio", "xMidYMid meet");
Not the cleanest solution in my opinion, but will go with this unless you all have a better solution!

Categories

Resources