how to add multiple x axes to d3 chart via data join? - javascript

I am trying to create several charts at once using d3 - code is here: http://jsfiddle.net/jgilfillan/W85ut/
I have an array bulletDataX of these objects:
function BulletObject(name, actual, target, ranges, bulletWidth) {
this.name = name;
this.actual = actual;
this.target = target;
this.ranges = ranges;
this.maxX = ranges[2];
this.bulletWidth = bulletWidth;
this.scale = d3.scale.linear().domain([0, this.maxX]).range([0, this.bulletWidth]);
this.xAxis = d3.svg.axis().scale(this.scale).orient("bottom");
}
this is the code I am trying to get working...
//axes??? not working
svg.selectAll(".xaxis")
.data(bulletDataX)
.enter()
.append("g")
.attr("id", function(d) { return d.name; })
.attr("class", "x axis")
.attr("transform", function(d, i) { return "translate(0, " + ((bulletHeight + bulletPadding) * i + .25 * bulletHeight).toString() + ")"; })
.call(function(d, i) { return d.xAxis; });
I know I have to fiddle with the transform attribute a bit but I can't even get the axis to display. I think the issue is with .call(function(d, i) { return d.xAxis; }) but I can't figure out how to get it to work. Any ideas would be much appreciated.

The call method doesn't provide you with d, i arguments. Instead, it:
Invokes the specified function once, passing in the current selection
along with any optional arguments.
See the API documentation: selection.call(function[, arguments…])
You can try using each instead. It:
Invokes the specified function for each element in the current
selection, passing in the current datum d and index i, with the this
context of the current DOM element.
See the API documentation: selection.each(function)
Here's a code example:
.each(function (d, i) {
return d.xAxis(d3.select(this));
});

Related

apply a transition on each object in a D3 selection

I'm having troubles in understanding how to get each D3 object in a selection to apply a transition.
Consider the follwoing code (here on jsfiddle):
var svg = d3.select('svg');
var dataSet = [10, 20, 30, 40];
var circle = svg.selectAll('circle')
.data(dataSet)
.enter()
.append('circle')
.attr("r",function(d){ return d })
.attr("cx", function(d, i){ return i * 100 + Math.random()*50 })
.attr("cy",50)
.attr("fill",'red')
;
circle.each(function(d,i) {
this
.transition()
.duration(1000)
.attr("cx",this.cx+100);
})
My use of this is wrong. I've also tried with d3.select(this) but I get the dom object corresponding to D3 object.
I'm unable to get the D3 object to apply transition.
The missing part is that you can supply a function to .attr('cx', function (d,i) { ... }) when using a transition, and inside that function you can access the cx attribute using this.getAttribute('cx').
Of course, you also want to make sure to turn it into a number using parseInt(), otherwise it will do string concatenation (because JS, sigh).
So change your final line to:
circle.transition().duration(1000).attr('cx', function(d, i) {
return parseInt(this.getAttribute('cx')) + 100;
});

Tweening numbers in D3 v4 not working anymore like in v3

I'm trying to figure out why my tweening numbers (counting up or down) code in Version 4 of D3 doesn't function any more.
Here is my code:
var pieText = svg4.append("text")
.attr("class", "pieLabel")
.attr("x", 0)
.attr("y", 0)
.text(0)
.attr("dy", "0.2em")
.style("font-size", 19)
.style("fill", "#46596b")
.attr("text-anchor", "middle");
d3.selectAll(".pieLabel").transition()
.delay(500)
.duration(1000)
.tween("text", function(d) {
var i = d3.interpolate(this.textContent, d.value);
return function(t) {
this.textContent = form(i(t));
};
});
The console.log tells me that the interpolation works fine.
So what has changed? And how do I get it to work?
Thanks for your help.
The problem here is just this inside the inner function, which will no longer work as it worked in v3.
Let's prove it. Have a look at the console here, using D3 v3, this is the DOM element:
d3.select("p").transition()
.tween("foo", function(d) {
var i = d3.interpolate(0, 1);
return function(t) {
console.log(this)
};
});
<script src="https://getfirebug.com/firebug-lite-debug.js"></script>
<script src="https://d3js.org/d3.v3.min.js"></script>
<p></p>
Now the same snippet, using D3 v4... this is now the window object:
d3.select("p").transition()
.tween("foo", function(d) {
var i = d3.interpolate(0, 1);
return function(t) {
console.log(this)
};
});
<script src="https://getfirebug.com/firebug-lite-debug.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<p></p>
Solution: keep the reference to this in the outer function as a variable (traditionally named self, but here I'll name it node):
d3.selectAll(".pieLabel").transition()
.delay(500)
.duration(1000)
.tween("text", function(d) {
var node = this;
//keep a reference to 'this'
var i = d3.interpolate(node.textContent, d.value);
return function(t) {
node.textContent = form(i(t));
//use that reference in the inner function
};
});
Here is your code with that change only:
var widthpie = 250,
heightpie = 300,
radius = Math.min(widthpie, heightpie) / 2;
var data = [{
antwort: "A",
value: 0.5
}, {
antwort: "B",
value: 0.4
}];
var form = d3.format(",%");
var body4 = d3.select("#chart1");
var svg4 = body4.selectAll("svg.Pie")
.data(data)
.enter()
.append("svg")
.attr("width", widthpie)
.attr("height", heightpie)
.append("g")
.attr("transform", "translate(" + widthpie / 2 + "," + heightpie / 2 + ")");
var pieText = svg4.append("text")
.attr("class", "pieLabel")
.attr("x", 0)
.attr("y", 0)
.text(0)
.attr("dy", "0.2em")
.style("font-size", 19)
.style("fill", "#46596b")
.attr("text-anchor", "middle");
d3.selectAll(".pieLabel").transition()
.delay(500)
.duration(1000)
.tween("text", function(d) {
var node = this;
var i = d3.interpolate(node.textContent, d.value);
return function(t) {
node.textContent = form(i(t));
};
});
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="chart1"></div>
PS: I'm using Firebug in the snippets because S.O. snippet don't log the window object correctly.
The problem is caused by a change which is not documented in the changelog. D3 v3 calls the inner function returned by the callback passed to transition.tween() on every tick passing the current node as the this context:
tweens[--n].call(node, e);
As of v4, however, this is no longer the case when the inner function is called:
tween[i].call(null, t);
Passing null to that function will be replaced with the global object. In this case this in your function no longer points to current element as was true for v3. The remedy was already been laid out by Gerardo in his answer and is even suggested by the documentation on v4:
transition.tween("attr.fill", function() {
var node = this, i = d3.interpolateRgb(node.getAttribute("fill"), "blue");
return function(t) {
node.setAttribute("fill", i(t));
};
});
Hence, you need a closure to keep a reference to the current element as it is referenced as this in the callback provided to transition.tween().
i thing i copy this code i dont know where, if any one knew just give referance. i use this to add transition to path, i hope this give you an idea
Use this to append the transition
.transition()
.duration(2000)
.attrTween("stroke-dasharray", tweenDash)
this the function
function tweenDash() {
return function(t) {
var l = path.node().getTotalLength();
interpolate = d3.interpolateString("0," + l, l + "," + l);
//t is fraction of time 0-1 since transition began
var p = path.node().getPointAtLength(t * l);
return interpolate(t);
}
}

Assign Class to All Elements of Same Bound Data

I have a scatter plot and a table. Each circle in the scatter plot has a corresponding row in the table. When I apply classes to the circles for CSS purposes, I also want to have that same class be assigned to the corresponding table row. They have the same data value, but are appended to separate elements.
Here is my circle class event:
my_circles.each(function(d,i) {
if (my_bool===true) {
d3.select(this).classed('selected',true);
//d3.selectAll('tr').filter(d===???)
}
});
I was trying to use a filter to select only the table rows of matching d value, but it didn't quite work out, I didn't know how to finish the line. Which got me thinking, maybe there is a better way, like the post title, assign classes to all elements bound to the same data.
If you have another solution aside from any of my ideas, that would be fine too.
Probably the easiest solution will be to check in the .classed() method for the tr selection, if the data bound to that tr matches the one for the selected circle.
my_circles.each(function(d,i) {
if (my_bool===true) {
d3.select(this).classed("selected",true);
d3.selectAll('tr')
.classed("selected", trData => d === trData); // Set class if data matches
}
});
This, however, is a bit clumsy and may be time-consuming because it will iterate over all trs each time this code is called. In case this is in an outer loop for handling multiple selected circles—as mentioned in your comment—things will get even worse.
D3 v4
For a slim approach I would prefer using D3's local variables, which are new to v4, to store the references between circles and table rows. This will require just a one-time setup which will depend on the rest of your code, but might go somewhat along the following lines:
// One-time setup
var tableRows = d3.local();
my_circles.each(function(d) {
var row = d3.selectAll("tr").filter(trData => d === trData);
tableRows.set(this, row); // Store row reference for this circle
});
This creates a new local variable tableRows which is used to store the reference to the corresponding table row for each circle. Later on you are then able to retrieve the reference to the row without the need for further iterations.
my_circles.each(function(d,i) {
if (my_bool===true) {
d3.select(this).classed('selected',true);
tableRows.get(this).classed("selected", true); // Use local variable to get row
}
});
D3 v3
If you are not yet using D3 there are, of course, other ways to achieve the same thing. Personally, I would prefer using a WeakMap to store the references. Because the API of the WeakMap also features get and set methods similar to d3.local, all you need to do is to change the line creating the local reference store while keeping the rest of the above code as is:
// var tableRows = d3.local();
var tableRows = new WeakMap(); // use a WeakMap to hold the references
You can use dataIndex for this purpose. Here is a code snippet for the same.
var data = ["A", "B", "C"];
var color = d3.scale.category10();
var container = d3.select("body")
.append("svg")
.attr("height", 500)
.attr("width", 500);
var my_circles = container.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("name", function(d, i) {
return "circle" + i
})
.attr("r", 10)
.attr("cx", function(d, i) {
return (i + 1) * 50
})
.attr("cy", function(d, i) {
return (i + 1) * 50
})
.style("fill", function(d, i) {
return color(i)
});
container.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("name", function(d, i) {
return "rect" + i
})
.attr("width", 15)
.attr("height", 15)
.attr("x", function(d, i) {
return i * 50 + 200
})
.attr("y", function(d, i) {
return (i + 1) * 50
})
.style("fill", function(d, i) {
return color(i)
});
my_circles.each(function(d, i) {
d3.select(this).classed("selected" + i, true);
container.selectAll("[name=rect" + i + "]").classed("selected" + i, true);
});
svg {
border: 1px solid black;
background: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

D3 -- Arcs for chord diagram not being displayed

I trying to understand how the D3 chord diagram works. My first step is to display the arcs for the diagram with following script. But for some reason, the arcs are not showing up.
See web page HERE
Can some one tell me what I am missing?
<body>
<script>
// Chart dimensions.
var width = 960,
height = 750,
innerRadius = Math.min(width, height) * .41,
outerRadius = innerRadius * 1.1;
//Create SVG element with chart dementions
var svg = d3. select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append ("g")
.attr("transform", "translate (" + width / 2 + "," + height / 2 + ")");
//------------Reformat Data ------------------------------------------
var matrix = []; // <- here is the data
d3.tsv('picData.tsv', function(err, data)
{
//console.log(data);
pictures = d3.keys(data[0]).slice(1);
//console.log(pictures);
data.forEach(function(row)
{
var mrow = [];
pictures.forEach(function(c)
{
mrow.push(Number(row[c]));
});
matrix.push(mrow);
//console.log(mrow);
});
//console.log('1st row: ' + matrix[0]);
//console.log(matrix);
});
//---------------- Define diagram layout ----------------------------
var chord = d3.layout.chord() //<-- produce a chord diagram from a matrix of input data
.matrix(matrix) //<-- data in matrix form
.padding(0.05)
.sortSubgroups(d3.descending);
var fill = d3.scale.category20(); //<-- https://github.com/mbostock/d3/wiki/API-Reference#d3scale-scales
//console.log(fill);
var g = svg.selectAll("g.group")
.data(chord.groups)
.enter().append("svg:g")
.attr("class", "group");
//console.log(g);
// create arcs
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
//console.log(arc);
g.append("path")
.attr("d", arc)
.style("fill", function(d) { console.log(d.index); return fill(d.index);})
.style("stroke", function(d) { return fill(d.index); })
.attr("id", function(d, i) { return"group-" + d.index });;
g.append("svg:text")
.attr("x", 6)
.attr("class", "picture")
.attr("dy", 15)
.append("svg:textPath")
.attr("xlink:href", function(d) { return "#group-" + d.index; })
.text(function(d) { return pictures[d.index]; });
//console.log(g);
</script>
</body>
Your problem stems from the fact that d3.tsv is asynchronous:
Issues an HTTP GET request for the comma-separated values (CSV) file at the specified url... The request is processed asynchronously.
As a result, all of your code under "Define diagram layout" is being executed before any data is loaded. Otherwise, your code works fine (See image below). So just move all your code into your d3.tsv(...) call and you'll be all set.
Your script is running without errors, but no elements are being created from your data join. That's usually a sign that you are passing in a zero-length data array.
In fact, you're not passing in an array at all; you're passing a function object. When d3 looks up the array length of that object, it returns undefined, which gets coerced to the number zero, and so no groups and no chords are created.
Relevant part of your code:
var g = svg.selectAll("g.group")
.data(chord.groups)
.enter().append("svg:g")
.attr("class", "group");
To actually get the array of chord group data objects, you need to call chord.groups(). Without the () at the end, chord.groups is just the name of the function as an object.
Edited to add:
Ooops, I hadn't even noticed that your drawing code wasn't included inside your d3.tsv callback function. Fix that, as described in mdml's answer, then fix the above.

D3Js updating wrong elements using call

I am new to D3JS and you can find the code at http://jsfiddle.net/3n8wD/
I am a facing an issue and any pointers would help
When I move the line it separates as expected, but as soon as I try to move the circles away it jumps back to the line.
On inspecting the Array, looks like the circle array is updating as I am moving the link, not sure what is causing that.
Any help on this would be highly appreciated. below is the code that i have on jsfiddle
var width = 960,
height = 500;
graph1 = {"nodes":[{"x":444,"y":275},{"x":378,"y":324}],"links":[{"source":1,"target":0}]}
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var onDraggable = function(d1) {
console.log(d1);
d1.x = d3.event.x, d1.y = d3.event.y;
d3.select(this).attr("cx", d1.x).attr("cy", d1.y);
//console.log(d1);
link.filter(function(l) { return l.source === d1; }).attr("x1", d3.event.x).attr("y1", d3.event.y);
link.filter(function(l) { return l.target === d1; }).attr("x2", d3.event.x).attr("y2", d3.event.y);
}
var drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on("drag", onDraggable)
var onDraggable1 = function(d) {
//d.x1 = d3.event.x1, d.y1 = d3.event.y1, d.x2=d3.event.x2, y2=d3.event.y2;
var mouseX = d3.mouse(this)[0];
var mouseY = d3.mouse(this)[1];
var relativeX = mouseX-d.source.x;
var relativeY = mouseY-d.source.y;
console.log(d);
//console.log(d);
// d3.select(this).attr("x1", d3.event.x).attr("y1", d3.event.y).attr("x2", d3.event.x).attr("y2", d3.event.y);
d.source.x= d.source.x+relativeX;
d.source.y = d.source.y+relativeY;
d.target.x= d.target.x+relativeX;
d.target.y = d.target.y+relativeY;
d3.select(this).attr("x1", d.source.x).attr("y1", d.source.y);
d3.select(this).attr("x2", d.target.x).attr("y2", d.target.y);
}
var drag1 = d3.behavior.drag()
.origin(function(d) { return d; })
.on("drag", onDraggable1);
graph1.links.forEach(function(d) {
d.source = graph1.nodes[d.source];
d.target = graph1.nodes[d.target];
});
var node = svg.append("g").attr("class","node")
.selectAll("circle")
.data(graph1.nodes)
.enter().append("circle")
.attr("r", 4)
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.call(drag);
var link = svg.append("g").attr("class","link")
.selectAll("line")
.data(graph1.links)
.enter().append("line")
.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; })
.attr("stroke-width", 2)
.attr("stroke", "black")
.call(drag1);
The reason that your nodes are "jumping" to the end of the line, even after the line has been moved away, is because the data object for the node is the same object as the source/target data object for the line. The d value for the node and the d.source or d.target value for the link are just pointers (references) to the same object in the browser's memory. Everything you do to that data object is reflected in both the link and the node. That's what makes the node-drag function work: you change the data object directly, and then you update the position of the line and the circle to match their already-changed data value.
So, even though you don't update the circle's cx and cy position at the time you move the line, the statements d.source.x = etc. in the line drag method are setting the d.x and d.y values for the nodes. Then in the node drag method, when you access these values to determine the new position, the movement is determined relative to the end of the line, not to the position of the circle on screen.
So how do you get the behaviour you want, to separate the line from the node? You need to create a new data object for the line's source and target when you start to drag it, one that no longer references the nodes' data objects. You only need to do this once, at the start of the drag, so you use the dragstart event of the drag behaviour object:
var drag1 = d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", separateNodes);
.on("drag", onDraggable1);
The separateNodes method is going to be quite short. All we have to do is, for both the target and the source of the link, create a new data object that is a copy of the existing target/source object (but which can be edited independently of the original object). Javascript doesn't have any default methods for copying an object (although various extensions do), but if the object in question just consists of x and y attributes, it is easy to do.
var separateNodes = function(d) {
//create new data objects that are copies of d.source and d.target
var newSource = {x:d.source.x, y:d.source.y};
var newTarget = {x:d.target.x, y:d.target.y};
//set the new objects as the target/source of this link
d.source = newSource;
d.target = newTarget;
}
You could even skip the variable declarations and just merge it all into one line for source and one for target, but I find it easier to understand like this. I should mention, however, that this approach works because x and y are both just simple data types (numbers), not pointers to objects. So when it says x:d.source.x, you are actually getting a new copy of that number which you can change without having it affect the original object that contained that number.
If, however, your nodes and links also have a value property that is a complex data object, then you have to decide what you want to do when the line separates from the node: do you want to create a copy of the entire value data object, or maintain the link to the original object, or replace it with a null (empty/undefined) object? The first case is complicated, and depends on the data structure of the value object; for the second you don't need to do anything (except remember that you've got multiple pointers to the same object); and for the last case you just add a value:null, attribute to the newSource and newTarget object definitions in your separateNodes function.
Hope that all makes sense,
--ABR

Categories

Resources