I'm trying to make a D3 chord diagram following Mike Bostock's v4 example and a v3 example with mouseover events.
In the v3 example above, there is a fade function that highlights specific ribbbons for a mouseover'd group:
function fade(opacity) {
return function(d, i) {
svg.selectAll("ribbons")
.filter(function(d) {
return d.source.index != i && d.target.index != i;
})
.transition()
.style("opacity", opacity);
};
}
Though for the life of me I can't get it working in my v4 example, despite trying to put it in a similar spot:
//Draw the ribbons that go from group to group
g.append("g")
.attr("class", "ribbons")
.selectAll("path")
.data(function(chords) { return chords; })
.enter().append("path")
.attr("d", ribbon)
.style("fill", function(d) { return color(d.target.index); })
.style("stroke", function(d) { return d3.rgb(color(d.target.index)).darker(); })
.on("mouseover", fade(.1)) /* Where attempt at mouseover is made */
.on("mouseout", fade(1));
.append("title").
text(function(d){return chordTip(d);})
Here is a jsfiddle with my attempt (with working tooltips, but non-working fade mouseovers): https://jsfiddle.net/wcat76y1/3/
I believe my error has something to do with how I'm segregating variables, but I'm not sure how exactly where I went wrong.
I found part 2 of the previous answer didn't work for me but this did.
function fade(opacity) {
return function(d, i) {
d3.selectAll("g.ribbons path")
.filter(function(d) {
return d.source.index != i && d.target.index!= i;
})
.transition()
.style("opacity", opacity);
};
}
The line you had with just ribbons didn't seem to select the right element. I agree with the first corrections you did, but didn't use the text elements.
Here's my forked fiddle https://jsfiddle.net/kLe38tff/
I managed to figure it out. Looks like there were several issues that combined to obfuscate my attempted solutions. While I can mark this as solved, there are still some elements to my solution that I don't understand .
I was adding the mouseover events to the wrong section (DOM element?). It should have been added to the operations on the group elements not ribbon elements. Hence, it should have gone here:
//Draw the radial arcs for each group
group.append("path")
.style("fill", function(d) { return color(d.index); })
.style("stroke", function(d) { return d3.rgb(color(d.index)).darker(); })
.attr("d", arc)
.on("mouseover", fade(.1))
.on("mouseout", fade(1))
Using svg.selectAll("ribbons") in the fade function was not selecting the elements I wanted (I'm still not sure why not...). Replacing that command with just the variable ribbons allowed the selection to complete successfully:
function fade(opacity) {
return function(d, i) {
ribbons
.filter(function(d) {
return d.source.index != i && d.target.index != i;
})
.transition()
.style("opacity", opacity);
};
}
Adding <text> elements to the variable ribbons during its creation led to the opacity being modified on the text element instead of the ribbon element itself. Separating this to a separate line of code fixed the final issue.
The updated JSFiddle shows the fully working example: https://jsfiddle.net/wcat76y1/5/
Related
Very much D3 newbie here, feeling my way around adapting an existing radar chart built in D3 v3.5.9
I'm having an issue in interpolating between points when there are zero values between them.
Example:
radar when all data points are present, looks fine
radar when there are zero values
The behaviour I want is for the interpolation to go around the circle, rather than just closing the sections bounded by the non-zero values.
green lines show desired behaviour whenever a zero is encountered
I have used the 'defined' function to find zero values in the source data, but I need to add something else to instruct D3 to draw the connecting lines between the desired points. Something with the index value for d, probably?
Or perhaps 'defined' is not the right function in this case?
var radarLine = d3.svg.line
.radial()
.defined(function (d) {
return d.value !== 0;
})
.interpolate("linear-closed")
.radius(function (d) {
return rScale(d.value);
})
.angle(function (d, i) {
return i * angleSlice;
});
if (cfg.roundStrokes) {
radarLine.interpolate("cardinal-closed");
}
//Create a wrapper for the blobs
var blobWrapper = g
.selectAll(".radarWrapper")
.data(data)
.enter()
.append("g")
.attr("class", "radarWrapper");
//Append the backgrounds
blobWrapper
.append("path")
//.attr("class", "radarArea")
.attr("class", function (d) {
return "radarArea" + " " + d[0].radar_area.replace(/\s+/g, "");
})
.attr("d", function (d, i) {
return radarLine(d);
})
.style("fill", function (d, i) {
return cfg.color(i);
})
.style("fill-opacity", 0);
//Create the outlines
blobWrapper
.append("path")
.attr("class", "radarStroke")
.attr("d", function (d, i) {
return radarLine(d);
})
.style("stroke-width", cfg.strokeWidth + "px")
.style("stroke", function (d, i) {
return cfg.color(i);
})
.style("fill", "none");
Any help would be greatly appreciated!
I have a transition running on some circles in d3. The transition brings the circles into view, and updates a text display with their time stamp:
var component = this;
select(this.node).select("#circles").selectAll(".pin")
.data(this.props.data)
.enter()
.append("circle", ".pin")
.attr("r", 5/component.state.zoomScale)
.style("fill", "#ff0000")
.style("opacity", "0.0")
.transition()
.on("start", function(d, i) {
if (i % component.props.multiplier == 0) {
select("#timer").text(d.time);
}
})
.style("opacity", "1.0")
.delay(function(d, i) {return d.delay/component.props.multiplier;});
If my component receives new data, I want to stop any running transitions on the circles, clear the text and remove the circles:
var circles = select(this.node).select("#circles").selectAll(".pin");
if (!circles.empty()) {
circles.interrupt();
select("#timer").text(""); //This is my time display that I want to clear
circles.remove();
While the circles are removed fine, the text reappears after being removed suggesting the transition was never actually stopped. How do I correctly stop the transition running on my circles? I am using d3.js v4 within ReactJS.
you have to set the class separate and not with the append call.
Your selection to interrupt does select nothing, there is no circle with that class.
var component = this;
select(this.node).select("#circles").selectAll(".pin")
.data(this.props.data)
.enter()
.append("circle")
.attr("class", "pin")
.attr("r", 5/component.state.zoomScale)
.style("fill", "#ff0000")
.style("opacity", "0.0")
.transition()
.on("start", function(d, i) {
if (i % component.props.multiplier == 0) {
select("#timer").text(d.time);
}
})
.style("opacity", "1.0")
.delay(function(d, i) {return d.delay/component.props.multiplier;});
I have a grouped bar chart similar to https://bl.ocks.org/mbostock/3887051
I used a mouseover function to fade the bars the mouse is currently not over
function mouseover(bar)
{
d3.selectAll(".bar")
.filter(function(d){ return (d != bar);})
.transition(t)
.style("opacity", 0.5);
}
While this works nicely to highlight a single bar, I now need to highlight the entire group / fade everything but this group.
So far I haven't been able to figure out though how to get from the datum element d passed via .on("mouseover", function(d) ... back to the entire group this element belongs to.
Is there a simple way to achieve this in D3v4?
In D3 4.0 the callback function for the .on() method is passed 3 arguments: the current datum (d), the current index (i), and the current group (nodes).
Within the mouseover callback, you can selectAll("rect"), and filter out items which are in the current group (node). With this selection, you then set opacity to 0.5. On mouseout, you just need to set all opacity back to 1.0. The pertinent code is:
...
.on('mouseover', function(d, i, node) {
d3.selectAll("rect")
.filter(function (x) { return !isInArray(this, node)})
.attr('opacity', 0.5);
}
)
.on('mouseout', function() {
d3.selectAll("rect").attr('opacity', 1.0);
});
with a small helper function to check if a value is present in an array (array of DOM elements in our case):
function isInArray(value, array) {
return array.indexOf(value) > -1;
}
The full code in context (given your linked example):
g.append("g")
.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d) { return "translate(" + x0(d.State) + ",0)"; })
.selectAll("rect")
.data(function(d) { return keys.map(function(key) { return {key: key, value: d[key]}; }); })
.enter().append("rect")
.attr("x", function(d) { return x1(d.key); })
.attr("y", function(d) { return y(d.value); })
.attr("width", x1.bandwidth())
.attr("height", function(d) { return height - y(d.value); })
.attr("fill", function(d) { return z(d.key); })
.on('mouseover', function(d, i, node) {
d3.selectAll("rect")
.filter(function (x) { return !isInArray(this, node)})
.attr('opacity', 0.5);
}
)
.on('mouseout', function() {
d3.selectAll("rect").attr('opacity', 1.0);
});
One solution could be:
Make a function which selects all group and gives it a transition of opacity 0.
The DOM on which mouse is over give opacity 1.
function hoverIn(){
d3.selectAll(".group-me").transition()
.style("opacity", 0.01);//all groups given opacity 0
d3.select(this).transition()
.style("opacity", 1);//give opacity 1 to group on which it hovers.
}
Make a function which selects all group and gives it a transition of opacity 1, when the mouse is out.
function hoverOut(){
d3.selectAll(".group-me").transition()
.style("opacity", 1);
}
On the group add a class and add the mouse out and in function like
g.append("g")
.selectAll("g")
.data(data)
.enter().append("g")
.classed("group-me", true)//add a class for selection.
.on("mouseover", hoverIn)
.on("mouseout", hoverOut)
working code here
Is it possible to add zooming to a circle pack? Seems like it should be but mine is jumping all around the place when zoom is clicked. I've been attempting to solve this for a few days but with little success.
I've been referencing Mike's Zoomable Circle Packing block (#7607535) and nilanjenator's block (#4950148). Other examples seem to be based on these two. Here's a fiddle of my work in progress: http://jsfiddle.net/cajmcmahon/9weovdm2/5/.
From what I can make out, my layout problems lie in these two functions:
t.selectAll("circle")
.attr("cx", function(d) {
return x(d.x);
})
.attr("cy", function(d) {
return y(d.y);
})
.attr("r", function(d) {
return k * d.r;
});
t.selectAll("text")
.attr("x", function(d) {
return x(d.x);
})
.attr("y", function(d) {
return y(d.y);
})
.style("opacity", function(d) { return k * d.r > 20 ? 1 : 0; });
Also, I can't get the viz to reset (zoom out?) when I click on the background. I believe it's not getting values for 'data'...
//Reset when click on background
d3.select(window).on("click", function(d, i) {
zoom(data)
});
Thanks for any help.
Ok both examples:
Have same output but they are implemented differently.
http://bl.ocks.org/nilanjenator/4950148: This one relies on changing the cx and cy of the circle for moving and updating the radius for zoom effect.
http://bl.ocks.org/mbostock/7607535: This one relies on translate to move the circles.
So in your example: you mixed both of it and thus you got a different flavor of circle packing.
You created circles and moved it into their position using translate but in the zoom section you made use of changing the cx and cy, as a result your circles flew out of the pack on zooming.
I have removed the translate and gave the cx and cy so the zoom function remain the same as what you have written.
node.append("circle")
.attr("r", function(d) {
return d.r;
})
.attr("class", function(d) { return d.children ? "parent" : "child"; })
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.on("click", function(d) {
zoom(node == d ? root : d);
});
I have changed the fiddle you gave here is a working example:
http://jsfiddle.net/cyril123/khq21pgb/2/
Hope this is helpful!
I am using V3 of the popular d3 library and basically want to have three transitions, followed by each other: The first transition should apply to the exit selection, the second to the update selection and the third to the enter selection. They should be chained in such a manner that when one of the selections is empty, its respective transition is skipped. I.e. when there is no exit selection, the update selection should start immediately. So far, I have come up with this code (using the delay function).
// DATA JOIN
var items = d3.select('#data').selectAll('.item');
items = items.data(data, function(d){
return d.twitter_screenname;
});
// EXIT
items.exit().transition().duration(TRANSITION_DURATION).style('opacity', 0).remove();
// UPDATE
// Divs bewegen
items.transition().duration(TRANSITION_DURATION).delay(TRANSITION_DURATION * 1)
.style('left', function(d, i) {
return positions[i].left + "px";
}).style('top', function(d, i) {
return positions[i].top + "px";
});
// ENTER
// Divs hinzufügen
var div = items.enter().append('div')
.attr('class', 'item')
.style('left', function(d, i) {
return positions[i].left + "px";
}).style('top', function(d, i) {
return positions[i].top + "px";
});
div.style('opacity', 0)
.transition().duration(TRANSITION_DURATION).delay(TRANSITION_DURATION * 2)
.style('opacity', 1);
First of all it doesn't allow to "skip" transitions and secondly I think there is a better way than delay. I've looked at http://bl.ocks.org/mbostock/3903818 but I did not really understand what is happening.
Also, somehow just writing items.exit().transition().duration(TRANSITION_DURATION).remove() does not work with the items, probably because they are not SVG elements but divs.
Sure. Here are two ways.
First, you could use an explicit delay, which you then compute using selection.empty to skip empty transitions. (This is only a minor modification of what you have already.)
var div = d3.select("body").selectAll("div")
.data(["enter", "update"], function(d) { return d || this.textContent; });
// 2. update
div.transition()
.duration(duration)
.delay(!div.exit().empty() * duration)
.style("background", "orange");
// 3. enter
div.enter().append("div")
.text(function(d) { return d; })
.style("opacity", 0)
.transition()
.duration(duration)
.delay((!div.exit().empty() + !div.enter().empty()) * duration)
.style("background", "green")
.style("opacity", 1);
// 1. exit
div.exit()
.style("background", "red")
.transition()
.duration(duration)
.style("opacity", 0)
.remove();
http://bl.ocks.org/mbostock/5779682
One tricky thing here is that you have to create the transition on the updating elements before you create the transition on the entering elements; that’s because enter.append merges entering elements into the update selection, and you want to keep them separate; see the Update-only Transition example for details.
Alternatively, you could use transition.transition to chain transitions, and transition.each to apply these chained transitions to existing selections. Within the context of transition.each, selection.transition inherits the existing transition rather than creating a new one.
var div = d3.select("body").selectAll("div")
.data(["enter", "update"], function(d) { return d || this.textContent; });
// 1. exit
var exitTransition = d3.transition().duration(750).each(function() {
div.exit()
.style("background", "red")
.transition()
.style("opacity", 0)
.remove();
});
// 2. update
var updateTransition = exitTransition.transition().each(function() {
div.transition()
.style("background", "orange");
});
// 3. enter
var enterTransition = updateTransition.transition().each(function() {
div.enter().append("div")
.text(function(d) { return d; })
.style("opacity", 0)
.transition()
.style("background", "green")
.style("opacity", 1);
});
http://bl.ocks.org/mbostock/5779690
I suppose the latter is a bit more idiomatic, although using transition.each to apply transitions to selections (rather than derive transitions with default parameters) isn’t a widely-known feature.