Fill Transition on a circle based on time - d3.js

This is a link which shows filling up a circle with 2 colors.
http://jsfiddle.net/9ChXk/
This is the code:
var r = 100;
var svg = d3.select("body").append("svg");
var grad = svg.append("defs").append("linearGradient").attr("id", "grad")
.attr("x1", "0%").attr("x2", "0%").attr("y1", "100%").attr("y2", "0%");
grad.append("stop").attr("offset", "50%").style("stop-color", "lightblue");
grad.append("stop").attr("offset", "50%").style("stop-color", "white");
svg.append("circle")
.attr("cx", r)
.attr("cy", r)
.attr("r", r)
.attr("stroke", "blue")
.attr("fill", "url(#grad)");
I want the circle to keep changing its fill based on time...something like this link->
http://invision-web.net/ticket-status/
how can it be done in d3.js? Please help me out!

Changing the 'y1' attribute of grad does the trick. You can even add transitions for a smooth effect:
grad.transition().duration(1000).attr("y1","20%")
JsFiddle: http://jsfiddle.net/chrisJamesC/9ChXk/18/
EDIT: To have a chain of transitions:
var data = ["10%","40%","80%","20%","60%","90%","40%","50%","80%","20%"];
var data2 = [10,40,80,20,60,90,40,50,80,20]
var position = 0;
window.setInterval(function() {
grad.transition().duration(1000).attr("y1",((100-data2[position])*2).toString()+'%');
position = (position+1)%data2.length;
},1000)
JsFiddle: http://jsfiddle.net/chrisJamesC/9ChXk/20/

Related

Rotating an SVG image using D3.JS not working

I've been trying to rotate some gears in an svg image clockwise repeatedly using D3.js but I can't seem to understand what's wrong with the code or the image. I was able to translate and rotate each gear using this code below...
d3.selectAll(".st6")
.transition()
.delay(function(d, i, n) { return i * 50; })
.on("start", function repeat() {
d3.active(this)
.transition()
.duration(2500)
.attr('transform', 'rotate(0)')
.transition() //And rotate back again
.duration(2500)
.attr('transform' , 'rotate(90) ')
.on("start", repeat); //at end, call it again to create infinite loop
});
But when I tried using the same code that did rotate a text repeatedly for me, the image became static and not moving again...
here is the code that rotate a text repeatedly for me...
var width = 600;
var height = 300;
var holder = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
//draw the text
holder.append("text")
.style("fill", "black")
.style("font-size", "56px")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.attr("transform", "translate(300,150) rotate(0)")
.text("Hi, how r u doing");
var i = 0;
var timeInterval = 10;
setInterval(function(){
i += 1;
update(i % 360)
}, timeInterval);
var n;
// update the element
function update(n) {
// rotate the text
holder.select("text")
.attr("transform", "translate(300,150) rotate("+n+")");
}
Here is what I tried replicating the above method but to no avail...
var width = 600;
var height = 300;
var holder = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
holder.append("svg:image")
.attr("image-anchor", "middle")
.attr("xlink:href", "produc.svg")
.attr("transform", "translate(300,150) rotate(0)")
// Initial starting angle of the text
var i = 0;
var timeInterval = 10;
setInterval(function(){
i += 1;
update(i % 360)
},timeInterval);
var n;
// update the element
function update(n) {
// rotate the text
holder.select("text")
.attr("transform", "translate(300,150) rotate("+n+")");
}
Here is a working example on CodePenclick here
As for the problem with your code (without considering if it is "a sledgehammer to crack a nut"):
You're taking working code that rotates text and not fully updating it to reflect that you are now working with an image. You have updated the append statement to append an image, but you haven't updated the update function to select that image - it's still looking for a text element:
function update(n) {
// rotate the text
holder.select("text")
.attr("transform", "translate(300,150) rotate("+n+")");
}
Since there is no longer a text element, this function doesn't select anything and consequently, doesn't set any element's transform. AS noted in the comment you need to select the image:
function update(n) {
// rotate the text
holder.select("image")
.attr("transform", "translate(300,150) rotate("+n+")");
}
As seen below:
var width = 600;
var height = 300;
var holder = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
holder.append("svg:image")
.attr("image-anchor", "middle")
.attr("xlink:href", "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/compass.svg")
.attr("transform", "translate(200,20) rotate(0)")
.attr("width", 100)
// Initial starting angle of the text
var i = 0;
var timeInterval = 10;
setInterval(function(){
i += 1;
update(i % 360)
},timeInterval);
// update the element
function update(n) {
// rotate the text
holder.select("image")
.attr("transform", "translate(200,20) rotate("+n+",50,50)");
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

How to move points in an orthogonal map?

I am trying to add red points at certain geolocations on the following map created by Mike Bostock. https://bl.ocks.org/mbostock/3795040. My points show up but don't move with the map. How do I edit the code to make the points move with the map. Thank you.
//add circles to svg
svg.selectAll("circle")
.data([wr,pt,sd,jp,fm])
.enter()
.append("circle")
.attr("cx", function (d) { console.log(projection(d)); return projection(d)[0]; })
.attr("cy", function (d) { return projection(d)[1]; })
.attr("r", "8px")
.attr("fill", "red");
The following is what the above array being referenced.
//points
var wr = [32.6130007, -83.624201];
var pt = [48.9334357, 8.961208];
var sd = [32.715738, -117.1610838];
var jp = [35.6894875, 139.6917064];
var fm = [39.1137602, -76.7267773];
You have to include the circles in the mousemove function:
svg.on("mousemove", function() {
var p = d3.mouse(this);
projection.rotate([λ(p[0]), φ(p[1])]);
svg.selectAll("path").attr("d", path);
//change the circles' positions here:
svg.selectAll("circle").attr("cx", function(d) {
console.log(projection(d));
return projection(d)[0];
})
.attr("cy", function(d) {
return projection(d)[1];
})
});
Here is the updated bl.ocks: https://bl.ocks.org/anonymous/2a6f07cdc12838b296674470ad715bbe/54d6de8d73347081f900c88a203019df74f23ade
PS: Some circles appear to move wrongly: they are correct, though. The thing is that they are behind the globe. To hide those circles you'll have to write another function (which is outside the scope of this answer).
An alternative to hide the circles behind the globe is using a path instead a circle. That way, the projection will automatically clip those paths. Have a look: https://bl.ocks.org/anonymous/9e640195e2c021cd79b5ca9b2238a44c/1c43719a7d6a85d0226cf3c468ac23e570add22d

Choropleth map scale and legend

Let me preface this by saying I am brand new to D3.js and coding in general. I am an infographic artist and I've been using QGIS to generate maps, but am trying to use D3.js to generate a choropleth map for a story about Opioid deaths. Basically I am trying to recreate this map.
map from the Economist
I have tried to start by using this map by Mike Bostock and changing some of the parameters but am getting stuck with the color range and scale. The measurement is 1 per 100,000 population. I have a domain that starts at 1.543385761 and ends at 131.0814217.
The code I'm struggling with is around the scale input and output:
var x = d3.scaleLinear()
.domain([0, 132])
.rangeRound([600, 860]);
var color = d3.scaleThreshold()
.domain(d3.range(2, 10))
.range(d3.schemeBlues[9]);
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) { return x(d[0]); })
.attr("width", function(d) { return x(d[1]) - x(d[0]); })
.attr("fill", function(d) { return color(d[0]); });
I can see that I need some bit of code that will define everything 25 and over as the darkest color. Not even sure I want that to be my final legend but I'd love to know how to reproduce that. I am shocked I was able to get this far but feel a bit lost right now. thank you in advance!
Let's examine your scale:
var color = d3.scaleThreshold()
.domain(d3.range(2, 10))
.range(d3.schemeBlues[9]);
Your domain is an array of created like so:
d3.range(2,10) // [2,3,4,5,6,7,8,9]
These are your thresholds, colors will be mapped based on values that are less than or equal to 2, more than two up to three, more than three and up to four .... and over 9. This domain is mapped to nine values defined in the range:
d3.schemeBlues[9] // ["#f7fbff", "#deebf7", "#c6dbef", "#9ecae1", #6baed6", #4292c6", "#2171b5", "#08519c", "#08306b"]
To set the thresholds for those colors so that values over 25 are one color, define the domain with array that has the appropriate threshold(s):
.domain([2,3,4,5,6,7,8,25]);
In the snippet below, this domain is applied. Rectangles have colors dependent on their location, all rectangles after the 25th (count left to right then line by line) one will be of one color.
var color = d3.scaleThreshold()
.domain([2,3,4,5,6,7,8,25])
.range(d3.schemeBlues[9]);
var svg = d3.select("body")
.append("svg")
.attr("width",500)
.attr("height",500);
var rects = svg.selectAll("rect")
.data(d3.range(100))
.enter()
.append("rect")
.attr("width",15)
.attr("height", 15)
.attr("y", function(d,i) { return Math.floor(i / 10) * 20 + 10 })
.attr("x", function(d,i) { return i % 10 * 20 })
.attr("fill", function(d) { return color(d); })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>

D3 scatter plot - visible points outside the chart after zoom

I'm new to D3 and I've a question related to adding zoom into a chart.
I built a scatter plot with zoom/pan and everything is working except for the fact that when I use the zoom on the chart, I can see some points outside the chart "area" and I really want to avoid that.
The zoom behavior looks like this:
var zoom = d3.behavior.zoom()
.x(xScale)
.y(yScale)
.scaleExtent([1, 10])
.on("zoom", zoomed);
And the zoomed function like this:
function zoomed() {
var panX = d3.event.translate[0];
var panY = d3.event.translate[1];
var scale = d3.event.scale;
panX = panX > 10 ? 10 : panX;
var maxX = -(scale-1)*width-10;
panX = panX < maxX ? maxX : panX;
panY = panY > 10 ? 10 : panY;
var maxY = -(scale-1)*height-10;
panY = panY < maxY ? maxY : panY;
zoom.translate([panX, panY]);
main.select(".x.axis").call(xAxis);
main.select(".y.axis").call(yAxis);
main.selectAll("circle")
.attr("cx", function (d,i) { return xScale(d[0]); } )
.attr("cy", function (d) { return yScale(d[1]); } )
.attr("r", 5);
}
You can see a working example (and all the code) here
Is there a way I can define the "area" of scope for the zoom, so any point outside this area is not visible? or can I add something into the zoomed function to fix this?
Thank you very much.
One option would be to restrict the g region of the element in which your points are drawn using a clip path
See http://jsfiddle.net/henbox/duwyay3y/1/ for the code.
First define your clip path:
var clip = main.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("id", "clip-rect")
.attr("x", "0")
.attr("y", "0")
.attr('width', width)
.attr('height', height);
then add to the g element
main.append("g").attr("clip-path", "url(#clip)")
.selectAll("circle")
...

trying to rotate a globe in d3, centering on a particular country

There is probably a simple answer to this question... . I'm using d3 to create a globe, showing all countries. I also have a div with the name of all the countries in it. When I click on a country name, I want the globe to spin to that country. But I'm having trouble getting the syntax right. Can anyone help, please?
var feature;
var projection = d3.geo.azimuthal()
.scale(zoom)
.origin([-71.03,42.37])
.mode("orthographic")
.translate([380, 450]);
var circle = d3.geo.greatCircle()
.origin(projection.origin());
var scale = {
orthographic: 380,
stereographic: 380,
gnomonic: 380,
equidistant: 380 / Math.PI * 2,
equalarea: 380 / Math.SQRT2
};
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("#globe").append("svg:svg")
.attr("width", 800)
.attr("height", 800)
.on("dblclick", dblclick)
.on("mousedown", mousedown);
var g = svg.append("g");
d3.json("simplified.geojson", function(collection) {
g.append("g")
.attr("id", "countries")
g.append("g")
.selectAll("path")
.data(collection.features)
.enter().append("svg:path")
.attr("d", clip)
.attr("id", function(d) { return d.properties.ISO3; })
.attr("fill", function(d) { return d.properties.FILL; }) //change color and make clickable if data on this country exists
.on("mouseover", pathOver)
.on("mouseout", pathOut)
.on( "dblclick", dblclick)
.on("mousewheel.zoom", null)
.on("click", click);
feature = svg.selectAll("path");
feature.append("svg:title")
.text(function(d) { return d.properties.NAME; });
//here is where I want to be able to click a country name in the div and have the globe rotate to that country:
$('.represented').click(function(){
var countryabbrev = $(this).attr('id');
projection.origin(projection.invert(#path.centroid(#CAN))); //this line is wrong
refresh(1500);
showPerson(countryabbrev)
});
I've gotten it to find the country and rotate. Now the rotate is sketchy, but at least there's progress:
$('.represented').click(function(){
var countryabbrev = $(this).attr('id');
getCentroid(d3.select("#" + countryabbrev));
//projection.origin(projection.invert(#path.centroid(#CAN)));
projection.origin(getCentroid(d3.select("#" + countryabbrev)));
refresh(1500);
//showPerson(countryabbrev)
});
function getCentroid(selection) {
// get the DOM element from a D3 selection
// you could also use "this" inside .each()
var element = selection.node(),
// use the native SVG interface to get the bounding box
bbox = element.getBBox();
// return the center of the bounding box
return [bbox.x + bbox.width/2, bbox.y + bbox.height/2];
}

Resources