Related
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>
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>
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>
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>
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;
}
});