How to keep d3 orthographic projection centered when zooming - javascript

I am trying to replicate the zoom functionality shown in Jason Davies "Rotate the World" visualization (https://www.jasondavies.com/maps/rotate/)
I am able to rotate and zoom, however, if I zoom after rotating, my projection is zoomed at an angle (meaning, if I turn the globe 15 degrees to the left and then zoom, the globe no longer stays centered in the canvas). Below is my code, any help would be greatly appreciated!
d3.select(window)
.on("mousemove", mousemove)
.on("mouseup", mouseup);
var width = 960,
height = 500;
var proj = d3.geo.orthographic()
.scale(220)
.translate([width / 2, height / 2])
.clipAngle(90);
var path = d3.geo.path().projection(proj).pointRadius(1.5);
var graticule = d3.geo.graticule();
var svg = d3.select("#content").append("svg")
.attr("width", width)
.attr("height", height)
.on("mousedown", mousedown);
var zoom = d3.behavior.zoom()
.center([width / 2, height / 2])
//.scaleExtent([.5, 10])
.on("zoom", zoomed);
svg.call(zoom);
queue()
.defer(d3.json, "/static/json/world.json")
.await(ready);
function ready(error, world) {
/*
Define gradients
*/
// ocean
var ocean_fill = svg.append("defs").append("radialGradient")
.attr("id", "ocean_fill")
.attr("cx", "75%")
.attr("cy", "25%");
ocean_fill.append("stop").attr("offset", "5%").attr("stop-color", "#777");
ocean_fill.append("stop").attr("offset", "100%").attr("stop-color", "#555");
// globe highlight
var globe_highlight = svg.append("defs").append("radialGradient")
.attr("id", "globe_highlight")
.attr("cx", "75%")
.attr("cy", "25%");
globe_highlight.append("stop")
.attr("offset", "5%").attr("stop-color", "#bbb")
.attr("stop-opacity","0.6");
globe_highlight.append("stop")
.attr("offset", "100%").attr("stop-color", "#999")
.attr("stop-opacity","0.2");
// globe shadow
var globe_shading = svg.append("defs").append("radialGradient")
.attr("id", "globe_shading")
.attr("cx", "50%")
.attr("cy", "40%");
globe_shading.append("stop")
.attr("offset","50%").attr("stop-color", "#333")
.attr("stop-opacity","0");
globe_shading.append("stop")
.attr("offset","100%").attr("stop-color", "#111")
.attr("stop-opacity","0.3");
// drop shadow
var drop_shadow = svg.append("defs").append("radialGradient")
.attr("id", "drop_shadow")
.attr("cx", "50%")
.attr("cy", "50%");
drop_shadow.append("stop")
.attr("offset","20%").attr("stop-color", "#000")
.attr("stop-opacity",".5");
drop_shadow.append("stop")
.attr("offset","100%").attr("stop-color", "#000")
.attr("stop-opacity","0");
/*
Draw globe objects
*/
// drop shadow
svg.append("ellipse")
.attr("cx", 440).attr("cy", 450)
.attr("rx", proj.scale()*.90)
.attr("ry", proj.scale()*.25)
.attr("class", "noclicks")
.style("fill", "url(#drop_shadow)");
// globe
svg.append("circle")
.attr("cx", width / 2).attr("cy", height / 2)
.attr("r", proj.scale())
.attr("class", "noclicks")
.style("fill", "url(#ocean_fill)");
// land
svg.append("path")
.datum(topojson.feature(world, world.objects.land))
.attr("class", "land")
.attr("d", path);
svg.append("path")
.datum(graticule)
.attr("class", "graticule noclicks")
.attr("d", path);
svg.append("circle")
.attr("cx", width / 2).attr("cy", height / 2)
.attr("r", proj.scale())
.attr("class","noclicks")
.style("fill", "url(#globe_highlight)");
svg.append("circle")
.attr("cx", width / 2).attr("cy", height / 2)
.attr("r", proj.scale())
.attr("class","noclicks")
.style("fill", "url(#globe_shading)");
/* svg.append("g").attr("class","points")
.selectAll("text").data(places.features)
.enter().append("path")
.attr("class", "point")
.attr("d", path);
svg.append("g").attr("class","labels")
.selectAll("text").data(places.features)
.enter().append("text")
.attr("class", "label")
.text(function(d) { return d.properties.name })*/
svg.append("g").attr("class","countries")
.selectAll("path")
.data(topojson.feature(world2, world2.objects.countries).features)
.enter().append("path")
.attr("d", path);
}
// modified from http://bl.ocks.org/1392560
var m0, o0;
function mousedown() {
m0 = [d3.event.pageX, d3.event.pageY];
o0 = proj.rotate();
d3.event.preventDefault();
}
function mousemove() {
if (m0) {
var m1 = [d3.event.pageX, d3.event.pageY]
, o1 = [o0[0] + (m1[0] - m0[0]) / 6, o0[1] + (m0[1] - m1[1]) / 6];
o1[1] = o1[1] > 30 ? 30 :
o1[1] < -30 ? -30 :
o1[1];
proj.rotate(o1);
refresh();
}
}
function mouseup() {
if (m0) {
mousemove();
m0 = null;
}
}
function refresh() {
svg.selectAll(".land").attr("d", path);
svg.selectAll(".countries path").attr("d", path);
svg.selectAll(".graticule").attr("d", path);
svg.selectAll(".point").attr("d", path);
//position_labels();
}
var slast = 1;
function zoomed() {
if (slast != d3.event.scale) {
svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
slast = d3.event.scale;
};
}

Related

Cannot append text to the d3.js force simulation circles

(function(){
//width and height
var width = 800,
height = 500;
//appending svg to the body and defining the canvas
var svg = d3.select("#chart")
.append("svg")
.attr("height", height)
.attr("width", width)
.append("g")
.attr("transform","translate(0,0)");
d3.queue()
.defer(d3.csv, "result1.csv")
.await(ready);
function ready(error, datapoints){
if(error){
console.log(error);
}else{
var color = d3.scaleLinear(datapoints)
.domain([1, 77])
.range(["#ffffff","#DD4B39"]);
var radiusScale = d3.scaleSqrt().domain([1, 10]).range([2,80]);
var simulation = d3.forceSimulation()
.force("x", d3.forceX(width/2).strength(0.05))
.force("y", d3.forceY(height/2).strength(0.05))
.force("center", d3.forceCenter().x(width * .5).y(height * .5))
.force("collide", d3.forceCollide(function(d){
return radiusScale(d.rad);
}));
var nodes = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(datapoints)
.enter()
.append("g")
.attr("transform", function(d, i) {
return "translate(" + width/12 + "," + height/12 + ")";
});
nodes.append("circle")
.attr("r", function(d){
return radiusScale(d.rad);
})
.attr("class","bubble")
.attr("stroke","#5e0000")
.attr("fill","red")
.attr("fill", function(d) {
return color(d.rad);
});
simulation.nodes(datapoints)
.on('tick',ticked);
function ticked(){
nodes
.attr("cx",function(d){return d.x})
.attr("cy",function(d){return d.y});
}
nodes.append("text")
.attr("class","system")
.attr("text-anchor", "middle")
.text(function(d) {
return d.rad;
});
}
}
})();
Above is my script. Im trying to append text the circles which has d3.js force simulations. I tried several methods but cannot append text to the circles. Please give me a solution for this. When i set simulation to the bubble class as
var nodes = d3.selectAll(".bubble");
, then it simulates only the circles. But text is invisible. plaese help.

d3 US state map with markers, zooming transform issues

I've created a d3 map with US states, following this example:
http://bl.ocks.org/mbostock/4699541
and added markers following this SO question:
Put markers to a map generated with topoJSON and d3.js
The problem is that on zoom, the map markers stay in place. I believe I need to translate them into a new position, but not sure how to make that happen.
var width = 900,
height = 500,
active = d3.select(null);
var projection = d3.geo.albersUsa()
.scale(1000)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select(".rebates").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height)
.on("click", reset);
var g = svg.append("g")
.style("stroke-width", "1.5px");
d3.json("/files/d3-geo/us.json", function(error, us) {
if (error) { throw error; }
g.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path)
.attr("class", function(item) {
return window.US_STATES[item.id].water_authorities > 0 ? 'avail' : 'unavail';
})
.on("click", clicked);
g.append("path")
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
.attr("class", "mesh")
.attr("d", path);
});
d3.json('/files/coordinates.json', function(error, coords) {
if (error) { throw error; }
svg.selectAll(".mark")
.data(coords)
.enter()
.append("image")
.attr('class','mark')
.attr('width', 20)
.attr('height', 20)
.attr("xlink:href",'assets/gmap_red.png')
.attr("transform", function(d) {
return "translate(" + projection([d[1],d[0]]) + ")";
});
});
function clicked(d) {
if (active.node() === this) { return reset(); }
if (window.US_STATES[d.id].water_authorities === 0) { return; }
active.classed("active", false);
active = d3.select(this).classed("active", true);
var bounds = path.bounds(d),
dx = bounds[1][0] - bounds[0][0],
dy = bounds[1][1] - bounds[0][1],
x = (bounds[0][0] + bounds[1][0]) / 2,
y = (bounds[0][1] + bounds[1][1]) / 2,
scale = .9 / Math.max(dx / width, dy / height),
translate = [width / 2 - scale * x, height / 2 - scale * y];
g.transition()
.duration(750)
.style("stroke-width", 1.5 / scale + "px")
.attr("transform", "translate(" + translate + ")scale(" + scale + ")");
}
function reset() {
active.classed("active", false);
active = d3.select(null);
rebatesTable.clear().draw();
g.transition()
.duration(750)
.style("stroke-width", "1.5px")
.attr("transform", "");
}
Step 1
Add all the points in the group and not in the svg.
This will ensure that the marker points translate with the main group.
g.selectAll(".mark")//adding mark in the group
.data(marks)
.enter()
.append("image")
.attr('class', 'mark')
.attr('width', 20)
.attr('height', 20)
.attr("xlink:href", 'https://cdn3.iconfinder.com/data/icons/softwaredemo/PNG/24x24/DrawingPin1_Blue.png')
.attr("transform", function(d) {
return "translate(" + projection([d.long, d.lat]) + ")";
});
Step2
Negate the scaling effect of the main group. else the markers will come zoomed up.
g.selectAll(".mark")
.transition()
.duration(750)
.attr("transform", function(d) {
var t = d3.transform(d3.select(this).attr("transform")).translate;//maintain aold marker translate
return "translate(" + t[0] +","+ t[1] + ")scale("+1/scale+")";//inverse the scale of parent
});
Step3
On zoom out make the marker scale back to 1.
g.selectAll(".mark")
.attr("transform", function(d) {
var t = d3.transform(d3.select(this).attr("transform")).translate;
console.log(t)
return "translate(" + t[0] +","+ t[1] + ")scale("+1+")";
});
Working code here
Hope this helps!

rotate points from csv on d3 orthogonal projection

I have been trying to project a heat map with data loaded from csv onto a orthogonal projection on D3. While rotating the earth (i.e. D3, orthogonal projection), the points/circles remain static. I have tried many combinations but failed to figure out what is missing.
Basically, i need the small circles move along the path of countries.
Here is the complete code :
<script>
var width = 600,
height = 500,
sens = 0.25,
focused;
//Setting projection
var projection = d3.geo.orthographic()
.scale(245)
.rotate([0,0])
.translate([width / 2, height / 2])
.clipAngle(90);
var path = d3.geo.path()
.projection(projection);
//SVG container
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// Define the gradient
var gradient = svg.append("svg:defs")
.append("svg:linearGradient")
.attr("id", "gradient")
.attr("x1", "0%")
.attr("y1", "0%")
.attr("x2", "100%")
.attr("y2", "100%")
.attr("spreadMethod", "pad");
// Define the gradient colors
gradient.append("svg:stop")
.attr("offset", "0%")
.attr("stop-color", "#FFFF00")
.attr("stop-opacity", 0);
gradient.append("svg:stop")
.attr("offset", "100%")
.attr("stop-color", "#FF0000")
.attr("stop-opacity", 1);
//Adding water
svg.append("path")
.datum({type: "Sphere"})
.attr("class", "water")
.attr("d", path)
var countryTooltip = d3.select("body").append("div").attr("class", "countryTooltip"),
countryList = d3.select("body").append("select").attr("name", "countries");
queue()
.defer(d3.json, "world-110m.json")
.defer(d3.tsv, "world-110m-country-names.tsv")
.await(ready);
//Main function
function ready(error, world, countryData) {
var countryById = {},
countries = topojson.feature(world, world.objects.countries).features;
//Adding countries to select
countryData.forEach(function(d) {
countryById[d.id] = d.name;
option = countryList.append("option");
option.text(d.name);
option.property("value", d.id);
});
//circles for heatmap are coming from the csv below
d3.csv("cities.csv", function(error, data) {
svg.selectAll("circle")
.data(data)
.enter()
.append("a")
.attr("xlink:href", function(d) {
return "https://www.google.com/search?q="+d.city;}
)
.append("circle")
.attr("cx", function(d) {
return projection([d.lon, d.lat])[0];
})
.attr("cy", function(d) {
return projection([d.lon, d.lat])[1];
})
.attr("r", 5.5)
.attr('fill', 'url(#gradient)');
var world = svg.selectAll("path.circle")
.data(countries) //countries from the tsc file is used to populate the names
.enter().append("path")
.attr("class", "land")
.attr("d", path)
//.attr('fill', 'url(#gradient)')
//Drag event
.call(d3.behavior.drag()
.origin(function() { var r = projection.rotate(); return {x: r[0] / sens, y: -r[1] / sens}; })
.on("drag", function() {
var rotate = projection.rotate();
projection.rotate([d3.event.x * sens, -d3.event.y * sens, rotate[2]]);
svg.selectAll("path.land").attr("d", path);
svg.selectAll(".focused").classed("focused", focused = false);
}))
//Mouse events
.on("mouseover", function(d) {
countryTooltip.text(countryById[d.id])
.style("left", (d3.event.pageX + 7) + "px")
.style("top", (d3.event.pageY - 15) + "px")
.style("display", "block")
.style("opacity", 1);
})
.on("mouseout", function(d) {
countryTooltip.style("opacity", 0)
.style("display", "none");
})
.on("mousemove", function(d) {
countryTooltip.style("left", (d3.event.pageX + 7) + "px")
.style("top", (d3.event.pageY - 15) + "px");
});
});//closing d3.csv here
//Country focus on option select
d3.select("select").on("change", function() {
var rotate = projection.rotate(),
focusedCountry = country(countries, this),
p = d3.geo.centroid(focusedCountry);
svg.selectAll(".focused").classed("focused", focused = false);
//Globe rotating
(function transition() {
d3.transition()
.duration(2500)
.tween("rotate", function() {
var r = d3.interpolate(projection.rotate(), [-p[0], -p[1]]);
return function(t) {
projection.rotate(r(t));
svg.selectAll("path").attr("d", path)
.classed("focused", function(d, i) { return d.id == focusedCountry.id ? focused = d : false; });
//svg.selectAll("circle").attr("d", data)
//.classed("focused", function(d, i) { return d.id == focusedCountry.id ? focused = d : false; });
};
})
})();
});
function country(cnt, sel) {
for(var i = 0, l = cnt.length; i < l; i++) {
if(cnt[i].id == sel.value) {return cnt[i];}
}
};
};
</script>
Please help.
Thank you in advance
Here is an option, using point geometries:
.enter().append('path')
.attr('class', 'circle_el')
.attr('fill', function(d) {return d.fill; })
.datum(function(d) {
return {type: 'Point', coordinates: [d.lon, d.lat], radius: some_radius};
})
.attr('d', path);
This is cool because you will update the circles simultaneously with a path redraw. And in addition it will account for the projection as a sphere, not showing circles that should be on the non-visible side of the sphere. I got the idea from this post by Jason Davies.

D3 Pie Chart: Arctween in mouseOver doesn't work

Problem: The Arctween function will not work on the .on("mouseOver").
Intention: When hovering the arcs in the Pie Chart a highlight needs to start (opacity etc.) and information needs (infoHover) to show, next to the Arctween that I also want to activate.
I am aware that the code is not perfect at all, I'm just experimenting with d3.js.
Thanks in advance!
Javascript:
d3.json("dataExample.json", function (data) {
var width = 260,
height = 260;
var outerRadius = height / 2 - 20,
innerRadius = outerRadius / 3,
cornerRadius = 10;
colors = d3.scale.category20c();
var tempColor;
var pie = d3.layout.pie()
.padAngle(.02)
.value(function(d) {
return d.value;
})
var arc = d3.svg.arc()
.padRadius(outerRadius)
.innerRadius(innerRadius);
var infoHover = d3.select('#chart').append('div')
.style('position', 'absolute')
.style('padding', '0 30px')
.style('opacity', 0)
function arcTween(outerRadius, delay) {
return function() {
d3.select(this).transition().delay(delay).attrTween("d", function(d) {
var i = d3.interpolate(d.outerRadius, outerRadius);
return function(t) { d.outerRadius = i(t); return arc(d); };
});
};
}
var svg = d3.select("#chart").append("svg")
.data(data)
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.selectAll('path').data(pie(data))
.enter().append('path')
.attr('fill', function(d, i) {
return colors(i);
})
.each(function(d) { d.outerRadius = outerRadius - 20; })
.attr('d', arc)
.on("mouseover", function(d) {
infoHover.transition()
.style('opacity', .9)
.style('left', '85px')
.style('top', '120px')
infoHover.html(d.value + '%')
d3.selectAll("path")
.transition()
.duration(500)
.style("opacity", .18)
d3.select(this)
.transition()
.duration(500)
.style('opacity', 1)
.style('cursor', 'pointer')
arcTween(outerRadius, 0);
})
.on("mouseout", function(d) {
d3.selectAll("path")
.transition()
.duration(500)
.style("opacity", 1)
d3.select(this)
.style('opacity', 1)
arcTween(outerRadius - 20, 150);
});
});
Your arcTween returns a function your need to call:
arcTween(outerRadius, 0).call(this);

D3 Donut chart: Display Value on segment hover

Long time lurker 1st time poster. I am trying to display the text value form a CSV file when the relevant segment of pie chart is hovered over. I have the pie chart (thanks to Mike Bostock) and the display when hovering but cant remove it on the mouse out. Any help would be greatly appreciated at this stage.
var width = 960,
height = 600,
radius = Math.min(width, height) / 2.5;
var arc = d3.svg.arc()
.outerRadius(radius + 10)
.innerRadius(radius - 70);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var color = d3.scale.ordinal()
.range(["#0bd0d2", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.Total; });
var pieSlice = svg.selectAll("g.slice");
d3.csv("childcare.csv", function(error, data) {
data.forEach(function(d) {
d.population = +d.population;
});
var arcs = svg.selectAll("g.slice")
.data(pie(data))
.enter()
.append("g")
.attr("class", "arc")
arcs.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.place); })
.on("mouseenter", function(d) {
//console.log("mousein")
arcs.append("text")
.attr("transform", arc.centroid(d))
.attr("dy", ".5em")
.style("text-anchor", "middle")
.style("fill", "blue")
.attr("class", "on")
.text(d.data.place);
})
.on("mouseout", function(d) {
console.log("mouseout")
});
});
You can just save your text and remove it on mouseout:
var text;
var arcs = svg.selectAll("g.slice")
.data(pie(data))
.enter()
.append("g")
.attr("class", "arc")
arcs.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.place); })
.on("mouseenter", function(d) {
//console.log("mousein")
text = arcs.append("text")
.attr("transform", arc.centroid(d))
.attr("dy", ".5em")
.style("text-anchor", "middle")
.style("fill", "blue")
.attr("class", "on")
.text(d.data.place);
})
.on("mouseout", function(d) {
text.remove();
});

Categories