D3 js tooltip issue for Choropleth Map - javascript

i am new to D3.js. I have been trying to add tooltip to the existing d3 chloropleth Map by Michelle Chandra. However i am unable to make any progress, the tooltip doesnt seem to appear. Where am i doing wrong? Any Help will be appreciated. http://bl.ocks.org/michellechandra/0b2ce4923dc9b5809922.
Thanks
<style type="text/css">
/* On mouse hover, lighten state color */
path:hover {
fill-opacity: .7;
}
/* Style for Custom Tooltip */
div.tooltip {
position: absolute;
text-align: center;
width: 60px;
height: 28px;
padding: 2px;
font: 12px sans-serif;
background: white;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
/* Legend Font Style */
body {
font: 11px sans-serif;
}
/* Legend Position Style */
.legend {
position:absolute;
left:800px;
top:350px;
}
</style>
</head>
<body>
<script type="text/javascript">
/* This visualization was made possible by modifying code provided by:
Scott Murray, Choropleth example from "Interactive Data Visualization for the Web"
https://github.com/alignedleft/d3-book/blob/master/chapter_12/05_choropleth.html
Malcolm Maclean, tooltips example tutorial
http://www.d3noob.org/2013/01/adding-tooltips-to-d3js-graph.html
Mike Bostock, Pie Chart Legend
http://bl.ocks.org/mbostock/3888852 */
//Width and height of map
var w = 900;
var h = 600;
// D3 Projection
var projection = d3.geo.albersUsa()
.translate([w/2, h/2]) // translate to center of screen
.scale([1000]); // scale things down so see entire US
// Define path generator
var path = d3.geo.path() // path generator that will convert GeoJSON to SVG paths
.projection(projection); // tell path generator to use albersUsa projection
// Define linear scale for output
var color = d3.scale.linear()
.range(["rgb(24,143,95)","rgb(51,188,196)","rgb(155,226,183)","rgb(217,91,67)"]);
var legendText = ["Cities Lived", "States Lived", "States Visited", "States Not Visited Yet"];
//Create SVG element and append map to the SVG
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
// Append Div for tooltip to SVG
var div = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// Load in my states data!
d3.csv("stateslived.csv", function(data) {
color.domain([0,1,2,3]); // setting the range of the input data
// Load GeoJSON data and merge with states data
d3.json("us-states.json", function(json) {
// Loop through each state data value in the .csv file
for (var i = 0; i < data.length; i++) {
// Grab State Name
var dataState = data[i].state;
// Grab data value
var dataValue = data[i].visited;
// Find the corresponding state inside the GeoJSON
for (var j = 0; j < json.features.length; j++) {
var jsonState = json.features[j].properties.name;
if (dataState == jsonState) {
// Copy the data value into the JSON
json.features[j].properties.visited = dataValue;
// Stop looking through the JSON
break;
}
}
}
// Bind the data to the SVG and create one path per GeoJSON feature
svg.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path)
.style("stroke", "#fff")
.style("stroke-width", "1")
.style("fill", function(d) {
// Get data value
var value = d.properties.visited;
if (value) {
//If value exists…
return color(value);
} else {
//If value is undefined…
return "rgb(213,222,217)";
}
});
// Map the cities I have lived in!
d3.csv("cities-lived.csv", function(data) {
svg.selectAll("circle")
.data(data)
.enter()
.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", function(d) {
return Math.sqrt(d.years) * 4;
})
.style("fill", "rgb(217,91,67)")
.style("opacity", 0.85)
// add browser tooltip of city name
//.append("title")
//.text(function(d) {
// return d.place;
//});
// Modification of custom tooltip code provided by Malcolm Maclean, "D3 Tips and Tricks"
// http://www.d3noob.org/2013/01/adding-tooltips-to-d3js-graph.html
.on("mouseover", function(d) {
d3.select(this).transition().duration(300).style("opacity", 1);
div.transition().duration(200)
.style("opacity", .9);
div.text(d.properties.visited)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
// fade out tooltip on mouse out
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
});
// Modified Legend Code from Mike Bostock: http://bl.ocks.org/mbostock/3888852
var legend = d3.select("body").append("svg")
.attr("class", "legend")
.attr("width", 140)
.attr("height", 200)
.selectAll("g")
.data(color.domain().slice().reverse())
.enter()
.append("g")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.data(legendText)
.attr("x", 24)
.attr("y", 9)
.attr("dy", ".35em")
.text(function(d) { return d; });
});
});
/* This code generate paths without mapping to other data
// Load JSON file and generate path for each state
d3.json("us-states.json", function(json) { // file path, callback function called when data loaded
svg.selectAll("path") // creates empty references to all the paths
.data(json.features) // loop through our data (the states in the array) and bind to paths
.enter() // create placeholder to reference the new elements
.append("path") // add to the DOM!
.attr("d", path) // generate paths for each state
.style("fill", "steelblue"); // make the states blue!
}); */
</script>

I'm not sure what this statement is supposed to do:
d3.select(this).transition().duration(300).style("opacity", 1);
It appears to select the window object.
One would need to look at your json file to see if you're getting the right data into the div.
Shameless plug. Take a look at foxToolTip.js. I think its easier and more flexible than d3tip.
https://github.com/MichaelRFox/foxToolTip.js

Once div is a div, you have to use html, not text:
div.transition().duration(200)
.style("opacity", .9);
div.html(d.properties.visited)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");

Related

Tooltip always displaying the same value

I'm trying to create a map of coordinates from some data I got in a csv file. The converting of the X/Y axes works perfectly, the circles (or rather dots) get drawn but the mouseover tooltip always displays the last values (or rather the last values +1 which is in my array out of bounds even though the tooltip should be set with the current values of the array.
Longitude and altitude are my two array names
var svgContainer = d3.select("body").append("svg")
.attr("width", 700)
.attr("height", 250)
.style("border", "1px solid black");
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
for (i = 0; i < longitude.length; i++) {
var circleSelection = svgContainer.append("circle")
.attr("cx", longitude[i])
.attr("cy", altitude[i])
.attr("r", 2)
.style("fill", "purple")
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div .html("X: " + longitude[i] + " Y: " + altitude[i])
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
}
and here's the css but I doubt the problem's to be found in here
<style>
div.tooltip {
position: absolute;
text-align: center;
width: 60px;
height: 28px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
Every clue is much appreciated
As a general rule: do not use loops for appending elements in a D3 code. Not only this is not the idiomatic D3 but, more importantly, things will break (as you're seeing right now).
Before anything, here is an explanation of why all the values are the same: JavaScript closure inside loops – simple practical example
Let's see this, hover over any circle:
var data = ["foo", "bar", "baz"];
var svg = d3.select("svg");
for (var i = 0; i < data.length; i++) {
svg.append("circle")
.attr("cy", 75)
.attr("cx", 50 + i * 100)
.attr("r", 20)
.attr("fill", "teal")
.on("mouseover", function() {
console.log(data[i - 1])
})
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
Things get better using let:
var data = ["foo", "bar", "baz"];
var svg = d3.select("svg");
for (let i = 0; i < data.length; i++) {
svg.append("circle")
.attr("cy", 75)
.attr("cx", 50 + i * 100)
.attr("r", 20)
.attr("fill", "teal")
.on("mouseover", function() {
console.log(data[i])
})
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
However, even if using let gives the correct result, it is not a good solution, because you are not binding any data.
The best solution is: use a D3 "enter" selection, binding data to the elements:
var data = ["foo", "bar", "baz"];
var svg = d3.select("svg");
svg.selectAll(null)
.data(data)
.enter()
.append("circle")
.attr("cy", 75)
.attr("cx", function(d, i) {
return 50 + i * 100
})
.attr("r", 20)
.attr("fill", "teal")
.on("mouseover", function(d) {
console.log(d)
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>

"Flight Animation" in d3.js v3

Im currently trying to wrap my head around connecting points around the map and animating them like - HERE:
http://www.tnoda.com/flightanimation
I want the points to connect using information in my CSV File:
Specifically 'where' field which will tell us is the place a destination or an origin of the flight:
code,city,country,lat,lon,where
ZNZ,ZANZIBAR,TANZANIA,-6.13,39.31,dest
TYO,TOKYO,JAPAN,35.68,139.76,dest
AKL,AUCKLAND,NEW ZEALAND,-36.85,174.78,orgin
BKK,BANGKOK,THAILAND,13.75,100.48,orgin
DEL,DELHI,INDIA,29.01,77.38,orgin
SIN,SINGAPORE,SINGAPOR,1.36,103.75,orgin
BSB,BRASILIA,BRAZIL,-15.67,-47.43,orgin
RIO,RIO DE JANEIRO,BRAZIL,-22.90,-43.24,orgin
YTO,TORONTO,CANADA,43.64,-79.40,orgin
IPC,EASTER ISLAND,CHILE,-27.11,-109.36,orgin
SEA,SEATTLE,USA,47.61,-122.33,orgin
(I know i spelled origin wrong its intentionally like that)
Now this is my HTML Code:
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.3.13/d3.min.js"></script>
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<style>
path {
stroke: limegreen;
stroke-width: 0.25px;
fill: black;
margin: 0 auto;
}
body {
background-color:darkgrey;
margin: 0 auto;
}
.packet {
max-height: height: 50px;
max-width: 50px;
fill: limegreen;
}
</style>
<body>
<script type="text/javascript" src="http://gc.kis.v2.scr.kaspersky-labs.com/3F7B1EB8-32BF-7449-968C-CB1318D27635/main.js" charset="UTF-8"></script><link rel="stylesheet" crossorigin="anonymous" href="http://gc.kis.v2.scr.kaspersky-labs.com/53672D8131BC-C869-9447-FB23-8BE1B7F3/abn/main.css"/><script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v0.min.js"></script>
<script>
var width = 960,
height = 500;
var projection = d3.geo.mercator()// Creating our projection for our map
var svg = d3.select("body").append("svg")//Append svg to body
.attr("width", width)
.attr("height", height);
var path = d3.geo.path()//create a path for the projection
.projection(projection);
var g = svg.append("g"); //create an empty space to append
var packet = svg.append("path")
.attr("width","50px")
.attr("d", "M612.074,132.141v-2.38c0-8.849-4.016-19.26-11.229-26.473l-0.818-0.818c0,0-0.818,0-0.818-0.818 c-1.636-1.636-3.198-2.38-4.833-4.016c-0.818,0-0.818-0.818-1.636-0.818c-1.636-0.818-4.016-1.636-5.652-2.38 c-0.818,0-0.818-0.818-1.636-0.818c-2.38-0.818-4.833-1.636-7.213-1.636c-0.818,0-0.818,0-1.636,0c-2.38,0-5.651-0.818-8.849-0.818 H43.427c-3.198,0-6.395,0-9.667,0.818c-0.818,0-1.636,0-2.38,0.818c-2.38,0.818-4.834,0.818-6.395,1.636 c-0.818,0-0.818,0.818-1.636,0.818c-1.636,0.818-4.016,1.636-5.652,2.38l-0.818,0.818c-1.636,0.818-3.198,2.38-4.834,3.198 c-0.818,0.818-1.636,1.636-2.38,2.38C4.016,110.428,0.818,117.715,0,125.746c0,0.818,0,0.818,0,1.636v357.384 c0,0.818,0,0.818,0,1.636c1.636,11.229,7.213,20.896,15.244,26.473c7.213,4.833,16.062,8.031,26.473,8.031H569.39c0,0,0,0,0.818,0 l0,0c2.38,0,5.651,0,8.031-0.818c0.818,0,0.818,0,1.636,0c2.38-0.818,4.834-0.818,6.395-1.636h0.818 c17.698-6.395,24.911-21.714,24.911-36.14v-2.38v-0.818v-0.818V134.521c0-0.818,0-0.818,0-1.636 C612.074,132.959,612.074,132.959,612.074,132.141z M560.69,120.913l-252.98,246.51l-57.854-56.218l0,0L51.459,120.838H560.69 V120.913z M29.819,475.099V140.991l187.095,179.882L29.819,475.099z M299.679,491.905H56.292l182.336-149.393l58.597,57.036 c2.38,2.38,4.834,3.198,7.213,4.016h0.818c0.818,0,0.818,0,1.636,0l0,0c0.818,0,1.636,0,1.636,0h0.818 c2.38-0.818,5.651-1.636,7.213-4.016l55.4-53.838l183.079,146.196H299.679z M582.329,475.843L394.417,324.07L582.329,140.99 V475.843z");
var route = svg.append("path");
// load and display the World
d3.json("world-110m2.json", function(error, topology) {//Load in the world map ( LOW RES)
g.selectAll("path")
.data(topojson.object(topology, topology.objects.countries)
.geometries)
.enter()
.append("path")//append path
.attr("d", path)//path is d3.geo.path which is the projection
//Loading the countries inside the world load display function to speed up the loading on local server and faster or client
//Loading the countries here also prevents the dots to be under the map instead on top of it!
d3.csv("countries.csv", function(error, data) {
g.selectAll("circle")
.data(data)
.enter()
.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)
.style("fill", "red");
//Writing out the Cities name
g.selectAll("text")
.attr("class","names")
.data(data)
.enter()
.append("text") // append text
.attr("x", function(d) {
return projection([d.lon, d.lat])[0];
})
.attr("y", function(d) {
return projection([d.lon, d.lat])[1];
})
.attr("dy", -7) // set y position of bottom of text
.style("fill", "limegreen") // fill the text with the colour black
.attr("text-anchor", "middle") // set anchor y justification
.text(function(d) {return d.city;}); // define the text to display
//Test
route.selectAll("path")
.datum({type: "LineString", coordinates:
[
function(d) {
if (d.where === origin){
return projection(d.lat,d.lon)
}},
function(d) {
if (d.where === dest){
return projection(d.lat,d.lon)
}}
]
})
.attr("class", "route")
.attr("d", path);
});
//Animating path
// Map Zooimng
var zoom = d3.behavior.zoom()
.on("zoom",function() {
g.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
g.selectAll("path")
.attr("d", path.projection(projection));
});
svg.call(zoom)
});
function transition(packet, route) {
var l = route.node().getTotalLength();
packet.transition()
.duration(5000)
.attrTween("transform", delta(route.node()));
}
function delta(path) {
var l = path.getTotalLength();
return function(i) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
}
}
}
transition(packet, route);
</script>
</body>
</html>
Also if anyone can tell me why is my SVG element not resizing that would be great but i'd say i will be able to figure it out.
If someone can explain to me why is the code not working - point out my mistake in my logic and correct me it would be really helpful.
I'm doing this because Data Visualisation has conquered my interest now.
Cheers!
Also hope my comments can be helpful for people that might have had the same problem and are trying to wrap their head around the code!

how to plot the image inside the polygon in d3

hi all i am using d3 chart with polygon i have one map structure d3 chart and plot one circle for the purpose of show tooltip now my need is i need to show one image 'https://i.stack.imgur.com/O9xB5.png' to replace the circle so when mouse over the image i shown tooltip and another need show 'State Abbr' inside polygon like Ak,TD,PD...
.help ow to do this here i attached my code files
code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v3.min.js"></script>
<style type="text/css">
/* On mouse hover, lighten state color */
path:hover {
fill-opacity: .7;
}
/* Style for Custom Tooltip */
div.tooltip {
position: absolute;
text-align: center;
width: 60px;
height: 28px;
padding: 2px;
font: 12px sans-serif;
background: white;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
/* Legend Font Style */
body {
font: 11px sans-serif;
}
/* Legend Position Style */
.legend {
position:absolute;
left:800px;
top:350px;
}
</style>
</head>
<body>
<script type="text/javascript">
//Width and height of map
var width = 960;
var height = 500;
// D3 Projection
var projection = d3.geo.albersUsa()
.translate([width/2, height/2])
.scale([1000]);
// Define path generator
var path = d3.geo.path()
.projection(projection);
// Define linear scale for output
var color = d3.scale.linear()
.range(["green","red"]);
var legendText = ["Population Present", "Population Absent"];
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var div = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// Load in my states data!
d3.csv("Population_education.csv", function(data) {
// Load GeoJSON data and merge with states data
d3.json("us-states.json", function(json) {
// Loop through each state data value in the .csv file
for (var i = 0; i < data.length; i++) {
// Grab State Name
var dataState = data[i].SiteState;
// Get Population
var dataPop = data[i].Population;
// Grab data value
if(data[i].Members > 0) {
var dataValue = 1;
}
else { var dataValue = 0;}
// Find the corresponding state inside the GeoJSON
for (var j = 0; j < json.features.length; j++) {
var jsonState = json.features[j].properties.name;
if (dataState == jsonState) {
// Copy the data value into the JSON
json.features[j].properties.MembersPresent = dataValue;
json.features[j].properties.pop = +dataPop;
// Stop looking through the JSON
break;
}
}
}
// Get Max and Min Population and update colorscale
var max = d3.max(json.features, function(d) { return d.properties.pop });
var min = d3.min(json.features, function(d) { return d.properties.pop })
color.domain([min, max]); // setting the range of the input data
// Bind the data to the SVG and create one path per GeoJSON feature
svg.selectAll("path")
.data(json.features)
.enter().append("path")
.attr("d", path)
.style("stroke", "#fff")
.style("stroke-width", "1")
.style("fill", function(d) {
return color(d.properties.pop)
});
// Map the cities I have lived in!
d3.csv("Population_education.csv", function(data) {
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
if (d.AvgLng != 0 && d.AvgLat != 0)
return projection([d.AvgLng, d.AvgLat])[0];
})
.attr("cy", function(d) {
if (d.AvgLng != 0 && d.AvgLat != 0)
return projection([d.AvgLng, d.AvgLat])[1];
})
.attr("r", function(d) {
return 3;
})
.style("fill", "rgb(217,91,67)")
.style("opacity", 0.45)
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div.html("State:" + d['State Abbr'] + "<br/>" + "Pop:" + d.Population)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
});
var legend = d3.select("body").append("svg")
.attr("class", "legend")
.attr("width", 140)
.attr("height", 200)
.selectAll("g")
.data(color.domain().slice().reverse())
.enter()
.append("g")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.data(legendText)
.attr("x", 24)
.attr("y", 9)
.attr("dy", ".35em")
.text(function(d) { return d; });
});
});
</script>
</body>
</html>
Data
Population_education.csv
RowID,SiteState,State Abbr,AvgLat,AvgLng,Population
1,Alabama,AL,32.806671,-86.79113,28
2,Arizona,AZ,33.729759,-111.431221,11704
3,California,CA,36.116203,-119.681564,4356448
4,Colorado,CO,39.059811,-105.311104,374435
5,Connecticut,CT,41.597782,-72.755371,455966
6,Florida,FL,27.766279,-81.68678300000001,442537
7,Georgia,GA,33.040619,-83.643074,1339081
8,Illinois,IL,40.349457,-88.986137,29
9,Indiana,IN,39.849426,-86.258278,1525124
10,Iowa,IA,42.011539,-93.210526,185146
11,Kansas,KS,38.5266,-96.72648599999999,129301
12,Kentucky,KY,37.66814,-84.670067,621047
13,Louisiana,LA,31.169546,-91.867805,170568
14,Maine,ME,44.693947,-69.381927,222966
15,Maryland,MD,39.063946,-76.80210099999999,256966
16,Massachusetts,MA,42.230171,-71.530106,27
17,Michigan,MI,43.326618,-84.536095,27
18,Minnesota,MN,45.694454,-93.900192,11
19,Missouri,MO,38.456085,-92.28836800000001,420415
20,Nevada,NV,38.313515,-117.055374,309799
21,New Hampshire,NH,43.452492,-71.563896,195948
22,New Jersey,NJ,40.298904,-74.521011,241039
23,New Mexico,NM,34.840515,-106.248482,1945
24,New York,NY,42.165726,-74.94805100000001,1075153
25,North Carolina,NC,35.630066,-79.80641900000001,14
26,Ohio,OH,40.388783,-82.764915,1526404
27,Oregon,OR,44.572021,-122.070938,11
28,Pennsylvania,PA,40.590752,-77.209755,197
29,South Carolina,SC,33.856892,-80.945007,45
30,Tennessee,TN,35.747845,-86.692345,446667
31,Texas,TX,31.054487,-97.563461,736672
32,Vermont,VA,37.769337,-78.169968,2324640
33,Washington,WA,47.400902,-121.490494,141319
34,West Virginia,WV,38.491226,-80.954453,128275
35,Wisconsin,WI,44.268543,-89.616508,405942
36,Alaska,AK,0,0,0
37,Arkansas,AR,0,0,0
38,Delaware,DE,0,0,0
39,District of Columbia,DC,0,0,0
40,Hawaii,HI,0,0,0
41,Idaho,ID,0,0,0
42,Mississippi,MS,0,0,0
43,Montana,MT,0,0,0
44,Nebraska,NE,0,0,0
45,North Dakota,ND,0,0,0
46,South Dakota,SD,0,0,0
47,Utah,UT,0,0,0
48,Virginia,VT,0,0,0
49,Wyoming,WY,0,0,0
50,Oklahoma,OK,0,0,0
51,Rhode Island,RI,0,0,0
My us-states.json is as in the following link https://raw.githubusercontent.com/alignedleft/d3-book/master/chapter_12/us-states.json
Code to add image and tooltip to each polygon.
paths.each(function(d) {
if (this.getTotalLength() > 0) {
var midPoint = path.centroid(d);
svg.append("svg:image")
.attr("height", "15px")
.attr("width", "15px")
.attr("xlink:href", "https://i.stack.imgur.com/O9xB5.png")
.attr("transform", "translate(" + midPoint[0] + ", " + midPoint[1] + ")")
.append("title")
.text(d.properties.abbr);
svg.append("svg:text")
.attr("x", midPoint[0])
.attr("y", midPoint[1])
.text(d.properties.abbr);
}
});
JSFiddle
To include abbr details to the data, code as shown below.
// Find the corresponding state inside the GeoJSON
for (var j = 0; j < json.features.length; j++) {
var jsonState = json.features[j].properties.name;
if (dataState == jsonState) {
// Copy the data value into the JSON
json.features[j].properties.MembersPresent = dataValue;
json.features[j].properties.pop = +dataPop;
json.features[j].properties.abbr = data[i]["State Abbr"];
// Stop looking through the JSON
break;
}
}

Animate position of svg rect on transition

Edit: here is an example Fiddle: https://jsfiddle.net/3c9dtLyh/6/
I have a layout that I am attempting to animate to compare the arrangement on two different dates. What I would like to accomplish is a transition where items whose x,y position is different on the second date smoothly fly to the new position. I have attempted to do this using an updateData function set to trigger onclick.
The layout looks like this:
I do not neccesarily expect this approach to work because how would the transition know which (x,y) pairs correspond to the correct item name in the new arrangement. What am I missing about how these transitions work and how could I improve my approach?
Here is the code I am using. It's a relatively simple sequence of appending and svg element, drawing the rectangles, then (failing) to update their position on click.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.dot {
stroke: #000;
}
</style>
<body>
<div id = "chart">
</div>
<div id = "select_params">
<input name="updateButton"
type="button"
value="Update"
onclick="updateData()" />
</div>
</body>
<!-- load js libraries -->
<script src="https://d3js.org/d3.v4.min.js"></script> <!-- uses v4 of d3 -->
<script type="text/javascript" src="http://code.jquery.com/jquery-1.6.2.min.js"></script> <!-- need to use this older version for tipsy -->
<script type="text/javascript" src="jquery.tipsy.js"></script> <!-- load from locally hosted source code -->
<!-- build the visualization -->
<script type='text/javascript'>
var item_width = 40, item_height = 60;
var margin = {top: 20, right: 50, bottom: 75, left: 40},
width = 700 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scaleLinear()
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var color = d3.scaleOrdinal(d3.schemeCategory10);
var svg = d3.select("#chart").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 + ")");
d3.csv("http://localhost:8080/udacity_test_vis_1/combined_item_pos.csv", function(data) {
// cast string to numeric
data.forEach(function(d) {
d.x_pos = +d.x_pos;
d.y_pos = +d.y_pos;
d.sales = +d.sales;
});
console.log(data);
var x_offset = 5, y_offset = 5;
x.domain(d3.extent(data, function(d) { return d.x_pos; })); // set the x domain
y.domain(d3.extent(data, function(d) { return d.y_pos; })); // set the y domain
svg.selectAll("g")
.data(data)
.enter()
.append("rect")
.filter(function(d){ return d.date == '1-20-2017'})
.attr("class", "dot")
.attr("width", item_width)
.attr("height", item_height)
.attr("x", function(d) { return x(d.x_pos) + x_offset; }) // x position of dots
.attr("y", function(d) { return y(d.y_pos) + y_offset; }) // y position of dots
.attr("rx", 5)
.attr("ry", 5)
.style("fill", "#1f5fc6") // color factor variable
.style("fill-opacity", 0.5);
svg.selectAll("g")
.data(data)
.enter()
.append("text")
.filter(function(d){ return d.date == '1-20-2017'})
.attr("x", function(d) { return x(d.x_pos) + item_width/2 + x_offset; })
.attr("y", function(d) { return y(d.y_pos) + item_height/2 + y_offset; })
.attr("font-size", 10)
.attr("text-anchor", "middle")
.attr("fill", "black")
.text(function(d){ return d.item_name});
});
function updateData() {
// grab the data again
d3.csv("http://localhost:8080/udacity_test_vis_1/combined_item_pos.csv", function(data) {
// cast string to numeric
data.forEach(function(d) {
d.x_pos = +d.x_pos;
d.y_pos = +d.y_pos;
d.sales = +d.sales;
});
var svg = d3.select("#chart").transition();
svg.selectAll("g")
.data(data)
.enter()
.append("rect")
.filter(function(d){ return d.date == '2-10-2017'})
.attr("class", "dot")
.attr("width", item_width)
.attr("height", item_height)
.attr("x", function(d) { return x(d.x_pos) + x_offset; }) // x position of dots
.attr("y", function(d) { return y(d.y_pos) + y_offset; }) // y position of dots
.attr("rx", 5)
.attr("ry", 5)
.style("fill", "#1f5fc6") // color factor variable
.style("fill-opacity", 0.5);
svg.selectAll("g")
.data(data)
.enter()
.append("text")
.filter(function(d){ return d.date == '2-10-2017'})
.attr("x", function(d) { return x(d.x_pos) + item_width/2 + x_offset; })
.attr("y", function(d) { return y(d.y_pos) + item_height/2 + y_offset; })
.attr("font-size", 10)
.attr("text-anchor", "middle")
.attr("fill", "black")
.text(function(d){ return d.item_name});
});
}
</script>
Here is my data:
,x_pos,y_pos,item_name,sales,date
0,1,1,S8221,2022,1-20-2017
1,2,1,NLC11,518,1-20-2017
2,3,1,35UUY,1614,1-20-2017
3,4,1,PPTNV,1059,1-20-2017
4,5,1,G0CWS,2183,1-20-2017
5,6,1,3JHUA,2513,1-20-2017
6,7,1,4HXGA,2251,1-20-2017
7,8,1,RYM9K,2330,1-20-2017
8,9,1,T8PUB,1476,1-20-2017
9,10,1,PLULW,1225,1-20-2017
10,1,2,YJ6S0,2403,1-20-2017
11,2,2,E9RGD,1361,1-20-2017
12,3,2,E2SW4,1131,1-20-2017
13,4,2,BZPGX,698,1-20-2017
14,5,2,0K682,1855,1-20-2017
15,6,2,D8UZW,2371,1-20-2017
16,7,2,USKY7,1851,1-20-2017
17,8,2,D0L0Y,1767,1-20-2017
18,9,2,P1AGP,1025,1-20-2017
19,10,2,9LT7O,1380,1-20-2017
20,1,3,1J184,1108,1-20-2017
21,2,3,RJDEG,2106,1-20-2017
22,3,3,LTSLR,1980,1-20-2017
23,4,3,ET3DF,2700,1-20-2017
24,5,3,42W1W,2194,1-20-2017
25,6,3,5QTJN,958,1-20-2017
26,7,3,O8XKY,2381,1-20-2017
27,8,3,LS9NW,516,1-20-2017
28,9,3,0MPZ7,2198,1-20-2017
29,10,3,R4E3J,2494,1-20-2017
30,1,4,WFPPY,2349,1-20-2017
31,2,4,MT2DB,2525,1-20-2017
32,3,4,6DRYS,600,1-20-2017
33,4,4,NVV0S,1556,1-20-2017
34,5,4,ODGZ2,912,1-20-2017
35,6,4,E3NLS,931,1-20-2017
36,7,4,9FFZ7,722,1-20-2017
37,8,4,UKZGF,2170,1-20-2017
38,9,4,XXORI,896,1-20-2017
39,10,4,QYU9Q,1104,1-20-2017
40,1,5,4KQPU,1562,1-20-2017
41,2,5,S3AYK,2298,1-20-2017
42,3,5,5W3CE,2580,1-20-2017
43,4,5,T0S7H,1677,1-20-2017
44,5,5,02SJG,1972,1-20-2017
45,6,5,GBMNZ,1845,1-20-2017
46,7,5,2Y7KH,982,1-20-2017
47,8,5,3WMOL,1952,1-20-2017
48,9,5,93KLU,2240,1-20-2017
49,10,5,K80OQ,2467,1-20-2017
50,1,6,2SIJS,1788,1-20-2017
51,2,6,5ZJ7V,2277,1-20-2017
52,3,6,HTL99,873,1-20-2017
53,4,6,C06QP,2185,1-20-2017
54,5,6,2S1YI,580,1-20-2017
55,6,6,IQ0L8,2395,1-20-2017
56,7,6,PEE2Y,2299,1-20-2017
57,8,6,6DEWK,2019,1-20-2017
58,9,6,9FY5B,1517,1-20-2017
59,10,6,NZQ54,2624,1-20-2017
60,1,7,C4SVV,1823,1-20-2017
61,2,7,Q4C4I,2339,1-20-2017
62,3,7,996OQ,1621,1-20-2017
63,4,7,PISK6,895,1-20-2017
64,5,7,KOKHE,1315,1-20-2017
65,6,7,6P4FT,1467,1-20-2017
66,7,7,3FY75,2085,1-20-2017
67,8,7,9YCNB,992,1-20-2017
68,9,7,NXXK1,2080,1-20-2017
69,10,7,4RDHV,2031,1-20-2017
0,6,1,9FFZ7,592,2-10-2017
1,1,6,E2SW4,622,2-10-2017
2,6,7,PLULW,1699,2-10-2017
3,8,3,ET3DF,784,2-10-2017
4,9,4,KOKHE,1092,2-10-2017
5,2,6,5ZJ7V,1691,2-10-2017
6,4,5,9FY5B,630,2-10-2017
7,9,4,G0CWS,1523,2-10-2017
8,9,2,PISK6,1778,2-10-2017
9,6,4,35UUY,2107,2-10-2017
10,3,5,5QTJN,1751,2-10-2017
11,6,6,NLC11,526,2-10-2017
12,8,2,C06QP,2308,2-10-2017
13,8,3,XXORI,1453,2-10-2017
14,5,1,E9RGD,1864,2-10-2017
15,7,2,HTL99,1222,2-10-2017
16,3,3,PEE2Y,2050,2-10-2017
17,9,7,GBMNZ,1941,2-10-2017
18,3,1,T8PUB,1440,2-10-2017
19,5,1,3WMOL,2692,2-10-2017
20,7,7,S3AYK,523,2-10-2017
21,1,5,BZPGX,2245,2-10-2017
22,2,1,S8221,2241,2-10-2017
23,9,7,IQ0L8,566,2-10-2017
24,8,5,D8UZW,1769,2-10-2017
25,3,1,RYM9K,1044,2-10-2017
26,4,6,4HXGA,2650,2-10-2017
27,2,2,WFPPY,2203,2-10-2017
28,2,4,93KLU,2289,2-10-2017
29,7,3,P1AGP,1084,2-10-2017
30,4,3,3JHUA,1364,2-10-2017
31,1,4,9LT7O,1198,2-10-2017
32,4,6,4RDHV,771,2-10-2017
33,10,7,T0S7H,873,2-10-2017
34,3,6,NXXK1,2391,2-10-2017
35,8,2,2SIJS,811,2-10-2017
36,8,4,LTSLR,1670,2-10-2017
37,6,7,02SJG,1880,2-10-2017
38,9,3,0MPZ7,2090,2-10-2017
39,2,6,E3NLS,2350,2-10-2017
40,7,6,QYU9Q,1092,2-10-2017
41,6,3,0K682,894,2-10-2017
42,1,5,LS9NW,1928,2-10-2017
43,7,7,NVV0S,951,2-10-2017
44,9,4,996OQ,670,2-10-2017
45,7,6,USKY7,706,2-10-2017
46,10,4,Q4C4I,2270,2-10-2017
47,4,2,UKZGF,1691,2-10-2017
48,10,3,RJDEG,597,2-10-2017
49,10,2,1J184,1921,2-10-2017
50,2,3,5W3CE,2604,2-10-2017
51,5,5,3FY75,1260,2-10-2017
52,1,1,6DEWK,2491,2-10-2017
53,7,5,9YCNB,1743,2-10-2017
54,4,7,6DRYS,2450,2-10-2017
55,5,2,MT2DB,1292,2-10-2017
56,8,5,C4SVV,1395,2-10-2017
57,3,7,ODGZ2,2685,2-10-2017
58,10,4,2S1YI,2617,2-10-2017
59,1,2,YJ6S0,1611,2-10-2017
60,6,3,2Y7KH,2188,2-10-2017
61,5,4,4KQPU,1413,2-10-2017
62,10,1,D0L0Y,2291,2-10-2017
63,5,1,NZQ54,1405,2-10-2017
64,5,2,6P4FT,1885,2-10-2017
65,3,1,PPTNV,1442,2-10-2017
66,1,5,K80OQ,2140,2-10-2017
67,4,5,42W1W,1697,2-10-2017
68,2,7,O8XKY,1007,2-10-2017
69,10,6,R4E3J,887,2-10-2017
So, I took a few minutes to completely refactor your code into proper d3 style. This aims to demonstrate a couple things:
The proper use of the enter, update, exit pattern.
Removed cut / paste duplicate code.
The proper way to use g to group elements and position them together.
How to add the transitions.
Here is the code running.
Commented code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.dot {
stroke: #000;
}
</style>
<body>
<div id="chart">
</div>
<div id="select_params">
<input name="updateButton" type="button" value="Update" onclick="updateData()" />
</div>
</body>
<!-- load js libraries -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<!-- uses v4 of d3 -->
<!-- build the visualization -->
<script type='text/javascript'>
var item_width = 40,
item_height = 60;
var margin = {
top: 20,
right: 50,
bottom: 75,
left: 40
},
width = 700 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scaleLinear()
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var color = d3.scaleOrdinal(d3.schemeCategory10);
var svg = d3.select("#chart").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 + ")");
// a single function to draw
function draw(data, someDate) {
data.forEach(function(d) {
d.x_pos = +d.x_pos;
d.y_pos = +d.y_pos;
d.sales = +d.sales;
});
// pre-filter data
data = data.filter(function(d) {
return d.date === someDate
});
var x_offset = 5,
y_offset = 5;
x.domain(d3.extent(data, function(d) {
return d.x_pos;
})); // set the x domain
y.domain(d3.extent(data, function(d) {
return d.y_pos;
})); // set the y domain
// create an update selection with a key function
var g_sel = svg.selectAll("g")
.data(data, function(d) {
return d.item_name;
});
// get rid of those leaving the update
g_sel.exit().remove();
// our entering g
var g_ent = g_sel.enter()
.append("g");
// add our rects to our g
g_ent.append("rect")
.attr("class", "dot")
.attr("width", item_width)
.attr("height", item_height)
.attr("rx", 5)
.attr("ry", 5)
.style("fill", "#1f5fc6") // color factor variable
.style("fill-opacity", 0.5);
// add our text to our g
g_ent.append("text")
.attr("font-size", 10)
.attr("text-anchor", "middle")
.attr("fill", "black")
.attr("dx", item_width / 2)
.attr("dy", item_height / 2)
.text(function(d) {
return d.item_name
});
// UPDATE + ENTER selection
g_sel = g_ent.merge(g_sel);
// move them into position with transition
g_sel
.transition()
.attr("transform", function(d) {
return "translate(" + (x(d.x_pos) + x_offset) + "," + (y(d.y_pos) + y_offset) + ")";
});
}
d3.csv("test.csv", function(data) {
draw(data, '1-20-2017');
});
function updateData() {
d3.csv("test.csv", function(data) {
draw(data, '2-10-2017');
});
}
</script>
Heres my attempt: https://jsfiddle.net/guanzo/3c9dtLyh/10/
There are multiple data points that share the same position, which is why some rectangles are overlapping. I made a lot of changes to your code that resulted in less repetition.
Your data contains duplicate item_names with different dates/positions, but in your visualization you seem to want to only show items at a single date. Therefore, you only need to pass d3 data for a certain date, versus passing d3 ALL the data and then filtering.
Your code:
svg.selectAll("g")
.data(data)
.enter()
.append("rect")
.filter(function(d){ return d.date == '1-20-2017'})
My code:
var firstDateData = data.filter(d=>d.date == '1-20-2017');
var groups = svg.selectAll("g")
.data(firstDateData, d=> d.item_name)
The difference between these 2 is that in my example, D3 is only aware of a single set of item_names on date 1-20-2017. Therefore when i update the date with item_names on date 2-10-2017, D3 will automatically move all updated rectangles to their new position. How?
Here is where your question comes into play:
I do not neccesarily expect this approach to work because how would
the transition know which (x,y) pairs correspond to the correct item
name in the new arrangement
This is because i associated each rectangle with an item_name. D3s data function can take an optional 2nd parameter that specifies HOW the data is bound to the rectangles. This is called the key function.
svg.selectAll("g").data(firstDateData, d=> d.item_name)
In this case, i told d3 that each group (rectangles and their text) is bound to item_name. Therefore, the next time i pass data to D3, it tries to match existing elements (that are associated with an item_name) to the data (which contains item_names). If in my new data, i pass an item_name that corresponds to an existing element, and the data contains a new x and y position, D3 will move to element to that new position.
Note that even though i'm talking about rectangles, i bound data to the g element, which contains the rectangle and the text.
Feel free to ask any questions, i made a lot of changes that i didn't discuss.

Access a specific path element using given ID

I am using Topojson and world-110m.json to visualize a map of the world. I am trying to change the fill property of two particular countries by click event.
The first country is selected by click from the user side and the ID of that path is retrieved using:
var current_country = d3.select(this).style("fill", "red");
var current_country_ID = d3.select(this).attr('id');
The second country to be changed is given (does not really matter) and defined by ID. I have tried using this:
var top = d3.select("path#643").style("fill", "green");
As suggested here: How to select a d3 svg path with a particular ID
Seems pretty straight forward but I always get the same error no matter what I try:
Uncaught SyntaxError: Failed to execute 'querySelector' on 'Document': 'path#643' is not a valid selector.
I have tried many things (all combinations that come to my mind) and I have seen a lot of similar questions posted here but have failed to find the proper solution. A path with that ID does in fact exist. And all of the countries are stored in the world variable and they seem OK to me.
Here is my full code:
var width = 2000;
var height = 2000;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var projection = d3.geo.mercator()
.center([50,70])
.scale(150)
.rotate([0,0,0]);
var path = d3.geo.path().projection(projection);
var g = svg.append("g");
var country_selector = d3.select("body").append("div").attr("class", "country_selector");
queue()
.defer(d3.json, "world-110m.json")
.defer(d3.csv, "country_data2.csv")
.await(main);
function main(error, world, countryData){
var country_names = {};
var countries = topojson.feature(world, world.objects.countries).features;
countryData.forEach(function(d) {
country_names[d.id] = d.name;
});
var world = g.selectAll("path")
.data(countries)
.enter()
.append("svg:path")
.attr("d", path)
.attr("id", function(d) { return d.id; })
.attr("class", function(d) { return d.id; })
.on("mouseover", function(d) {
country_selector.text(country_names[d.id])
.style("left", (d3.event.pageX + 7) + "px")
.style("top", (d3.event.pageY - 15) + "px")
.style("display", "block")
.style("opacity", 0.8);
})
.on("mouseout", function(d) {
country_selector.style("opacity", 0)
.style("display", "none");
})
.on("mousemove", function(d) {
country_selector.style("left", (d3.event.pageX + 7) + "px")
.style("top", (d3.event.pageY - 15) + "px");
})
.on("click", function(d) { // the rouble part
var current_country = d3.select(this).style("fill", "red")
var current_country_ID = d3.select(this).attr('id')
console.log(current_country)
console.log(current_country_ID)
console.log(world)
var top = d3.select("path#643").style("fill", "green"); // the bad guy
console.log(top)
})
}; // end of main function
var zooming = d3.behavior.zoom()
.on("zoom",function() {
g.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
g.selectAll("path")
.attr("d", path.projection(projection));
});
svg.call(zooming).on("dblclick.zoom", null);
Any help would be greatly appreciated.
ids can't start with numbers. So the id itself is invalid. You can change the id in the HTML to something like _643, then in your JavaScript do
var top = d3.select("path#_643").style("fill", "green");
Here's an example using CSS to show id validity
#643 {
background-color: orange;
color: #FFF;
}
#_643 {
background-color: orange;
color: #FFF;
}
#-643 {
background-color: orange;
color: #FFF;
}
#six43 {
background-color: orange;
color: #FFF;
}
<ul>
<li id="643">643</li>
<li id="_643">_643</li>
<li id="-643">-643</li>
<li id="six43">six43</li>
</ul>

Categories

Resources