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", ...)
Related
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 have data that can have a variable numbers of series. And inside each of those series is a date and number that I want to plot as a scatter plot in D3js.
This is my (non working) code. It works when I do it straight, but not once I add the $.each loop. I'm pretty sure its some sort of problem with indexing or something like that.
var color = d3.scale.category20();
// Now actually add the data to the graph
$.each(mpgData, function(k, v) {
console.log(v);
//console.log(k);
svg.selectAll('circle')
.data(v)
.enter()
.append('circle')
.attr('cx', function(d, i) {
console.log(i);
//console.log(d);
return xScale(getDate(d[1]));
})
.attr('cy', function(dd, ii) {
//console.log(ii);
return yScale(dd[2]);
})
.attr('fill', function(d, i) {
return color(k);
})
.attr("class", "mpgColorClass"+k)
.attr("r", 5)
.on("mouseover", function() {
d3.selectAll(".mpgColorClass"+k)
.attr("r", 8);
})
.on("mouseout", function() {
d3.selectAll(".mpgColorClass"+k)
.attr("r", 5);
});
});
I only showed what I think is the relevant part.
So that code kind of works. But it only shows 6 things, which I think is because the 2nd 'series' has 6 items. So somehow its not looping over everything at the "attr('cx', function(d, i)) part. I think I'm not understanding how to get that function to loop over each part of the series.
I'm new to D3js, so still struggling through the learning curve. But it works and I get a graph out with the correct data. Its just not ALL the data, only 6 points out of the entire (variable) dataset.
Thanks!
in your $.each() block you are overwriting the same set of circles in the SVG element. So instead of using selectAll('circle') you can do this:
$.each(mpgData, function(k, v) {
svg.selectAll('circle' + k)
.data(v)
.enter()
.append('circle')
.attr('class','circle' + k)
});
truncated rest of details in your code... edit at will.
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/
How can d3 areas have their transitions animated? I've seen examples for lines but can't find anything on animating an area.
Eg area:
var area = d3.svg.area()
.x(function(d) { return x(d.x); })
.y0(height)
.y1(function(d) { return y(d.y); });
Update: I've found an example for an area chart but I don't understand it. How is this function creating the area transition?
function transition() {
d3.selectAll("path")
.data(function() {
var d = layers1;
layers1 = layers0;
return layers0 = d;
})
.transition()
.duration(2500)
.attr("d", area);
}
The transition of areas works just like for other attributes. Only in case of areas, we are interpolating strings instead of interpolating numbers. When you call the area function with some data, then it produces a string which looks like M0,213L4,214L9,215 ... L130,255.7, which is a DSL used for the d attribute. When you change the data you pass to the area function, this string changes and D3 interpolates them.
Regarding the example you have found, the interesting bit which causes the transition is only this:
.transition()
.duration(2500)
.attr("d", area);
The other part merely is a fancy way of alternatively returning layers1 and layers0 as the data for the area function on consecutive calls.
d3.selectAll("path")
.data(function() {
var d = layers1;
layers1 = layers0;
return layers0 = d;
})
Thanks #neptunemo for your suggestion. However, your code is too specific for your problem. I would like to take a general case for better illustration of your idea:
Please see the full code from an example of d3noob: https://bl.ocks.org/d3noob/119a138ef9bd1d8f0a8d57ea72355252
Original code of area generator:
var area = d3.area()
.x(function(d) { return x(d.date); })
.y0(height)
.y1(function(d) { return y(d.close); });
Modified code of area generator:
var area = function(datum, boolean) {
return d3.area()
.y0(height)
.y1(function (d) { return y(d.close); })
.x(function (d) { return boolean ? x(d.date) : 0; })
(datum);
}
datum is to take the data,
boolean is to control the:
.x() (in case you want the animation along x-axis)
.y1() (in case you want the animation along y-axis)
By setting boolean to false, we're able to set .x() or .y1() to 0.
This will help us to set the initial state of area before triggering the transition process.
Modified code of area transition:
svg.append("path")
.data([data])
.attr("class", "area")
.attr("d", d => area(d, false))
.attr("fill", "lightsteelblue")
.transition()
.duration(2000)
.attr("d", d => area(d,true));
Effects?
case of controlling .x()
case of controlling .y1()
Note: The issue I met is that I cannot synchronize the animation of line and area :(
Bit late to the party, but:
I solved the problem by modifying the original 'area' function, passing two variables: the data, and the field I wish to chart:
var area = function(datum, field) {
return d3.svg.area()
.x(function(d) {
return xScale(d.period_end);
})
.y0(height)
.y1(function(d) {
return yScale(d[field] || 0);
})(datum);
};
Then when you draw the path, just use basic transition. First time, passing no 'field', resulting in drawing zero values, and then - after transition() - passing the field I wanted:
areaChart.append('path')
.attr('class', 'area')
.attr('d', area(chartData))
.attr('fill', function() {
return chartColor;
})
.attr('opacity', 0.15)
.transition().duration(chartSettings.duration)
.attr('d', area(chartData, 'value'));
Works nicely without the need for sub functions. Exactly the same can of course be done for line charts.
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.