How to make circle to appear one after another using d3 transition? - javascript

I am following the circle example:
I created the circle below, and I wish to make the opacity transition be that as the data set updates, the circle will start appearing one after another. For example, if the data length is 5, then circle 1 appears, then circle 2, ... finally circle 5. And if the data is updated so its length is 2, then circle 1 appears, then circle 2 appears. How do I do this effect? So far, the transition() works on the data set uniformly.
circle.enter().append("circle")
.attr("class", "dot");
// Update (set the dynamic properties of the elements)
circle
.attr("r", 5)
.attr("cy", 20)
.attr("cx", function(d,i){return i*50;})
.attr("fill", "red");
svg.selectAll("circle")
.style("opacity", 0)
.transition()
.duration(1000)
.style("opacity", 1);

Problem:
Setting a delay for each element in a "transition" selection.
Solution:
Use delay() with function(d, i)
Instructions:
You have to add this after transition():
.delay(function(d,i){ return i * someNumber })
Where someNumber is the delay, in milliseconds, for each element.

Related

svg circle too small for mouseevent

I am working on a D3 Graph. My graph has circles with no fill color which look like this:
var circles = svg.selectAll("circle")
.data(x.ticks(6))
.enter().append("circle")
.attr("r", function (d) { return radius(d); })
.style("fill", "none")
.style("stroke", "black")
.style("stroke-dasharray", "3,3")
.style("stroke-width", "1px")
I have added a mouse event to that circle so that whenever someone hovers over it, the circle would get larger width:
.on('mouseenter', function (a, i) {
d3.select(this)
.style("stroke-dasharray", "0")
.style("stroke-width", "3px")
})
However, the width of the circle is too small to be easily touchable without extra effort. What would be a good and efficient solution to make the hit slop bigger so that the mouseevent would trigger with ease?
The best option is control the minimum circle radius(the radius where it is easily touchable).That means, if a circle is not easily touchable when radius < 5px then keep minimum radius as 5px.

Redrawing a key on top of a D3 projection after transition

I'm working with a D3 map projection similar to Mike Bostock's Choropleth seen here.
The issue I'm having is that I've added a transition; and when I transition the projection, the map key (seen in the top right corner) is being covered by the background color of the map.
I know I probably just need to redraw the g layer after the transition, but I'm not able to get that working as expected.
I'm originally drawing the key on the map with the following code:
var g = svg.append("g")
.attr("class", "key")
.attr("transform", "translate(0,40)");
g.selectAll("rect")
.data(color.range().map(function(d) {
d = color.invertExtent(d);
if (d[0] == null) d[0] = x.domain()[0];
if (d[1] == null) d[1] = x.domain()[1];
return d;
}))
.enter().append("rect")
.attr("height", 8)
.attr("x", function(d, i) { return 350 + (i * 30)})
.attr("width", 30)
.attr("fill", function(d) { console.log(d[1]); return color(d[1]); });
g.append("text")
.attr("class", "caption")
.attr("x", x.range()[0])
.attr("y", -6)
.attr("fill", "#000")
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text("Number of Licensed Establishments");
g.call(d3.axisBottom(x)
.tickSize(13)
.tickValues(color.domain()))
.select(".domain")
.remove();
Then I'm transitioning the projection with this code (which also works fine).
path = d3.geoPath(projection);
svg.selectAll("path").transition().duration(2000).attr("d", path);
But the key gets covered. I've tried redrawing it like this:
g.selectAll("g").attr("transform", "translate(0,40)");
It doesn't do anything though. What step am I missing to correctly redraw that g layer on top?
Transitioning a path shouldn't change where it appears in the DOM. Transitioning element attributes with d3 modifies that element in place in the DOM. The following example should demonstrate this (path is appended first and should be behind the text, the path then transitions its d attribute through two d3 symbol paths remaining behind the text):
var svg = d3.select('body').append('svg').attr('width',400).attr('height',200);
var cross = "M-21.213203435596427,-7.0710678118654755L-7.0710678118654755,-7.0710678118654755L-7.0710678118654755,-21.213203435596427L7.0710678118654755,-21.213203435596427L7.0710678118654755,-7.0710678118654755L21.213203435596427,-7.0710678118654755L21.213203435596427,7.0710678118654755L7.0710678118654755,7.0710678118654755L7.0710678118654755,21.213203435596427L-7.0710678118654755,21.213203435596427L-7.0710678118654755,7.0710678118654755L-21.213203435596427,7.0710678118654755Z";
var star = "M0,-29.846492114305246L6.700954981042517,-9.223073285798176L28.38570081386192,-9.223073285798177L10.8423729164097,3.5229005144437298L17.543327897452222,24.146319342950797L1.7763568394002505e-15,11.400345542708891L-17.543327897452215,24.1463193429508L-10.842372916409698,3.522900514443731L-28.38570081386192,-9.22307328579817L-6.7009549810425195,-9.223073285798176Z";
var wye = "M8.533600336205877,4.926876451265144L8.533600336205877,21.9940771236769L-8.533600336205877,21.9940771236769L-8.533600336205877,4.9268764512651435L-23.31422969000131,-3.6067238849407337L-14.78062935379543,-18.387353238736164L0,-9.853752902530289L14.78062935379543,-18.387353238736164L23.31422969000131,-3.6067238849407337Z"
var symbol = svg.append('path')
.attr('transform','translate(100,100)')
.attr('d', cross )
.attr("fill","orange");
var text = svg.append('text')
.attr('x', 100)
.attr('y', 105)
.style('text-anchor','middle')
.text('THIS IS SOME TEXT')
symbol.transition()
.delay(2000)
.attr('d', star )
.duration(2000)
.transition()
.attr('d', wye )
.duration(2000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
Given your example, it is likely that the key is initially rendered behind the features of the map - only there is no overlap between the two. Each appears as intended. When transitioning, with say a zoom, the features overlap and the key is hidden. As noted in the comments, try g.raise() or d3.select(".key").raise() to move the key to the bottom of the parent container, effectively lifting it above other svg elements (as elements are rendered in the order they appear in the DOM, as close as we get to a z-index in svg). You should only need to apply .raise() once - as the transition won't change the ordering, or alternatively, ensure that the key is appended to the svg last.

Simple circle animation is only displayed the first time

I'm trying to put circles on a map every second. This animation consists of 4 circles that are shown once a point is added on the map.
After the first time the animation is not repeated again. I do not know why this happens. When new points are added, the animation does not happen again.
https://plnkr.co/edit/benkcHIINN9DCjvIvtEn?p=preview
var aNumCircles=[1,2,4,5];
function addpoints(){
//add circle on map
var coordenadas=map.latLngToLayerPoint([coordinates[cont].lat,coordinates[cont].long]);
svg.append('circle').attr("cx",coordenadas.x)
.attr("cy", coordenadas.y)
.attr("r", 1)
.style("fill",'red')
.attr("class",'circulo_mapa')
//add animation circles on map
var circle = svg.selectAll("circle").data(aNumCircles).enter().append('circle')
.attr("cx",coordenadas.x)
.attr("cy", coordenadas.y)
.attr("id", cont)
.attr("r", 0)
.style("stroke-width", function(d,i){ return 5 / (i+1) })
.attr("class", 'animation_explosion')
.transition()
.delay(function(d,i){ return Math.pow((i+1), 2.5) * 50 })
.duration(2000)
.ease('quad-in')
.attr("r", 25)
.style("stroke-opacity", 0)
.each("end", function (d,i) {
d3.select(this).remove();
});
cont++;
}
var interval = setInterval(function(){
addpoints();
if(cont==5){
clearInterval(interval);
}
},1000);
The problem is just the first line in this selection:
var circle = svg.selectAll("circle")
.data(aNumCircles)
.enter()
.append("circle")
//etc...
Since there are already circles in the SVG at the second time addpoints() runs, your "enter" selection will be empty.
Instead of that, it should be:
var circle = svg.selectAll(null)
.data(aNumCircles)
.enter()
.append("circle")
//etc...
By using selectAll(null) you can be completely sure that your "enter" selection has all the elements in your data array.
Here is the updated plunker: https://plnkr.co/edit/3u0er01thuj5P8e0XqO6?p=preview

javascript d3.js - Multiple transitions of points on a scatter plot

My objective is to take a set of points, and move them (.transition.duration()) a few times, in series-like fashion.
Example of code:
d3.csv("X.csv", function(csv) {
// initialize circles at random positions
svg.selectAll("circle")
.data(csv)
.enter()
.append("circle")
.attr("cx", function(d) {
return x(80*Math.random());
})
.attr("cy", function(d) {
return y(500*Math.random());
})
.attr("r", function(d) {
return r(Math.sqrt(10*Math.random()));
})
.style("fill", function(d) {
return color(d.A);
})
.style("opacity", 1.0)
.style("stroke-opacity", 1)
.style("stroke-width", 3)
.style("stroke", function(d) {
return stroke(d.B)
});
// Move #1: moving the marks to their position
svg.selectAll("circle")
.transition().duration(2000)
.attr("cx",function(d) {
return x(+d.C);
})
.attr("cy",function(d) {
return y(+d.D);
})
.attr("r",function(d) {
return r(Math.sqrt(+d.E));
})
.style("opacity", 0.8);
//Move #2: move again to highlight
svg.selectAll("circle")
.transition().duration(2000)
.style("opacity", function(d) {
if (d["A"] == "male") {
return 0.1;
} else if (d["A"] == "female") {
return 0.8;
}
});
}
Problem: Running as is, Move #1 is skipped over.
Failed Attempts: If I comment out Move #2 section, then Move #1 works. If I comment out Move #1 section, then Move #2 works.
Ideas considered: I have Googled .delay, setTimeout(), and other options with .exit() and further data bind steps, but I believe there should be something simpler that exists. I have also tried to follow this SO post, but have a hard time following the "General Update Pattern" examples of the first answer.
Question: How do I get Move #1 and Move #2 to work in succession (with possible further Moves #3, #4, etc.)?
Excellent tutorial here
Idea is delay the second transition by the duration of first transition.
So if you have 3 transitions each of duration 1 sec then, delay the second by 1 sec and third by 2 sec, because we have to wait for both first and second transitions to complete. Hope you get the idea.
var canvas = d3.select('body')
.append("svg")
.attr("width",500)
.attr("height",500);
var addcircle = canvas.append("circle")
.attr("cx",50)
.attr("cy",50)
.attr("r",25);
var circles = d3.select('circle');
// first transition
circles.transition().duration(1000)
.attr("cx",250);
// 2nd
circles.transition().delay(1000)
.duration(1000)
.attr("cy",250)
// 3rd
circles.transition().delay(2000)
.duration(1000)
.attr("cx",50)
// 4th
circles.transition().delay(3000)
.duration(1000)
.attr("cy",50);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

generate clipPaths for multiple elements in d3.js

I am trying to create partially filled circles like the ones in the final NYT political convention visualization: http://www.nytimes.com/interactive/2012/09/06/us/politics/convention-word-counts.html
The two clearest code examples I've found for clipPaths in d3 ( https://gist.github.com/1067636 and http://bl.ocks.org/3422480) create individual div elements with unique ids for each clip-path and then apply these paths to single elements.
I can not figure out how to go from these examples to a visualization with a unique circular clipPath for each element in a set of elements based on data values so that I can create my effect.
Here is what I have so far:
Given data with the following structure:
data = [
{value: 500, pctFull: 0.20, name: "20%"},
{value: 250, pctFull: 0.75, name: "75%"},
{value: 700, pctFull: 0.50, name: "50%"},
]
1) Create a force diagram with a circle for each object in the dataset. The area of the circle is derived from the objects value.
2) Calculate k (and h) from a proportion (pctFull) for each datapoint using the algorithm from the mbostock example http://bl.ocks.org/3422480
3) Use k to generate a rect for each datapoint that covers the appropriate area of the circle.
I think the illustration would be done if I could limit the visibility of each rect to its respective circle but this is where I am stuck. I've tried a bunch of things, none of which have worked.
Here's the jsfilddle: http://jsfiddle.net/G8YxU/2/
See a working fiddle here: http://jsfiddle.net/nrabinowitz/79yry/
// blue circle
node.append("circle")
.attr("r", function(d, i) {return rVals[i];})
.style("fill", "#80dabe")
.style("stroke", "#1a4876");
// clip path for the brown circle
node.append("clipPath")
// make an id unique to this node
.attr('id', function(d) { return "clip" + d.index })
// use the rectangle to specify the clip path itself
.append('rect')
.attr("x", function(d, i){ return rVals[i] * (-1);})
.attr("width", function(d, i){ return rVals[i] * 2;})
.attr("y", function(d, i) {return rVals[i] - (2 * rVals[i] * kVals[i]);})
.attr("height", function(d, i) {return 2 * rVals[i] * kVals[i];});
// brown circle
node.append("circle")
// clip with the node-specific clip path
.attr("clip-path", function(d) { return "url(#clip" + d.index + ")"})
.attr("r", function(d, i) {return rVals[i];})
.style("fill", "#dabe80")
.style("stroke", "#1a4876");
It looks like the only way to specify a clip path for an element is to use the url(IRI) notation in the clip-path attribute, which means that you'll need a unique id for each clip path based on the node data. I've used the form clip<node index> for the id - so each node gets its own clip path, and other sub-elements of the node can refer to it.
It seemed easiest, following Mike's example, to make two circles of different colors and use the rectangle itself for the clip path, rather than making a circle-based clip path. But you could do it either way.

Categories

Resources