I want to create a responsive chart in D3.js without using viewBox. I want to avoid it because I had some problems when I added tooltips to the chart. I started with a great tutorial, but I cant continue following the same logic.
Anyone can help me why is it not drawing both of the lines (const axis, absAxis)? I tried to join/append to the svg, to the rect, but did not work.
function myResponsiveChart(container, props) {
const { width, height } = props;
let svg = container.selectAll('svg').data([null]);
svg = svg.enter().append('svg').merge(svg)
.attr('width', width)
.attr('height', height);
const rect = svg.selectAll('rect').data([null]);
rect.enter().append('rect').merge(rect)
.attr('width', width)
.attr('height', height)
.attr('rx', 100);
const fontSize = 16,
hPix = height/6,
margin = { top: hPix/2, bottom: hPix/2, left: hPix/10, right: hPix/2 };
//DELTA
//svg.append('line').merge(svg)
const axis = svg.selectAll('line').data([null]);
axis.join('line').append('line').merge(axis)
.attr("x1", margin.left)
.attr("x2", width)
.attr("y1", margin.top)
.attr("y2", margin.top)
.attr("class", "diff_axis");
let axis = svg.select('line').data([null]);
axis.enter().append('line').merge(axis)
.attr("x1", margin.left)
.attr("x2", width)
.attr("y1", margin.top)
.attr("y2", margin.top)
.attr("class", "diff_axis");
//ABSOLUTE
//svg.append('line').merge(svg)
let absAxis = svg.selectAll('line').data([null]);
absAxis.enter().append('line').merge(absAxis)
.attr("x1", margin.left)
.attr("x2", width)
.attr("y1", margin.top + hPix)
.attr("y2", margin.top + hPix)
.attr("class", "diff_axis")
.attr("id", "abs");
}
function render() {
myResponsiveChart(d3.select('body'), {
width: document.body.clientWidth,
height: document.body.clientHeight
});
}
render();
window.addEventListener('resize', render);
body {
position: fixed;
left: 0px;
right: 0px;
top: 0px;
bottom: 0px;
margin: 0px;
padding: 0px;
}
.diff_axis {
stroke: red;
stroke-width: 6;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
strong text
This works
When you define a constraint, you cannot redefine it or change its value.
function myResponsiveChart(container, props) {
const { width, height } = props;
let svg = container.selectAll('svg').data([null]);
svg = svg.enter().append('svg').merge(svg)
.attr('width', width)
.attr('height', height);
const rect = svg.selectAll('rect').data([null]);
rect.enter().append('rect').merge(rect)
.attr('width', width)
.attr('height', height)
.attr('rx', 100);
const fontSize = 16,
hPix = height/6,
margin = { top: hPix/2, bottom: hPix/2, left: hPix/10, right: hPix/2 };
//DELTA
//svg.append('line').merge(svg)
const axis = svg.selectAll('line').data([null]);
axis.join('line').append('line').merge(axis)
.attr("x1", margin.left)
.attr("x2", width)
.attr("y1", margin.top)
.attr("y2", margin.top)
.attr("class", "diff_axis");
//ABSOLUTE
//svg.append('line').merge(svg)
let absAxis = svg.selectAll('line').data([null]);
absAxis.enter().append('line').merge(absAxis)
.attr("x1", margin.left)
.attr("x2", width)
.attr("y1", margin.top + hPix)
.attr("y2", margin.top + hPix)
.attr("class", "diff_axis")
.attr("id", "abs");
}
function render() {
myResponsiveChart(d3.select('body'), {
width: document.body.clientWidth,
height: document.body.clientHeight
});
}
render();
window.addEventListener('resize', render);
body {
position: fixed;
left: 0px;
right: 0px;
top: 0px;
bottom: 0px;
margin: 0px;
padding: 0px;
}
.diff_axis {
stroke: red;
stroke-width: 6;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
Related
I am new to d3js and I need some help for my project. I want to my map to change color on dropdown change. I have added two maps in my code, default would be data/world-map.json, and I want to change to data/2000.json when 2000 is selected from dropdown menu. Any help would be appreciated.
Here is my code:
<!DOCTYPE html>
<meta charset="utf-8">
<title>Project</title>
<style>
.country {
stroke: white;
stroke-width: 0.5px;
}
.country:hover{
stroke: #fff;
stroke-width: 1.5px;
}
.text{
font-size:10px;
text-transform:capitalize;
}
#container {
margin:10px 10%;
border:2px solid #000;
border-radius: 5px;
height:100%;
overflow:hidden;
background: #F0F8FF;
}
.hidden {
display: none;
}
div.tooltip {
color: #222;
background: #fff;
padding: .5em;
text-shadow: #f5f5f5 0 1px 0;
border-radius: 5px;
box-shadow: 0px 0px 2px 0px #a6a6a6;
opacity: 0.9;
position: absolute;
}
.graticule {
fill: none;
stroke: #bbb;
stroke-width: .5px;
stroke-opacity: .5;
}
.equator {
stroke: #ccc;
stroke-width: 1px;
}
</style>
</head>
<body>
<h1 align="center">FIFA PLAYER OF THE YEAR 2000 - 2010</h1>
<select id="select_button" name="select_button">
<option value="0" selected="selected">Select year</option>
<option value="1">2000.</option>
<option value="2">2001.</option>
<option value="3">2002.</option>
<option value="4">2003.</option>
<option value="5">2004.</option>
<option value="6">2005.</option>
<option value="7">2006.</option>
<option value="8">2007.</option>
<option value="9">2008.</option>
<option value="10">2009.</option>
<option value="11">2010.</option>
</select>
<div id="container"></div>
<script src="js/d3.min.js"></script>
<script src="js/topojson.v1.min.js"></script>
<script>
d3.select(window).on("resize", throttle);
var zoom = d3.behavior.zoom()
.scaleExtent([1, 9])
.on("zoom", move);
var width = document.getElementById('container').offsetWidth;
var height = width / 2;
var topo,projection,path,svg,g;
var currentKey = '0';
d3.select('#select_button').on('change', function(a) {
currentKey = d3.select(this).property('value');
draw(topo);
});
var graticule = d3.geo.graticule();
var tooltip = d3.select("#container").append("div").attr("class", "tooltip hidden");
setup(width,height);
function setup(width,height){
projection = d3.geo.mercator()
.translate([(width/2), (height/2)])
.scale( width / 2 / Math.PI);
path = d3.geo.path().projection(projection);
svg = d3.select("#container").append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom)
.on("click", click)
.append("g");
g = svg.append("g");
}
d3.json("data/world-map.json", function(error, world) {
var countries = topojson.feature(world, world.objects.countries).features;
topo = countries;
draw(topo);
});
d3.json("data/2000.json", function(error, world) {
var countries2 = topojson.feature(world, world.objects.countries).features;
topo = countries2;
draw(topo);
});
d3.selectAll("path").remove();
function draw(topo) {
/* svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
g.append("path")
.datum({type: "LineString", coordinates: [[-180, 0], [-90, 0], [0, 0], [90, 0], [180, 0]]})
.attr("class", "equator")
.attr("d", path);
*/
var country = g.selectAll(".country").data(topo);
country.enter().insert("path")
.attr("class", "country")
.attr("d", path)
.attr("id", function(d,i) { return d.id; })
.attr("title", function(d,i) { return d.properties.name; })
.style("fill", function(d, i) { return d.properties.color; });
//offsets for tooltips
var offsetL = document.getElementById('container').offsetLeft+20;
var offsetT = document.getElementById('container').offsetTop+10;
//tooltips
country
.on("mousemove", function(d,i) {
var mouse = d3.mouse(svg.node()).map( function(d) { return parseInt(d); } );
tooltip.classed("hidden", false)
.attr("style", "left:"+(mouse[0]+offsetL)+"px;top:"+(mouse[1]+offsetT)+"px")
.html(d.properties.name);
})
.on("mouseout", function(d,i) {
tooltip.classed("hidden", true);
});
/*
//EXAMPLE: adding some capitals from external CSV file
d3.csv("data/country-capitals.csv", function(err, capitals) {
capitals.forEach(function(i){
addpoint(i.CapitalLongitude, i.CapitalLatitude, i.CapitalName );
});
}); */
}
function redraw() {
width = document.getElementById('container').offsetWidth;
height = width / 2;
d3.select('svg').remove();
setup(width,height);
draw(topo);
}
function move() {
var t = d3.event.translate;
var s = d3.event.scale;
zscale = s;
var h = height/4;
t[0] = Math.min(
(width/height) * (s - 1),
Math.max( width * (1 - s), t[0] )
);
t[1] = Math.min(
h * (s - 1) + h * s,
Math.max(height * (1 - s) - h * s, t[1])
);
zoom.translate(t);
g.attr("transform", "translate(" + t + ")scale(" + s + ")");
//adjust the country hover stroke width based on zoom level
d3.selectAll(".country").style("stroke-width", 0.5 / s);
}
var throttleTimer;
function throttle() {
window.clearTimeout(throttleTimer);
throttleTimer = window.setTimeout(function() {
redraw();
}, 200);
}
//geo translation on mouse click in map
function click() {
var latlon = projection.invert(d3.mouse(this));
console.log(latlon);
}
/*
//function to add points and text to the map (used in plotting capitals)
function addpoint(lat,lon,text) {
var gpoint = g.append("g").attr("class", "gpoint");
var x = projection([lat,lon])[0];
var y = projection([lat,lon])[1];
gpoint.append("svg:circle")
.attr("cx", x)
.attr("cy", y)
.attr("class","point")
.attr("r", 1.5);
//conditional in case a point has no associated text
if(text.length>0){
gpoint.append("text")
.attr("x", x+2)
.attr("y", y+2)
.attr("class","text")
.text(text);
}
}*/
</script>
</body>
</html>
My question is:
I have a polygon which is drawn by taking some points and I am showing the points on the edges/outline of polygon. When I increase the stroke-width for the polygon the points getting aligned in the center of the outline. How do we achieve the points alignment on the outside of the edge/border?
Actual how it come is like below picture, the points are center aligned across the border/stroke
working area link for this is
https://codepen.io/jinata92/pen/JjKZeqE
I am looking for a solution like as below picture, The points should be aligned on the outer edges of border/stroke
var height = 100,
width = 100;
var polygon;
var arrVertexes = [
[6, 6],
[94, 6],
[94, 94],
[6, 94]
];
var svg, gContainer;
function config() {
svg = d3
.select(".main")
.attr("width", width)
.attr("height", height)
.attr("viewBox", `0 0 ${width} ${height}`);
gContainer = svg.select("g");
}
function drawPolygon() {
polygon = gContainer
.append("polygon")
.attr("points", arrVertexes)
.attr("class", "segment");
}
function drawCircle() {
gContainer
.selectAll("circle")
.data(arrVertexes)
.enter()
.append("circle")
.attr("class", "vertex")
.classed("handle", true)
.attr("cx", function(d) {
return d[0];
})
.attr("cy", function(d) {
return d[1];
})
.attr("r", 4);
}
config();
drawPolygon();
drawCircle();
body {
background-color: grey;
}
svg {
position: absolute;
overflow: visible;
}
.resize-div {
position: relative;
overflow: visible;
}
.polygon {
stroke: yellow;
fill: transparent;
}
.vertex,
.dot {
fill: black;
stroke: none;
}
.segment {
stroke-width: 30;
stroke: yellow;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div style="width:100px;height:100px; left: 400px;
top:50px;" class="resize-div">
<svg class="main" height="100%" width="100%">
<g class="polygon"></g>
</svg>
</div>
Stroke-width is always applied to both sides, so the centre of the line is still at the required place. But there are some workarounds. One is to just draw the shape, but also apply a clip path equal to the element, but without the stroke-width:
var height = 100,
width = 100;
var polygon;
var arrVertexes = [
[6, 6],
[94, 6],
[94, 94],
[6, 94]
];
var svg, gContainer;
function config() {
svg = d3
.select(".main")
.attr("width", width)
.attr("height", height)
.attr("viewBox", `0 0 ${width} ${height}`);
gContainer = svg.select("g");
}
function drawPolygon() {
polygon = gContainer
.append("polygon")
.attr("clip-path", "url('#my-clip-path')")
.attr("points", arrVertexes)
.attr("class", "segment");
// Append a clip path
svg.append("defs")
.append("clipPath")
.attr("id", "my-clip-path")
.append("polygon")
.attr("points", arrVertexes)
.attr("class", "segment");
}
function drawCircle() {
gContainer
.selectAll("circle")
.data(arrVertexes)
.enter()
.append("circle")
.attr("class", "vertex")
.classed("handle", true)
.attr("cx", function(d) {
return d[0];
})
.attr("cy", function(d) {
return d[1];
})
.attr("r", 4);
}
config();
drawPolygon();
drawCircle();
body {
background-color: grey;
}
svg {
position: absolute;
overflow: visible;
}
.resize-div {
position: relative;
overflow: visible;
}
.polygon {
stroke: yellow;
fill: transparent;
}
.vertex,
.dot {
fill: black;
stroke: none;
}
.segment {
stroke-width: 30;
stroke: yellow;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div style="width:100px;height:100px; left: 400px;
top:50px;" class="resize-div">
<svg class="main" height="100%" width="100%">
<g class="polygon"></g>
</svg>
</div>
Alternatively, you could draw a polygon on top of the other, but inwards a little bit. Use the bounding box to calculate the centre of the outer polygon, and move the coordinates of the inner polygon inwards by some number of pixels:
var height = 100,
width = 100;
var polygon;
var arrVertexes = [
[6, 6],
[94, 6],
[94, 94],
[6, 94]
];
var svg, gContainer;
function config() {
svg = d3
.select(".main")
.attr("width", width)
.attr("height", height)
.attr("viewBox", `0 0 ${width} ${height}`);
gContainer = svg.select("g");
}
function drawPolygon() {
polygon = gContainer
.append("polygon")
.attr("points", arrVertexes)
.attr("class", "segment-border");
// Get the bounding box so you can calculate the centre
const boundingBox = polygon.node().getBBox();
const centre = {
x: boundingBox.x + boundingBox.width / 2,
y: boundingBox.y + boundingBox.height / 2,
};
const innerVertexes = arrVertexes.map(d => [
d[0] < centre.x ? d[0] + 15 : d[0] - 15,
d[1] < centre.y ? d[1] + 15 : d[1] - 15,
]);
polygon = gContainer
.append("polygon")
.attr("points", innerVertexes)
.attr("class", "segment");
}
function drawCircle() {
gContainer
.selectAll("circle")
.data(arrVertexes)
.enter()
.append("circle")
.attr("class", "vertex")
.classed("handle", true)
.attr("cx", function(d) {
return d[0];
})
.attr("cy", function(d) {
return d[1];
})
.attr("r", 4);
}
config();
drawPolygon();
drawCircle();
body {
background-color: grey;
}
svg {
position: absolute;
overflow: visible;
}
.resize-div {
position: relative;
overflow: visible;
}
.polygon {
stroke: yellow;
fill: transparent;
}
.vertex,
.dot {
fill: black;
stroke: none;
}
.segment-border {
fill: yellow;
}
.segment {
fill: white;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div style="width:100px;height:100px; left: 400px;
top:50px;" class="resize-div">
<svg class="main" height="100%" width="100%">
<g class="polygon"></g>
</svg>
</div>
I've seen this question and many other examples, but it isn't helping. I'm trying the last example on this page and after 5 seconds, I want the curved path that is being drawn, to completely disappear and 5 more seconds later, I want a new path to be created.
I've tried the below code, but although the entire svg element itself is removed, when I use appendGraph() to created the svg and the path again, the same old path re-appears. How can I ensure that the old path is completely removed and that the tick function also does not get called when the graph is removed?
The fiddle is here: http://jsfiddle.net/nav9/5uygqj9v/
And the code is:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<style>
svg {
font: 10px sans-serif;
}
.noselect {
/* these are to disable text selection */
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
opacity: 0.5;
shape-rendering: crispEdges;
}
rect.zoom {
stroke: steelblue;
fill-opacity: 0.3;
}
#placeholder {margin: 10px 5px 15px 70px;}
</style>
<script type="text/javascript" src="http://d3js.org/d3.v3.js"></script>
</head>
<body>
<div id="placeholder" ></div>
<script>
//---------globals
var timer = null, interval = 500, value = 0;
var value1 = 0;
var n = 143, duration = interval, now = new Date(Date.now() - duration), count = 0, data = d3.range(n).map(function() { return 0; });
var margin = {top: 20, right: 40, bottom: 50, left: 60}, width = 580 - margin.right, height = 420 - margin.top - margin.bottom;
var x = d3.time.scale().domain([now - (n - 2) * duration, now - duration]).range([0, width]);
var y = d3.scale.linear().domain([-1, 1]).range([height, 0]);
var line = d3.svg.line().interpolate("basis")
.x(function(d, i) { return x(now - (n - 1 - i) * duration); })
.y(function(d, i) { return y(d); });
var svg, path, yaxis, axis;
//--------program starts
appendGraph();
tick();
value1 = 0;
setTimeout(function() {removeGraph();}, 5000);
setTimeout(function() {addGraphAgain();}, 10000);
//-------------------------------functions -------------------------------
function appendGraph()
{
svg = d3.select("body").select("#placeholder").append("p").append("svg:svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("id", "mainSVG")
.style("margin-left", -margin.left + "px")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
axis = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(x.axis = d3.svg.axis().scale(x).orient("bottom"));
yaxis = svg.append("g")
.attr("class", "y axis")
.call(y.axis = d3.svg.axis().scale(y).orient("left"));
path = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.data([data])
.attr("id", "line1")
.attr("fill", "none")
.attr("stroke", "black")
.attr("stroke-width", "1.5px")
.style("visibility","visible");
}//appendGraph
//TODO: These tick functions could be simplified to handle more lines on the graph
function tick()
{
// push the accumulated count onto the back, and reset the count
value1 = Math.random() * 100;
if (value1 >= 0) {data.push(value1);} else {data.push(0);}//ensure that no NaN or undefined values corrupt the range
// update the domains
now = new Date();
x.domain([now - (n - 2) * duration, now - duration]);
count = 0;
// redraw the lines
svg.select("#line1").attr("d", line).attr("transform", null);
// slide the line left
path.transition().duration(duration).ease("linear").attr("transform", "translate(" + x(now - (n - 1) * duration) + ")").each("end", tick);
y.domain([0, 100]);
y = d3.scale.linear().domain([0, 100]).range([height, 0]);
yaxis.call(y.axis = d3.svg.axis().scale(y).orient("left"));
// pop the old data point off the front
data.shift();
console.log("tick being called");
}
function removeGraph()
{
path.transition().duration(0).each(function() { this.__transition__.active = 0; });//at least this is stopping tick from being called
svg.selectAll("*").remove();
//-------tried these too
// d3.select("#mainSVG").remove("svg");
// d3.select("#line1").remove("path");
// path.remove();
//d3.selectAll("path").attr("d", "Z");
console.log("REMOVED");
}//removeGraph
function addGraphAgain()
{
appendGraph();
tick();
value1 = 0;
console.log("ADDED AGAIN");
}//addGraphAgain
</script>
</body>
</html>
Not an exact answer to this question, but since the reason I asked this was because I wanted to have phases where I wanted to send null inputs to the graph and there seemed no other way to do it other than to remove the line and replace it with a new line.
The trick to handle null or NaN data or missing data in d3.js or to simply not display data for a while is to use defined.
A working example of it here and in the line transition, it's like this:
I supply a random number at if (counter%5==0) ran = null;data.push(ran); and .defined(function(d) { return d != null; }) takes care of the null, by not drawing a line there.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.line {
fill: none;
stroke: #000;
stroke-width: 1.5px;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var n = 40,
random = d3.random.normal(0, .2),
data = d3.range(n).map(random);
var margin = {top: 20, right: 20, bottom: 20, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([0, n - 1])
.range([0, width]);
var y = d3.scale.linear()
.domain([-1, 1])
.range([height, 0]);
var line = d3.svg.line()
.defined(function(d) { return d != null; })
.x(function(d, i) { return x(i); })
.y(function(d, i) { return y(d); });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + y(0) + ")")
.call(d3.svg.axis().scale(x).orient("bottom"));
svg.append("g")
.attr("class", "y axis")
.call(d3.svg.axis().scale(y).orient("left"));
var path = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
var counter = 0;
tick();
function tick()
{
// push a new data point onto the back
var ran = random();
counter++;
if (counter%5==0) ran = null;
data.push(ran);
// redraw the line, and slide it to the left
path
.attr("d", line)
.attr("transform", null)
.transition()
.duration(500)
.ease("linear")
.attr("transform", "translate(" + x(-1) + ",0)")
.each("end", tick);
// pop the old data point off the front
data.shift();
}
</script>
I'm trying to get a d3 globe to rotate to a particular country when you click that country in a list. To start out, I'm trying to get the following example working (I got it from http://bl.ocks.org/KoGor/5994804), but it throws an error TypeError: world is undefined from line 100. Can anyone help, please?:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Earth globe</title>
<script src="./d3/d3.v3.min.js"></script>
<script src="./d3/topojson.v1.min.js"></script>
<script src="./d3/queue.v1.min.js"></script>
</head>
<style type="text/css">
.water {
fill: #00248F;
}
.land {
fill: #A98B6F;
stroke: #FFF;
stroke-width: 0.7px;
}
.land:hover {
fill:#33CC33;
stroke-width: 1px;
}
.focused {
fill: #33CC33;
}
select {
position: absolute;
top: 20px;
left: 580px;
border: solid #ccc 1px;
padding: 3px;
box-shadow: inset 1px 1px 2px #ddd8dc;
}
.countryTooltip {
position: absolute;
display: none;
pointer-events: none;
background: #fff;
padding: 5px;
text-align: left;
border: solid #ccc 1px;
color: #666;
font-size: 14px;
font-family: sans-serif;
}
</style>
<body>
<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);
//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, "http://bl.ocks.org/KoGor/raw/5685937/world-110m.json")
.defer(d3.tsv, "http://bl.ocks.org/KoGor/raw/5685937/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);
});
//Drawing countries on the globe
var world = svg.selectAll("path.land")
.data(countries)
.enter().append("path")
.attr("class", "land")
.attr("d", path)
//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");
});
//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; });
};
})
})();
});
function country(cnt, sel) {
for(var i = 0, l = cnt.length; i < l; i++) {
if(cnt[i].id == sel.value) {return cnt[i];}
}
};
};
</script>
</body>
</html>
I'm adapting the zoomable and clickable map found http://bl.ocks.org/mbostock/2206340 at to plot some points and do some other things. Right now, I'm trying to make it such that on the zoom and click actions, the plotted points also move / honor the zoom. I'm not sure what in the code here is wrong, since I seem to be calling the red.circle and blue.circle objects in the zoom + click -- can anyone identify the issue? Thanks! data.csv is formatted as follows:
lon_0,lat_0,lon_1,lat_1
-122.1430195,37.4418834,-122.415278,37.778643
-122.1430195,37.4418834,-122.40815,37.785034
-122.4194155,37.7749295,-122.4330827,37.7851673
-122.4194155,37.7749295,-122.4330827,37.7851673
-118.4911912,34.0194543,-118.3672828,33.9164666
-121.8374777,39.7284944,-121.8498415,39.7241178
-115.172816,36.114646,-115.078011,36.1586877
and here is the d3.js script.
.background {
fill: none;
pointer-events: all;
}
#states path {
fill: #aaa;
stroke: #fff;
stroke-width: 1.5px;
}
#states path:hover {
stroke: white;
}
</style>
<body>
<script>
var width = 1920/2,
height = 1000/2;
var projection = d3.geo.albersUsa()
.scale(width)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var zoom = d3.behavior.zoom()
.translate(projection.translate())
.scale(projection.scale())
.scaleExtent([height, 50 * height])
.on("zoom", zoom);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.attr("style", "stroke:black; stroke-width:2px");
var states = svg.append("g")
.attr("id", "states")
.call(zoom);
var dataset = [];
states.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height);
d3.json("us-states.json", function(json) {
states.selectAll("path")
.data(json.features)
.enter().append("path")
.attr("d", path)
.on("click", click);
d3.csv("data.csv", function(data) {
states.selectAll(".blue.circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return projection([d["lon_0"], d["lat_0"] ])[0];
})
.attr("cy", function(d) {
return projection([d["lon_0"],d["lat_0"] ])[1];
})
.attr("r", 5)
.attr("class", "blue circle")
.style("fill", "blue");
states.selectAll(".red.circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return projection([+d["lon_1"], +d["lat_1"] ])[0];
})
.attr("cy", function(d) {
return projection([+d["lon_1"],+d["lat_1"] ])[1];
})
.attr("r", 5)
.attr("class", "red circle")
.style("fill", "red");
});
});
function click(d) {
var centroid = path.centroid(d),
translate = projection.translate();
projection.translate([
translate[0] - centroid[0] + width / 2,
translate[1] - centroid[1] + height / 2
]);
zoom.translate(projection.translate());
states.selectAll("path").transition()
.duration(1000)
.attr("d", path);
states.selectAll("red.circle").transition()
.duration(1000)
.attr("d", circle);
states.selectAll("blue.circle").transition()
.duration(1000)
.attr("d", circle);
}
function zoom() {
projection.translate(d3.event.translate).scale(d3.event.scale);
states.selectAll("path").attr("d", path);
states.selectAll("red.circle").attr("d", path);
states.selectAll("blue.circle").attr("d",path);
}
</script>
you're setting the co-ordinates of the circles when you load the map, so when you click the zoom function, your circles are displayed but are not using the same co-ordinates - i think - it will help if you can create a http://bl.ocks.org to see this.
perhaps this could be of help http://bl.ocks.org/nkhine/3150901 only UK, US and Afganistan works, but i am basically re-projecting the secondary map to fit the new zoom level.