Update data source for D3 TopoJSON map - javascript

I am currently attempting to update the data source for a TopoJson map so that the points on the map get replotted when a button is pressed / updateData() function is called.
Here is my code:
<head>
<script src="js/d3.js"></script>
<script src="http://d3js.org/topojson.v0.min.js"></script>
<style>
path {
stroke: white;
stroke-width: 0.25px;
fill: grey;
}
</style>
</head>
<body>
<div id="option">
<input name="updateButton"
type="button"
value="Update"
onclick="updateData()" />
<script>
var width = 960,
height = 500;
var projection = d3.geo.mercator()
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var path = d3.geo.path()
.projection(projection);
var g = svg.append("g");
// load and display the World
d3.json("data/world-110m2.json", function(error, topology) {
// load and display the cities
d3.json("data/commodities3.json", function(error, data) {
g.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return projection([d.location_lon, d.location_lat])[0];})
.attr("cy", function(d) {
return projection([d.location_lon, d.location_lat])[1];})
.style("fill", function(d){
if(d.commodity_text == "Tin") {
return "blue";
} else {
return "red";
}
})
.attr("r", function(d){
if(d.commodity_text == "Iron") {
return 10;
} else {
return 4;
}
});
});
g.selectAll("path")
.data(topojson.object(topology, topology.objects.countries)
.geometries)
.enter()
.append("path")
.attr("d", path)
});
// zoom and pan
var zoom = d3.behavior.zoom()
.on("zoom",function() {
g.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
g.selectAll("circle")
.attr("d", path.projection(projection));
g.selectAll("path")
.attr("d", path.projection(projection));
});
svg.call(zoom)
function updateData(){
var g = svg.append("g").transition();
d3.json("data/commodities3.json", function(error, data) {
g.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return projection([d.location_lon, d.location_lat])[0];})
.attr("cy", function(d) {
return projection([d.location_lon, d.location_lat])[1];})
.style("fill", function(d){
if(d.commodity_text == "Tin") {
return "blue";
} else {
return "red";
}
})
.attr("r", function(d){
if(d.commodity_text == "Iron") {
return 10;
} else {
return 4;
}
});
});
}
</script>
</body>
</html>
Currently I am calling my transition function on my variable 'g', but I am not sure where to progress from here. Any help would be very appreciated. Thank you.

Related

D3: How to draw multiple Convex hulls on Groups of Force layout nodes?

I'm trying to draw convex hulls on all the groups in a force layout. But I only manage to draw half of the convex hulls. When D3 tries to draw the rest of the hulls, the console returns ERROR: the elements have not been created yet. Yet when I check the "groups" variable in the console, all the groups data are there with x, y data all nicely set up. See picture below:
I even tried delaying the drawing of the hull in the tick function, but it still doesn't work & I get the same results (as seen in picture below).
JSFiddle: Only getting half the no. of convex hulls I want
Here is the code:
<script>
var radius = 5.5;
var color = d3.scaleOrdinal(d3.schemeCategory20b);
var scale = d3.scaleLinear()
.domain([0.5, 1])
.range([1.8, 3.8]);
var svg2 = d3.select("#svg2");
var w = +svg2.attr("width"),
h = +svg2.attr("height");
var hull = svg2.append("path")
.attr("class", "hull");
var groupPath = function(d) { return "M" + d3.polygonHull(d.values.map(function(i) { return [i.x, i.y]; }))
.join("L") + "Z"; };
function ticked() {
link
.attr("x1", function (d) {
return d.source.x;
})
.attr("y1", function (d) {
return d.source.y;
})
.attr("x2", function (d) {
return d.target.x;
})
.attr("y2", function (d) {
return d.target.y;
});
fnode
.attr("cx", function (d) {
return d.x = Math.max(radius, Math.min(w - radius, d.x));
})
.attr("cy", function (d) {
return d.y = Math.max(radius, Math.min(h - radius, d.y));
})
.attr("r", radius);
delayHull(6000);
}
function delayHull(delay) {
setTimeout(function() {
svg2.selectAll("path")
.data(groups)
.attr("d", groupPath)
.enter()
.append("path")
.attr("class", "hull")
.attr("d", groupPath);
}, delay);
}
var simulation, link, fnode, groups;
var fnodeg = svg2.append("g")
.attr("class", "fnode");
var linkg = svg2.append("g")
.attr("class", "links")
.attr("id", "linkg");
d3.json("..//vizData//forceLayout//forceLayout_15000.json", function(error, graph) {
if (error) throw error;
simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(30).strength(1))
.force("charge", d3.forceManyBody().strength(-2).distanceMin(15).distanceMax(180))
.force("center", d3.forceCenter(w / 2, h / 2))
.force("collide", d3.forceCollide().strength(1).iterations(2));
link = linkg.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke-width", function (d) {
return scale(d.value);
});
fnode = fnodeg.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", radius)
.attr("fill", function (d) {
return color(d.truth);
});
simulation
.nodes(graph.nodes);
simulation.force("link")
.links(graph.links);
groups = d3.nest().key(function(d) { return d.group; }).entries(graph.nodes);
simulation.on("tick", ticked);
fnode.append("title")
.text(function (d) { return d.id; });
link.append("title")
.text(function (d) { return d.value; });
})
</script>
I referenced this http://bl.ocks.org/donaldh/2920551 convex hull example; he set up his "groups" variable outside the tick function, and it was ok.
What am I doing wrong???
Building upon Andrew's answer, you can simply push another inner array when your cluster have just two points:
if (d.values.length === 2) {
var arr = d.values.map(function(i) {
return [i.x, i.y];
})
arr.push([arr[0][0], arr[0][1]]);
return "M" + d3.polygonHull(arr).join("L") + "Z";
Here is your code with that change only:
var radius = 5.5;
var color = d3.scaleOrdinal(d3.schemeCategory20b);
var scale = d3.scaleLinear()
.domain([0.5, 1])
.range([1.8, 3.8]);
var svg2 = d3.select("#svg2");
var w = +svg2.attr("width"),
h = +svg2.attr("height");
var hull = svg2.append("path")
.attr("class", "hull");
var groupPath = function(d) {
if (d.values.length === 2) {
var arr = d.values.map(function(i) {
return [i.x, i.y];
})
arr.push([arr[0][0], arr[0][1]]);
return "M" + d3.polygonHull(arr).join("L") + "Z";
} else {
return "M" + d3.polygonHull(d.values.map(function(i) {
return [i.x, i.y];
}))
.join("L") + "Z";
}
};
function ticked() {
link
.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
fnode
.attr("cx", function(d) {
return d.x = Math.max(radius, Math.min(w - radius, d.x));
})
.attr("cy", function(d) {
return d.y = Math.max(radius, Math.min(h - radius, d.y));
})
.attr("r", radius);
delayHull(1000);
}
function delayHull(delay) {
setTimeout(function() {
svg2.selectAll("path")
.data(groups)
.attr("d", groupPath)
.enter()
.append("path")
.attr("class", "hull")
.attr("d", groupPath);
}, delay);
}
var simulation, link, fnode, groups;
var fnodeg = svg2.append("g")
.attr("class", "fnode");
var linkg = svg2.append("g")
.attr("class", "links")
.attr("id", "linkg");
d3.json('https://api.myjson.com/bins/bkzxh', function(error, graph) {
if (error) throw error;
simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}).distance(30).strength(1))
.force("charge", d3.forceManyBody().strength(-2).distanceMin(15).distanceMax(180))
.force("center", d3.forceCenter(w / 2, h / 2))
.force("collide", d3.forceCollide().strength(1).iterations(2));
link = linkg.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke-width", function(d) {
return scale(d.value);
});
fnode = fnodeg.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", radius)
.attr("fill", function(d) {
return color(d.truth);
});
simulation
.nodes(graph.nodes);
simulation.force("link")
.links(graph.links);
groups = d3.nest().key(function(d) {
return d.group;
}).entries(graph.nodes);
simulation.on("tick", ticked);
fnode.append("title")
.text(function(d) {
return d.id;
});
link.append("title")
.text(function(d) {
return d.value;
});
});
.links line {
stroke: #999;
stroke-opacity: 0.8;
}
.fnode circle {
stroke: #fff;
stroke-width: 1.5px;
fill-opacity: 1;
}
.hull {
fill: steelblue;
stroke: steelblue;
fill-opacity: 0.3;
stroke-opacity: 0.3;
stroke-width: 10px;
stroke-linejoin: round;
}
<script src="https://d3js.org/d3.v4.js"></script>
<svg id="svg2" width="600" height="600" style="margin-left: -5px"></svg>
Your issue, I believe, is that some groups only have 2 nodes. In v4 this generates a type error, as d3.polygonHull() will return null if using two points - a convex hull requires three points (presumably not in a line. Correction - they can be in a line, see Gerardo's comment on this answer and his answer as well). The following snippets are barely modified from Mike's canonical example:
This snippet demonstrates the problem:
var width = 960,
height = 500;
var randomX = d3.randomNormal(width / 2, 60),
randomY = d3.randomNormal(height / 2, 60),
vertices = d3.range(2).map(function() { return [randomX(), randomY()]; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.on("mousemove", function() { vertices[0] = d3.mouse(this); redraw(); })
.on("click", function() { vertices.push(d3.mouse(this)); redraw(); });
svg.append("rect")
.attr("width", width)
.attr("height", height);
var hull = svg.append("path")
.attr("class", "hull");
var circle = svg.selectAll("circle");
redraw();
function redraw() {
hull.datum(d3.polygonHull(vertices)).attr("d", function(d) { return "M" + d.join("L") + "Z"; });
circle = circle.data(vertices);
circle.enter().append("circle").attr("r", 3);
circle.attr("transform", function(d) { return "translate(" + d + ")"; });
}
rect {
fill: none;
pointer-events: all;
}
.hull {
fill: steelblue;
stroke: steelblue;
stroke-width: 32px;
stroke-linejoin: round;
}
circle {
fill: white;
stroke: black;
stroke-width: 1.5px;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
It seems this didn't generate an error in v3:
var width = 960,
height = 500;
var randomX = d3.random.normal(width / 2, 60),
randomY = d3.random.normal(height / 2, 60),
vertices = d3.range(2).map(function() { return [randomX(), randomY()]; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.on("mousemove", function() { vertices[0] = d3.mouse(this); redraw(); })
.on("click", function() { vertices.push(d3.mouse(this)); redraw(); });
svg.append("rect")
.attr("width", width)
.attr("height", height);
var hull = svg.append("path")
.attr("class", "hull");
var circle = svg.selectAll("circle");
redraw();
function redraw() {
hull.datum(d3.geom.hull(vertices)).attr("d", function(d) { return "M" + d.join("L") + "Z"; });
console.log(d3.geom.hull(vertices));
circle = circle.data(vertices);
circle.enter().append("circle").attr("r", 3);
circle.attr("transform", function(d) { return "translate(" + d + ")"; });
}
rect {
fill: none;
pointer-events: all;
}
.hull {
fill: steelblue;
stroke: steelblue;
stroke-width: 32px;
stroke-linejoin: round;
}
circle {
fill: white;
stroke: black;
stroke-width: 1.5px;
}
<script src="https://d3js.org/d3.v3.min.js"></script>

Drawing a map with census tracks and population in d3

Here is my gist, all the contents are in place. Why does it not draw the population and the tracts data?
var width = 960, height = 500;
var data; // declare a global variable
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height)
var threshold = d3.scaleThreshold()
.domain([1, 10, 20, 200, 800, 2000, 5000, 10000])
.range(d3.schemeOrRd[9]);
d3.queue()
.defer(d3.json, 'https://umbcvis.github.io/classes/class-12/tracts.json')
.defer(d3.json, 'https://umbcvis.github.io/classes/class-12/population.json')
.await(ready);
// Note: scale and translate will be determined by the data
var projection = d3.geoConicConformal()
.parallels([38 + 18 / 60, 39 + 27 / 60])
.rotate([77, -37 - 40 / 60]);
var path = d3.geoPath()
.projection(projection);
function ready(error, json, population) {
if (error) throw error;
// Convert topojson to GeoJSON
geojson = topojson.feature(json, json.objects.tracts);
tracts = geojson.features;
// Set the projection's scale and translate based on the GeoJSON
projection.fitSize([960, 500], geojson);
// Extract an array of features (one tract for each feature)
tracts.forEach(function(tract) {
var countyfips = tract.properties.COUNTYFP;
var tractce = tract.properties.TRACTCE;
pop = population.filter(function(d) {
return (d[2] === countyfips) &&
(d[3] === tractce);
});
pop = +pop[0][0];
var aland = tract.properties.ALAND / 2589975.2356;
//area in square miles
tract.properties.density = pop / aland;
});
svg.selectAll('path.tract')
.data(tracts)
.enter()
.append('path')
.attr('class', 'tract')
// .attr('d', path)
.style('fill', function(d) {
return threshold(d.properties.density);
})
.style('stroke', '#000')
// Draw all counties in MD
fips = geojson.features.map(function(d) {
return d.properties.COUNTYFP;
});
uniqueFips = d3.set(fips).values();
counties = uniqueFips.map(function(fips) {
return json.objects.tracts.geometries
.filter(function(d) {
return d.properties.COUNTYFP === fips;
});
});
counties = counties.map(function(county) {
return topojson.merge(json, county);
})
svg.selectAll("path.county")
.data(counties)
.enter()
.append('path')
.attr('class', 'county')
.attr('d', path)
.style('stroke', 'red')
.style('stroke-width', '2px')
.style('fill', 'none');
// 1. NEW: Define an array with Rockville MD longitude & latitude
var Rockville = [-77.1528, 39.0840];
// 2. NEW: Add an HTML <div> element that will function as the tooltip
var tooltip = d3.select('body').append('div')
.attr('class', 'tooltip')
.text('Hello, world!')
// 3. NEW: Add a draggable circle to the map
// See: https://bl.ocks.org/mbostock/22994cc97fefaeede0d861e6815a847e)
var layer2 = svg.append("g");
layer2.append("circle")
.attr("class", "Rockville")
.attr("cx", projection(Rockville)[0])
.attr("cy", projection(Rockville)[1])
.attr("r", 10)
.style("fill", "yellow") // Make the dot yellow
.call(d3.drag() // Add the drag behavior
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
//Add legend
addLegend();
}
function addLegend() {
var formatNumber = d3.format("d");
var x = d3.scalePow().exponent('.15')
.domain([1, 80000])
.range([0, 300]);
var xAxis = d3.axisBottom(x)
.tickSize(13)
.tickValues(threshold.domain())
.tickFormat(formatNumber)
var g = svg.append("g")
.attr('transform', 'translate(100, 200)')
.call(xAxis);
g.select(".domain")
.remove();
g.selectAll("rect")
.data(threshold.range().map(function(color) {
var d = threshold.invertExtent(color);
if (d[0] == null) d[0] = x.domain()[0];
if (d[1] == null) d[1] = x.domain()[1];
return d;
}))
.enter().insert("rect", ".tick")
.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 threshold(d[0]);
});
g.append("text")
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", "start")
.attr("y", -6)
.text("Population per square mile");
}
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("active", false);
}
path {
fill: #555555;
stroke: #aaaaaa;
}
body {
position: absolute;
margin: 0px;
font: 16px sans-serif;
}
.info {
color: #000;
position: absolute;
top: 450px;
left: 800px;
}
.tooltip {
position: absolute;
visibility: visible;
background-color: #aaa;
padding: 5px;
}
/* This style is used when dragging the dot */
.active {
stroke: #000;
stroke-width: 2px;
}
path {
fill: #555555;
stroke: #aaaaaa;
}
svg {
background-color: #4682b4;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
Gist: https://bl.ocks.org/MTClass/0fb9c567311cfd2ea31f884a974fd246
You have not defined a path for your tracts:
svg.selectAll('path.tract')
.data(tracts)
.enter()
.append('path')
.attr('class', 'tract')
// .attr('d', path)
.style('fill', function(d) {
return threshold(d.properties.density);
})
.style('stroke', '#000')
For some reason this is commented out:
.attr('d', path)
So your tracts have no shape.
example:
<path class="tract" style="fill: rgb(215, 48, 31); stroke: rgb(0, 0, 0);"></path>
Try uncommenting that line, and you should get:
var width = 960, height = 500;
var data; // declare a global variable
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height)
var threshold = d3.scaleThreshold()
.domain([1, 10, 20, 200, 800, 2000, 5000, 10000])
.range(d3.schemeOrRd[9]);
d3.queue()
.defer(d3.json, 'https://umbcvis.github.io/classes/class-12/tracts.json')
.defer(d3.json, 'https://umbcvis.github.io/classes/class-12/population.json')
.await(ready);
// Note: scale and translate will be determined by the data
var projection = d3.geoConicConformal()
.parallels([38 + 18 / 60, 39 + 27 / 60])
.rotate([77, -37 - 40 / 60]);
var path = d3.geoPath()
.projection(projection);
function ready(error, json, population) {
if (error) throw error;
// Convert topojson to GeoJSON
geojson = topojson.feature(json, json.objects.tracts);
tracts = geojson.features;
// Set the projection's scale and translate based on the GeoJSON
projection.fitSize([960, 500], geojson);
// Extract an array of features (one tract for each feature)
tracts.forEach(function(tract) {
var countyfips = tract.properties.COUNTYFP;
var tractce = tract.properties.TRACTCE;
pop = population.filter(function(d) {
return (d[2] === countyfips) &&
(d[3] === tractce);
});
pop = +pop[0][0];
var aland = tract.properties.ALAND / 2589975.2356;
//area in square miles
tract.properties.density = pop / aland;
});
svg.selectAll('path.tract')
.data(tracts)
.enter()
.append('path')
.attr('class', 'tract')
.attr('d', path)
.style('fill', function(d) {
return threshold(d.properties.density);
})
.style('stroke', '#000')
// Draw all counties in MD
fips = geojson.features.map(function(d) {
return d.properties.COUNTYFP;
});
uniqueFips = d3.set(fips).values();
counties = uniqueFips.map(function(fips) {
return json.objects.tracts.geometries
.filter(function(d) {
return d.properties.COUNTYFP === fips;
});
});
counties = counties.map(function(county) {
return topojson.merge(json, county);
})
svg.selectAll("path.county")
.data(counties)
.enter()
.append('path')
.attr('class', 'county')
.attr('d', path)
.style('stroke', 'red')
.style('stroke-width', '2px')
.style('fill', 'none');
// 1. NEW: Define an array with Rockville MD longitude & latitude
var Rockville = [-77.1528, 39.0840];
// 2. NEW: Add an HTML <div> element that will function as the tooltip
var tooltip = d3.select('body').append('div')
.attr('class', 'tooltip')
.text('Hello, world!')
// 3. NEW: Add a draggable circle to the map
// See: https://bl.ocks.org/mbostock/22994cc97fefaeede0d861e6815a847e)
var layer2 = svg.append("g");
layer2.append("circle")
.attr("class", "Rockville")
.attr("cx", projection(Rockville)[0])
.attr("cy", projection(Rockville)[1])
.attr("r", 10)
.style("fill", "yellow") // Make the dot yellow
.call(d3.drag() // Add the drag behavior
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
//Add legend
addLegend();
}
function addLegend() {
var formatNumber = d3.format("d");
var x = d3.scalePow().exponent('.15')
.domain([1, 80000])
.range([0, 300]);
var xAxis = d3.axisBottom(x)
.tickSize(13)
.tickValues(threshold.domain())
.tickFormat(formatNumber)
var g = svg.append("g")
.attr('transform', 'translate(100, 200)')
.call(xAxis);
g.select(".domain")
.remove();
g.selectAll("rect")
.data(threshold.range().map(function(color) {
var d = threshold.invertExtent(color);
if (d[0] == null) d[0] = x.domain()[0];
if (d[1] == null) d[1] = x.domain()[1];
return d;
}))
.enter().insert("rect", ".tick")
.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 threshold(d[0]);
});
g.append("text")
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", "start")
.attr("y", -6)
.text("Population per square mile");
}
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("active", false);
}
path {
fill: #555555;
stroke: #aaaaaa;
}
body {
position: absolute;
margin: 0px;
font: 16px sans-serif;
}
.info {
color: #000;
position: absolute;
top: 450px;
left: 800px;
}
.tooltip {
position: absolute;
visibility: visible;
background-color: #aaa;
padding: 5px;
}
/* This style is used when dragging the dot */
.active {
stroke: #000;
stroke-width: 2px;
}
path {
fill: #555555;
stroke: #aaaaaa;
}
svg {
background-color: #4682b4;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>

Preserve color of a state in mouseover function in d3

I have a map created from a geojson using d3.js lib and I colored randomly different states of map. Now I want to get color of a state when I hover it in mouseover function :
var lastColor;
function mouseover(d) {
lastColor = d.color; //This code is not works for me
d3.select(this)
.style('fill', 'orange')
.style('cursor', 'pointer');
}
function mouseout(d) {
d3.select(this)
.style('fill', lastColor);
}
Is it possible to get the color from d so that I return to this color when I mouseout from this state ?
In the on function, this refers to the DOM element. So, if you set the colour using style, you can get the same colour using style as a getter:
.on('mouseover', function(d){
console.log(d3.select(this).style("fill"))//'style' as a getter
});
Check this demo, hovering over the states (I set the colours using Math.random()):
var width = 720,
height = 375;
var colorScale = d3.scale.category20();
var projection = d3.geo.albersUsa()
.scale(800)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("https://dl.dropboxusercontent.com/u/232969/cnn/us.json", function(error, us) {
svg.selectAll(".state")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path)
.style('fill', function(d) {
return colorScale(Math.random() * 20)
})
.attr('class', 'state')
.on('mouseover', function(d) {
console.log(d3.select(this).style("fill"))
});
svg.append("path")
.datum(topojson.mesh(us, us.objects.states, function(a, b) {
return a !== b;
}))
.attr("d", path)
.attr("class", "state-boundary");
});
.land {
fill: #222;
}
.county-boundary {
fill: none;
stroke: #fff;
stroke-width: .5px;
}
.state-boundary {
fill: none;
stroke: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
EDIT: I have to confess that I read only the title of your question when you first posted it ("How to find the color of a state in mouseover"). Now, after properly reading the text of your post, I reckon the solution is even easier (btw, "Preserve the color of a state" is indeed a better title to the question).
If you set the colour using any property in the data (let's say, id):
.style('fill', function(d){
return colorScale(d.id)
})
You can simply set it again in the "mouseout":
.on('mouseover', function(d) {
d3.select(this).style("fill", "orange")
}).on("mouseout", function(d) {
d3.select(this).style('fill', function(d) {
return colorScale(d.id)
})
});
Check this other demo:
var width = 720,
height = 375;
var colorScale = d3.scale.category20();
var projection = d3.geo.albersUsa()
.scale(800)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("https://dl.dropboxusercontent.com/u/232969/cnn/us.json", function(error, us) {
svg.selectAll(".state")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path)
.style('fill', function(d){
return colorScale(d.id)
})
.attr('class', 'state')
.on('mouseover', function(d){
d3.select(this).style("fill", "orange")
}).on("mouseout", function(d){
d3.select(this).style('fill', function(d){
return colorScale(d.id)
})});
svg.append("path")
.datum(topojson.mesh(us, us.objects.states, function(a, b) {
return a !== b;
}))
.attr("d", path)
.attr("class", "state-boundary");
});
.land {
fill: #222;
}
.county-boundary {
fill: none;
stroke: #fff;
stroke-width: .5px;
}
.state-boundary {
fill: none;
stroke: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>

Read Data from JSON file using d3.js

I am new to d3.js and I am trying to reproduce the example B is for breaking links in this tutorial.
As you can see in the jsfiddle of this example, the data is read from a <script type="application/json" id="mis"> tag, but what I want is to read it from a different file.
I used the d3.json() function as suggested in this Force-Layout example, but when I added the threshold slider which is supposed to break my links, nothing happens.
When I ran the console, it gave me the following error :
Uncaught TypeError: Cannot read property 'splice' of
undefined
threshold # index.html:82
onchange # index.html:101
This is my code :
output.json
The file output.json is in this link.
index.html
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
h3 {
color: #1ABC9C;
text-align:center;
font-style: italic;
font-size: 14px;
font-family: "Helvetica";
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var width = 960,
height = 500;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var graph = d3.json("output.json", function(error, graph) {
if (error) throw error;
graphRec=JSON.parse(JSON.stringify(graph));
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function(d) { return color(d.group); })
.call(force.drag);
node.append("title")
.text(function(d) { return d.name; });
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
});
//adjust threshold
function threshold(thresh) {
graph.links.splice(0, graph.links.length);
for (var i = 0; i < graphRec.links.length; i++) {
if (graphRec.links[i].value > thresh) {graph.links.push(graphRec.links[i]);}
}
restart();
}
//Restart the visualisation after any node and link changes
function restart() {
link = link.data(graph.links);
link.exit().remove();
link.enter().insert("line", ".node").attr("class", "link");
node = node.data(graph.nodes);
node.enter().insert("circle", ".cursor").attr("class", "node").attr("r", 5).call(force.drag);
force.start();
}
</script>
<form>
<h3> Link threshold 0 <input type="range" id="thersholdSlider" name="points" value = 0 min="0" max="10" onchange="threshold(this.value)"> 10 </h3>
</form>
What did I do wrong ? And how can I fix it ?
Thanks!
I could not find any bugs in your code. The only problem I could find is that the variable name graph used twice.
var graph = d3.json("output.json", function(error, graph) {
---------------------
---------------------
});
Edit: Try this code.
var graphRec, node, link;
d3.json("output.json", function(error, graph) {
if (error) throw error;
graph = JSON.parse(JSON.stringify(graph));
force
.nodes(graph.nodes)
.links(graph.links)
.start();
graphRec = graph;
link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function(d) { return color(d.group); })
.call(force.drag);
------------------------
------------------------
});
//adjust threshold
function threshold(thresh) {
graphRec.links.splice(0, graphRec.links.length);
for (var i = 0; i < graphRec.links.length; i++) {
if (graphRec.links[i].value > thresh) {graphRec.links.push(graphRec.links[i]);}
}
restart();
}
//Restart the visualisation after any node and link changes
function restart() {
link = link.data(graphRec.links);
link.exit().remove();
link.enter().insert("line", ".node").attr("class", "link");
node = node.data(graphRec.nodes);
node.enter().insert("circle", ".cursor").attr("class", "node").attr("r", 5).call(force.drag);
force.start();
}
Working Code Snippet.
var width = 960,
height = 500;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var graph = {"nodes":[{"name":"DOU,_H.","group":1},{"name":"QUONIAM","group":1},{"name":"DA_SILV","group":1},{"name":"GUIMARA","group":1},{"name":"SOARES_","group":0}],"links":[{"source":1,"target":0,"value":19,"oriented":false,"date":null},{"source":1,"target":2,"value":2,"oriented":false,"date":null},{"source":1,"target":3,"value":1,"oriented":false,"date":null},{"source":1,"target":4,"value":1,"oriented":false,"date":null},{"source":1,"target":3,"value":2,"oriented":false,"date":null}]};
graph = JSON.parse(JSON.stringify(graph));
force
.nodes(graph.nodes)
.links(graph.links)
.start();
graphRec = graph;
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) {
return Math.sqrt(d.value);
});
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function(d) {
return color(d.group);
})
.call(force.drag);
node.append("title")
.text(function(d) {
return d.name;
});
force.on("tick", function() {
link.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
node.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
});
//adjust threshold
function threshold(thresh) {
graph.links.splice(0, graph.links.length);
for (var i = 0; i < graphRec.links.length; i++) {
if (graphRec.links[i].value > thresh) {
graph.links.push(graphRec.links[i]);
}
}
restart();
}
//Restart the visualisation after any node and link changes
function restart() {
link = link.data(graph.links);
link.exit().remove();
link.enter().insert("line", ".node").attr("class", "link");
node = node.data(graph.nodes);
node.enter().insert("circle", ".cursor").attr("class", "node").attr("r", 5).call(force.drag);
force.start();
}
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
h3 {
color: #1ABC9C;
text-align:center;
font-style: italic;
font-size: 14px;
font-family: "Helvetica";
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<form>
<h3> Link threshold 0 <input type="range" id="thersholdSlider" name="points" value = 0 min="0" max="10" onchange="threshold(this.value)"> 10 </h3>
</form>

Dynamically size points on D3 TopoJSON map

I have plotted points on a TopoJSON map using D3, with the following code:
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<script src="js/d3.js"></script>
<script src="http://d3js.org/topojson.v0.min.js"></script>
<style>
path {
stroke: white;
stroke-width: 0.25px;
fill: grey;
}
</style>
</head>
<body>
<script>
var width = 960,
height = 500;
var projection = d3.geo.mercator()
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var path = d3.geo.path()
.projection(projection);
var g = svg.append("g");
// load and display the World
d3.json("data/world-110m2.json", function(error, topology) {
// load and display the cities
d3.json("data/commodities3.json", function(error, data) {
g.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return projection([d.location_lon, d.location_lat])[0];})
.attr("cy", function(d) {
return projection([d.location_lon, d.location_lat])[1];})
.attr("r", 4)
.style("fill", "green");
});
//plot the path
g.selectAll("path")
.data(topojson.object(topology, topology.objects.countries)
.geometries)
.enter()
.append("path")
.attr("d", path)
});
// zoom and pan
var zoom = d3.behavior.zoom()
.on("zoom",function() {
g.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
g.selectAll("circle")
.attr("d", path.projection(projection));
g.selectAll("path")
.attr("d", path.projection(projection));
});
svg.call(zoom)
</script>
</body>
</html>
I am now looking to size these points based on the value of d.commodity_text. Therefore if the commodity_text were to be equal to "Iron" for instance, the circle would be bigger? Thanks.
You simply need to replace the static value for the radius with a function:
.attr("r", function(d) {
if(d.commodity_text == "Iron") {
return 6;
} else {
return 4;
}
});

Categories