I have two functions that perform animations on lines. The first function, one is executed at the beginning, in order to perform operations on the enter selection, and only animates the lines' horizontal movement (x1 and x2). The second function two only animates the lines' height (only y2,y1 stays fixed).
Through user events, function one cannot be interrupted by function two, but vice versa (also because the animation in two is considerably longer). This means that, when the transition from two is still running, the user can trigger one.
This gave me serious headaches, because one would somehow take the values of the last state of the running transition of two instead of correctly assigning a data-driven value (i.e. .attr('y2', function(d){ ... });).
http://jsfiddle.net/h39WN/6/ - Please do the following: Click on one. You see that only the horizontal movement is animated as the data changes. You also see that at the end of its execution, the lines should always be ordered from lowest to highest.
Click on two once and wait the full 2 seconds until its animation is completed. Then click on one again. This is the desired behavior.
Now click on two, wait a few ms and then click on one - you see that the lines keep the height of the last state of two's animation, even though they are correctly ordered. (I know the data are not realistic and maybe a bit confusing in this example, but they still allow to replicate the problem).
I then came up with the solution to schedule another, "empty", transition on the lines in one - according to the docs this should cancel the one still running in two when one is invoked:
var one = function () {
var svg = d3.select('svg');
vis.mobileTeams = svg
.selectAll('.teamGroup')
.data(data.values, function (d) {
return d.ID;
});
// ENTER
var teamEnter = vis.mobileTeams
.enter()
.append('g')
.attr('class', 'teamGroup');
// enter line
teamEnter
.append('line')
.attr('class', 'teamWeightedLine');
// UPDATE THE LINE
// HEIGHT - SHOULD NOT BE ANIMATED
svg
.selectAll('.teamGroup line')
.attr('y1', paddingY)
// I inserted a transition here to cancel
// the one triggered by the other function
var lineTransition = svg
.selectAll('.teamGroup line')
.transition()
.attr('y2', function(d){ ... });
// I need to use transition chaining so changing 'y2'
// with a transition does not get
// overwritten by the following transition
// HORIZONTAL POSITION - SHOULD BE ANIMATED
lineTransition
.transition()
.duration(500)
// 'x1' and 'x2' are the only values that
// need to be animated in this function
.attr('x1', function (d) {
return function(d){ ... });
})
.attr('x2', function (d) {
return function(d){ ... });
});
};
And here is the second function.
var two = function () {
var svg = d3.select('svg');
// this is where the only transition concerning HEIGHT is supposed to take place
svg
.selectAll('.teamGroup line')
.transition()
.ease('circle-out')
.duration(2000)
.attr('y2', vis.mobileCalculateScoreToHeight);
console.log('mobile vis updated');
};
Even though this fixes the "interference" problem as two's transition is canceled because another one is scheduled, it brings another problem:
var lineTransition = svg
.selectAll('.teamGroup line')
.transition()
.attr('y2', function(d){ ... });
http://jsfiddle.net/96uN6/8/ This is the fiddle that incorporates this change. Even when two is interrupted by one, do the correct heights result in the end - but:
y2 is now being animated in one too! I know that .transition() brings with it a default duration of 250ms, so I did this:
var lineTransition = svg
.selectAll('.teamGroup line')
.transition()
.duration(0)
.attr('y2', function(d){ ... });
This, in turn, brings another problem: y2 is not set at all now, i.e. the <line>s don't even have it as an attribute:
Weirdly, it works when using a very short duration (so the animation is barely visible), but it only works sometimes and is probably browser-dependent:
var lineTransition = svg
.selectAll('.teamGroup line')
.transition()
.duration(10)
.attr('y2', function(d){ ... });
Setting y2 in the regular selection instead of the transition selection does not work either as it brings back the "interference" problem - as y2 is set when the animation from two is still running.
svg
.selectAll('.teamGroup line')
.attr('y1', paddingY)
.attr('y2', function(d){ ... });
var lineTransition = svg
.selectAll('.teamGroup line')
.transition();
The approach without transition chaining does not work either, of course, because the first transition is immediately canceled by the second and y2 is never set.
svg
.selectAll('.teamGroup line')
.transition()
.duration(10)
.attr('y2', function(d){ ... });
svg
.selectAll('.teamGroup line')
.transition()
.duration(TRANSDURATION)
.attr('x1', function (d) {
return function(d){ ... };
})
.attr('x2', function (d) {
return function(d){ ... };
});
So the only possible solution working for me (the one with the short duration) seems very quirky, there must be a better solution, mustn't it?
Feel free to ask if something is unclear.
Through Mike Bostock (https://github.com/mbostock/d3/issues/1877), I found out that I can use selection.interrupt() to cancel any previous transitions, i.e., already running transitions.
So, the quirky
var lineTransition = svg
.selectAll('.teamGroup line')
.transition()
.duration(0)
.attr('y2', function(d){ ... });
becomes
var lineTransition = svg
.selectAll('.teamGroup line')
.interrupt()
.attr('y2', function(d){ ... });
It's as easy as that.
See: http://jsfiddle.net/96uN6/9/
Related
var IndChart = dc.geoChoroplethChart("#india-chart");
var states = data.dimension(function (d) {
return d["state_name"];
});
var stateRaisedSum = states.group().reduceSum(function (d) {
return d["popolation"];
});
IndChart
.width(700)
.height(500)
.dimension(states)
.group(stateRaisedSum)
.colors(d3.scale.ordinal().domain().range(["#27AE60", "#F1C40F", "#F39C12","#CB4335"]))
.overlayGeoJson(statesJson.features, "state", function (d) { //console.log(d.properties.name);
return d.id;
})
.projection(d3.geo.mercator().center([95, 22]).scale(940))
.renderLabel(true)
.title(function (d) { console.log(d); return d.key + " : " + d.value ;
})
.label(function (d) { console.log(d);}) ;
wanted to add Label or custom value(25%, added in Map chart screen-shots) in map chart for each path using dc.js.
In the comments above, you found or created a working example that answers your original question. Then you asked how to make it work for two charts on the same page.
This is just a matter of getting the selectors right, and also understanding how dc.js renders and redraws work.
First off, that example does
var labelG = d3.select("svg")
which will always select the first svg element on the page. You could fix this by making the selector more specific, i.e. #us-chart svg and #us-chart2 svg, but I prefer to use the chart.select() function, which selects within the DOM tree of the specific chart.
Next, it's important to remember that when you render a chart, it will remove everything and start from scratch. This example calls dc.renderAll() twice, so any modifications made to the first chart will be lost on the second render.
In contrast, a redraw happens when any filter is changed, and it incrementally changes the chart, keeping the previous content.
I prefer to listen to dc.js chart events and make my modifications then. That way, every time the chart is rendered or redrawn, modifications can be made.
In particular, I try to use the pretransition event whenever possible for modifying charts. This happens right after drawing, so you have a chance to change things without any glitches or pauses.
Always add event listeners before rendering the chart.
Adding (the same) handler for both charts and then rendering, looks like this:
usChart.on('pretransition', function(chart) {
var project = d3.geo.albersUsa();
var labelG = chart.select("svg")
.selectAll('g.Title')
.data([0])
.enter()
.append("svg:g")
.attr("id", "labelG")
.attr("class", "Title");
labelG.selectAll("text")
.data(labels.features)
.enter().append("svg:text")
.text(function(d){return d.properties.name;})
.attr("x", function(d){return project(d.geometry.coordinates)[0];})
.attr("y", function(d){return project(d.geometry.coordinates)[1];})
.attr("dx", "-1em");
});
usChart2.on('pretransition', function(chart) {
var project = d3.geo.albersUsa();
var labelG = chart.select("svg")
.selectAll('g.Title')
.data([0])
.enter()
.append("svg:g")
.attr("id", "labelG")
.attr("class", "Title");
labelG.selectAll("text")
.data(labels.features)
.enter().append("svg:text")
.text(function(d){return d.properties.name;})
.attr("x", function(d){return project(d.geometry.coordinates)[0];})
.attr("y", function(d){return project(d.geometry.coordinates)[1];})
.attr("dx", "-1em");
});
dc.renderAll();
I used one more trick there: since pretransition happens for both renders and redraws, but we only want to add these labels once, I use this pattern:
.selectAll('g.Title')
.data([0])
.enter()
.append("svg:g")
.attr("class", "Title");
This is the simplest data binding there is: it says we want one g.Title and its data is just the value 0. Since we give the g element the Title class, this ensures that we'll add this element just once.
Finally, the result of this expression is an enter selection, so we will only add text elements when the Title layer is new.
Fork of your fiddle.
The original Code can be found at: http://bl.ocks.org/Guerino1/3a51eeb95d3a8345bc27370e8c9d5b77
I have numerous polygons that are transitioning onto an svg canvas (from left to right, at the bottom of the HTML page).
The code I use to create an transition the chevrons leverages D3 Polygon:
// Create Polygons for SDLC
svgCanvas.selectAll("a")
.data(dataSet)
.enter().append("a")
.attr("xlink:href", function(d) { return d.link; })
.append("svg:polygon")
//svgCanvas.selectAll("polygon")
//.data(dataSet)
//.enter().append("polygon")
.attr("id", function(d,i){ return (selectString.replace(/ /g,'_').replace(/#/g,'') + "_index_" + i); })
.attr("originalcolor","violet")
.style("stroke","blue")
.style("fill","violet")
.style("stroke-width",2)
.attr("points", origin)
.on('mouseover', chevronMouseOver)
.on("mouseout", chevronMouseOut)
.on("click", chevronMouseOut)
.transition() // <------- TRANSITION STARTS HERE --------
.duration(3000)
.attr("points", calculateChevron);
Currently, all polygons transition into the svg canvas, together. I'd like to put a delay between each of them, so that it looks more like dealing from a deck of cards, one at a time.
How would I properly add a D3 delay to make this happen?
Thanks for any help you can offer.
try this..
.transition() // <------- TRANSITION STARTS HERE --------
.delay(function(d,i){ return 100*i; })
.duration(3000)
.attr("points", calculateChevron);
Original Code can be found at: http://bl.ocks.org/Guerino1/451f4c47842967dd813c8a64b24f7686
Problem: Applying .transition() code to different polygon sets appears to yield different results.
The following portion of the code seems to work as expected. Applying a transition causes the chevrons to transition onto the svg canvas, from left to right...
svgChevronsCanvas.selectAll("a")
.data(dataSet)
.enter().append("a")
.attr("xlink:href", function(d) { return d.link; })
.append("svg:polygon")
.attr("id", function(d,i){ return ("chevron_" + selectString.replace(/ /g,'_').replace(/#/g,'') + "_index_" + i); })
.attr("originalcolor", polygonPrimaryColor)
//.style("stroke","blue")
//.style("stroke-width",1)
.style("fill", polygonPrimaryColor)
.attr("points", chevronOrigin)
.on('mouseover', chevronMouseOver)
.on("mouseout", chevronMouseOut)
.on("click", chevronMouseOut)
.transition() // <------------------- TRANSITION HERE
.duration(3000)
.attr("points", calculateChevron);
The following code, which attempts to follow the same pattern as above does not seem to work as expected. Given the transition, I would expect the textboxes (in light blue below the chevrons), which are also drawn using D3 polygons, to transition onto their svg canvas from left to right, just like the chevron polygons in the above code...
svgTextboxesCanvas.selectAll("a")
.data(dataSet)
.enter().append("a")
.attr("xlink:href", function(d) { return d.link; })
.append("svg:polygon")
.attr("id", function(d,i){ return ("textbox_" + selectString.replace(/ /g,'_').replace(/#/g,'') + "_index_" + i); })
.attr("originalcolor", textboxColor)
.style("stroke", textboxColor)
.style("stroke-width",1)
.style("fill", textboxColor)
.attr("points", textboxesOrigin)
.on('mouseover', textboxMouseOver)
.on("mouseout", textboxMouseOut)
.on("click", textboxMouseOut)
.transition()
.duration(3000)
.attr("points", calculateTextbox);
Question: How do I properly add transitions to the D3 polygons that are built to look like rectangles (below the chevrons), in the latter set of code, and make them transition into the page just like the dark blue chevrons above them?
In the original code:
Make
var chevronGapSpace = 5;//this is the distance between each rectangle.
var slantDepth = 0;//to make the polygon rectangle.
Next, to make rectangle transition inside function calculateChevron change the calculations accordingly:
function calculateChevron(d, i){
return [
[(svgMargin) + i*(chevronDistance),chevronTopOffset],
[(svgMargin + chevronWidth - slantDepth) + i*(chevronDistance),chevronTop],
[(svgMargin + chevronWidth - slantDepth) + i*(chevronDistance),chevronPointY],
[(svgMargin + chevronWidth - slantDepth) + i*(chevronDistance),chevronBottom],
[(svgMargin) + i*(chevronDistance),chevronBottom],
[(svgMargin + slantDepth) + i*(chevronDistance),chevronPointY]
];
};
working code here
I am trying to draw a circular heat or ring-chart. There are several options it seems with d3js. The most popular appears to use the pie layout to make several donut rings Another option is to use a circular heat chart like this one -
Both of these however use filling segments as their way of depicting area size. I wanted however to use lines to depict events over time. With each line occurring within a particular ring.
To get this effect, I've adapted this radial weather chart - http://bl.ocks.org/susielu/b6bdb82045c2aa8225f5
This is my attempt so far:
http://blockbuilder.org/jalapic/12a3a23651f40283d489
It does not have labeling, but each ring (12 total) represents an individual subject. Each segment represents a sample of time (says months here but could be anything). The lines are drawn within each ring that they belong to. I have kept the same variable names as the weather example to enable comparisons between my stripped down code and the author's original code.
This is what it looks like:
My question is how might it be possible to mouseover each ring to make only that ring's contents (i.e. lines) remain visible, i.e. to hide the other rings - this would make viewing the chart easier.
Here is the code for how the rings are made up:
var mycircles = [110,100, 90, 80, 70, 60,50,40,30,20,10,0]
origin.selectAll('circle.axis-green')
.data(mycircles) //original circles
.enter()
.append('circle')
.attr('r', function(d) { return rScale(d)})
.style("fill", "#fff8ee")
.style("opacity", .05)
.attr('class', 'axis record')
.on("mouseover", function(d) {d3.select(this).style("fill", "red");})
.on("mouseout", function(d) {d3.select(this).style("fill", "#fff8ee");
});
As can be seen the rings are actually overlapping circles. Is there a way to achieve what I'm trying to do using the approach I'm taking, or would I have to go back to working something out with segments like in the heatchart or pie layouts?
Looking at your data and code, one method would be to assign a class to each line representing it's ring position. You can then use mouseover and mouseout events to toggle the opacity of those lines.
First, create a couple helper functions:
// which ring is currently highlighted
var curRing = null;
// show all rings
function unShowRing(){
d3.selectAll(".record")
.style("opacity", 1);
curRing = null;
}
// only show current ring
function showRing(ringId){
// all lines that are not in my ring, hide them
d3.selectAll(".record:not(.ring" + ringId + ")")
.style("opacity", 0);
curRing = ringId;
}
Set up the lines a little different:
...
.enter().append('line')
// assign a unique class to each ring's lines
.attr('class', function(d) {
return cl + " ring" + d.recLow/10;
})
// on mouseover only show my ring
.on("mouseover", function(d){
var ringId = d.recLow/10;
showRing(ringId);
})
// on mouseout show all rings
.on("mouseout", function(d){
unShowRing();
})
// this will prevent lines transitioning in from being shown
.style('opacity', function(d){
if (!curRing){
return 1;
} else {
var ringId = d.recLow/10;
return ringId === curRing ? 1 : 0;
}
})
Finally, you'll need to handle the ring "circle" mouseovers as well in case the user mouses over lines or rings:
origin.selectAll('circle.axis-green')
.data(mycircles) //original circles
...
.on("mouseover", function(d) {
d3.select(this).style("fill", "red");
var ringId = d/10;
showRing(ringId);
})
.on("mouseout", function(d) {
d3.select(this).style("fill", "#fff8ee");
unShowRing();
});
Here's the whole thing working.
I'm making a dripping paint effect using d3.js see fiddle and relevant code:
.append("line")
.attr("x1", function(d){
return xScale(d)})
.attr("y1", 0)
.attr("x2", function(d){
return xScale(d)})
.transition().delay(function (d,i){ return i * 500;})
.duration(10000)
.attr("y2", function(d,i){
return yScale(i) ;
})
line.style("stroke", function() {
var colors = ["rgba(242,100,5,0.7)","rgba(32,144,209,0.7)","rgba(203,214,86,0.7)"];
var colorscale = Math.floor(Math.random() * colors.length);
var randomcolors = colors[colorscale];
return randomcolors;
});
line.style("stroke-width", function(d){
return strokeWidth[d] + "px" });
line.style("stroke-opacity", 1);
line.style("stroke-linecap", "round");
Its kind of working but I can't work out how to apply the transition to the line length only. Currently transition is applied to line weight as well as line length. Thanks for your help in advance
By applying the transition when saving the variable, all the subsequent calls to set attributes apply to the end of the transition. To prevent this, add a semicolon after appending the line and apply everything to the saved variable.
See the updated jsfiddle. The only changes are basically
...
.append("line");
line.attr("x1", ...)