How to dynamically translate an SVG group in D3.js? - d3.js

I'am working on a bubble-chart using d3. Now there should be an arrow as a graphical asset below the text elements. What I wan to achieve is a dynamic positioning of the arrow-group having a defined gap between itself and the last text:
I've already tried to position it with a percentage value:
arr.append("g").attr("class", "arrow").style('transform', 'translate(-1%, 5%)');
Which does not give the effect I want to.
I also tried to insert a dynamic value based on the radius of the circles which is simply not working and I don't know why:
arr.append("g")
.attr("class", "arrow")
.attr('transform', function(d, i) { return "translate(20," + d.r / 2 + ")");});
OR
arr.append("g")
.attr("class", "arrow")
.style('transform', (d, i) => 'translate(0, ${d.r/2})');
Please find my pen here.
Thank you very much in advance!

Ok.. solved it! For everyone who is interested or having the same trouble:
My last attempt was nearly correct but I was not able to transform via .style(...). I had to use .attr(...) like this:
arr.append("g")
.attr("class", "arrow")
.attr('transform', (d, i) => translate(0, ${d.r/2})');

Related

How to get the parent selection in d3?

To create this DOM:
<g>
<rect></rect>
<circle></circle>
</g>
from .enter() selection, I tried:
someUpdate.enter()
.append('g')
.attr('class', 'my-group')
.append('rect')
.attr('class', 'my-rect')
// I'd like to get the .parent() here
.append('cicle')
.attr('class', 'my-circle')
This doesn't work since .append('rect') changes the selection to rect.
Breaking this to:
const update = someUpdate.enter()
.append('g')
.attr('class', 'my-group')
update
.append('rect')
.attr('class', 'my-rect')
update
.append('cicle')
.attr('class', 'my-circle')
works.
But, I wonder if there is a cleaner way?
There are no methods in D3 for traversing the DOM like e.g. jQuery's .parent(). Hence, the way you broke this down into separate statements will be the correct approach.
On the other hand, it is not completely impossible to do it the way you first suggested. Just yesterday I posted an answer to "D3.js - what is selection.call() returning?" explaining how selection.call() will return exactly the selection it was called upon to allow for method chaining. Keeping that in mind you could something like the following:
d3.select("svg").selectAll("g")
.data([1])
.enter().append('g')
.call((parent) => parent.append('rect')
.attr("fill", "red")
.attr("width", 100).attr("height", 100))
.call((parent) => parent.append('circle')
.attr("fill", "blue").attr("r", 50));
<script src="https://d3js.org/d3.v4.js"></script>
<svg></svg>
Both functions invoked by .call() will be passed the same selection of previously entered <g> elements, which happens to be the parent element in this case.
Although it is possible to do it this way, the solution has its drawbacks. First, it will look somewhat strange and awkward to any seasoned D3 developer, which might complicate matters if you want to share or discuss your code with others. Second, even though I named the parameter parent, which it is in this particular case, it is still not really an equivalent to jQuery's .parent() method. It will just pass in and return the very same selection be it a parent selection or something else.
Agree with the others that your second code snippet is the correct way to do what you want but I want to play to, how about:
d3.select("svg").selectAll("g")
.data([1])
.enter()
.append('g')
.each(function() {
var p = d3.select(this);
p.append('rect')
.attr("fill", "red")
.attr("width", 100).attr("height", 100);
p.append('circle')
.attr("fill", "blue").attr("r", 50);
});
<script src="https://d3js.org/d3.v4.js"></script>
<svg></svg>
#altocumulus said in his answer:
Although it is possible to do it this way, the solution has its drawbacks. First, it will look somewhat strange and awkward to any seasoned D3 developer.
Well, just as him, I have to say that OP's original code (appending the rectangle and the circle to the group separately) is the standard way, applied by the majority of D3 coders.
But, just for fun and to participate in this strange competition, here is another way to do it, using the less famous third argument:
d3.select("svg").selectAll("g")
.data([1])
.enter()
.append('g')
.attr("foo", function(d, i, p) {
d3.select(p[0]).append('rect')
.attr("fill", "red")
.attr("width", 100).attr("height", 100);
return null;
})
.attr("foo", function(d, i, p) {
d3.select(p[0]).append('circle')
.attr("fill", "blue").attr("r", 50);
return null;
});
<script src="https://d3js.org/d3.v4.js"></script>
<svg></svg>
I bet that this wins the "awkward code" competition.

D3 tooltip with dotted x/y axis lines, can't create multiple tooltips

I am trying to create a tooltip for my line chart that sends out dotted lines to the x and y axis (identical to this d3 n00b example, but my chart has multiple lines) http://www.d3noob.org/2014/07/my-favourite-tooltip-method-for-line.html
I have tried setting up two focus groups, tried adding the extra line info into the existing group, but all I can get is the date running on both lines but the dotted lines and data info only work on one.
Any help would be gratefully accepted.
Here is the chart with code underneath:
http://bl.ocks.org/anonymous/d1dbc221f95f6308b351
This is now fixed, renaming the two focus groups worked
Correct code:http://bl.ocks.org/anonymous/b77e904f34aa2162e1df
You are using the same variable for the two focus groups:
var focus = svg.append("g") // tooltip
.style("display", "none");
var focus = svg.append("g") // tooltip
.style("display", "none");
Name one variable focus1 and the other focus2 to make sure that they remain separate.
I would also recommend refactoring the code such that you don't have to call the exact same functions on both of these, once for the CPI line and once for RPIJ line. Instead, you should create a function which calls the required functions on a focus. You should pass the function which line to attach to and the correct focus group, i.e. focus1 and focus2.
Update for the edit:
As I understand it, you have now added text.y1, text.y2, text.y3 and text.y4 to both the focus groups and are effectively hiding the unnecessary information by setting the correct strokes to #fff here:
// place the value at the intersection RPIJ
focus2.append("text")
.attr("class", "y1")
.style("stroke", "white")
.style("stroke-width", "3.5px")
.style("opacity", 0.8)
.attr("dx", 8)
.attr("dy", "-.3em");
focus2.append("text")
.attr("class", "y2")
.attr("dx", 8)
.attr("dy", "-.3em");
// place the date at the intersection RPIJ
focus2.append("text")
.attr("class", "y3")
.style("stroke", "white")
.style("stroke-width", "3.5px")
.style("opacity", 0.8)
.attr("dx", 8)
.attr("dy", "1em");
focus2.append("text")
.attr("class", "y4")
.attr("dx", 8)
.attr("dy", "1em");
However, while doing it you have committed a couple of unique mistakes in each case.
For focus1, you do not create a text.y4 at all while for focus2, you update text.RPIJ instead of text.y3:
focus2.select("text.RPIJ")
.attr("transform",
"translate(" + x(d.date) + "," +
y(d.RPIJ) + ")")
.text("£" + d.RPIJ);

DimpleJS barchart styling columns

I'm basically using a modified version of : http://dimplejs.org/advanced_examples_viewer.html?id=advanced_bar_labels .
I'd like to be able to add for each value a border on the left as high as the value (with a specific color for that border).
I'm not really sure where to start for adding that.
Any ideas?
Thanks.
More details : This is what I'd like to obtain : https://dl.dropboxusercontent.com/u/2227188/Image%202.png - the border on the left is the issue. (jsfiddle.net/mkzTk/5/ this what I currently have which is pretty much what's in the example - I don't know where to start really for adding a border)
You could append a rectangle after drawing for each element of the series as follows:
mySeries.afterDraw = function (s, d) {
var shape = d3.select(s);
svg.append("rect")
.attr("x", shape.attr("x"))
.attr("y", shape.attr("y"))
.attr("height", shape.attr("height"))
.attr("width", "10px")
.style("fill", shape.style("stroke"))
.style("pointer-events", "none");
};
The example you mention already uses the afterDraw function so just add the contents above to the existing method for labelling.
It looks nice, here's an example:
http://jsbin.com/lorin/9/edit?js,output#J:L20
I would set up each bar + edge pair as its own group based on a certain data point, and then append two rect elements to that group. Differences in color can be used to give them their distinctive colors.
Your code would look something like this:
var monthBars = d3.selectAll('.monthBar') //These will be for each chart
.data(allMyData, idFunction) //Assign and key your data
.enter()
.append('g')
.classed('monthBar', true);
.each(function(d){
var taskGroups = d3.select(this).selectAll('.taskGroup')
.data(d.dataForThisMonth, taskIdFn)
.enter()
.append('g')
.classed('.taskGroup', true);
.attr('transform', ...) //Define the x and y positioning for the group
taskGroups.append('rect')
//Make this the 'body' rect with the text in it
taskGroups.append('rect')
//Make this the edge rect
})

dc.js accessing a chart other that it's own in postRender

I have 2 charts on a page (A and B) and wish for some custom behavior to be added to chart B when a brush filter on chart A is performed.
I thought I could achieve this by doing something like;
charta.on('postRender', function(){
...
d3.selectAll('#chartb svg')
.data(nested, function(d){ return d.key})
.enter()
.append("g")
.attr("class", "aclass")... more code....
But this #chartb selector doesn't seem to work - when I inspect the DOM it has appended the <g> attributes to the <html> element and not the svg element I wanted to append to.
Is what I am trying to achieve possible?
If you are just adding stuff to the other chart, something like this should be possible. I don't think you will be able to select the generated items of the other chart and then apply a d3 join to it, because it is already joined.
I believe the problem with the code above is that d3.select is what you use to choose the context for a join, and d3.selectAll is what you use to actually make the data join. See
http://bost.ocks.org/mike/join/
So your code is trying to join to the chart and svg elements, which would have the effect you are describing. Instead you'll want to d3.select the svg and then d3.selectAll the elements you want to add - even though they don't exist yet! Yes, it's kind of a mind-bender; take a look at the above and the linked articles to get a better idea of it.
Note: there are dc convenience methods on the chart object which will execute the selects in the right context.
I got this working in the end by replacing the .enter() with repeated calls to datum() instead. A bit of a hack, but it works; If anyone can suggest a more d3ish way of acheiving this, I would be very grateful.
var svg = chart.svg();
nested.forEach(function(withValues) {
_(withValues.values).filter(function(d){return d.value < threshold}).forEach(function(timesMatchingThreshold){
svg.datum(timesMatchingThreshold)
.append("rect")
.style("opacity", 0.6)
.attr("transform", "translate(" + 30 + ", " + (-30) + ")")
.attr("class", "belowThreshold")
.attr("x", function(d) {return x(d.date)})
.attr("y", function(d) {return 200 - y(d.value)})
.attr("width", 3)
.attr("height", function(d) {return y(d.value)});
});

d3.scale.category20b always returning first color

I try to use d3.scale.category.20b() to generate a color scale, problem is whatever number of the list I ask for, it always returns first element of the list.
var color = d3.scale.category20b();
console.log(color(X));
OR
console.log(d3.scale.category20b()(X);
No matter what X is, it always logs #393b79 which is the first elements, according to the d3 API
This can happen because categorical scales in d3 append to the domain as new data comes in. If every enter() creates a new categorical scale, the domain of the categorical scale remains the same.
As an example, please consider this jFiddle: http://jsfiddle.net/seldomawake/MV55j/1/
Here, we see that as data enters, we append to a categorical scale in the global namespace, $colorScale (specific code below).
function redraw(theData) {
var localColorScale = d3.scale.category20c(); //< NOT USED HERE
var svg = d3.select("svg");
var circles = svg.selectAll("circle")
.data(theData).enter().append("circle")
var circleAttributes = circles.attr("cx", getRandomInt(50, 450))
.attr("cy", getRandomInt(50, 450))
.attr("r", function (d) { return d.value; })
.style("fill", function () { return $colorScale(getRandomInt(0, 19)); });
}
However, if we were to replace return $colorScale(getRandomInt(0, 19)) with return localColorScale(getRandomInt(0, 19)), we would no longer have the data append to the range of the categorical scale, and which would result in a single-color output.
Edit: fixing URL to jsfiddle.
At first I thought this would have been a bug with D3.js so created this jsfiddle which works fine.
var data = d3.range(0,20);
var color = d3.scale.category20b();
d3.select('.target').selectAll('div')
.data(data)
.enter()
.append('div')
.text(function(d){return color(d);})
.attr('style', function(d){return "background-color:"+ color(d) + ";" ;})
It had been raised by others about version of D3 you are using. This looks unlikely to be the cause of your issue as the code in question has hardly been touched. If the code has not been touched much and others have no issue it raises the question of browser compatibly. I sent my jsfiddle to browsershots and did not see any browser output a single block of color instead of the expected pretty color stripes.
After all this it seams there is not enough information to properly answer your problem. I suggest you have a look to see if X is really changing by making a small change to the code console.log({'color':color(X), 'x':X}).
Which version of D3 are you using? I wrote a jsFiddle (D3 3.0.4), the colors are shown normally:
var color = d3.scale.category20b();
var svg = d3.select('#chart').append('svg')
.attr('width', 200)
.attr('height', 100);
svg.append('rect')
.attr('width', 100)
.attr('height', 100)
.attr('fill', color(0));
svg.append('rect')
.attr('x', 100)
.attr('width', 100)
.attr('height', 100)
.attr('fill', color(1));
The result is:

Resources