Drawing a map with census tracks and population in d3 - javascript

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>

Related

How do I properly sort the data for my d3 bubble map so that smaller bubbles show up on top of larger bubbles?

I'm making a bubble map similar to this one: https://observablehq.com/#d3/bubble-map
Everything is working except that my smaller bubbles are not always showing on top of the larger ones. I can't see why, as I've sorted the data before drawing the circles. Can anyone see what I'm doing wrong?
Here is a plunker:
https://plnkr.co/edit/JKWeQKkhN2TQwvNZ?open=lib%2Fscript.js
Code is below. The other files are too large for stack overflow but can be accessed via the Plunker.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.states {
fill: #d3d3d3;
stroke: #ffffff;
stroke-linejoin: round;
}
div.tooltip {
position: absolute;
left: 75px;
text-align: center;
height: 12px;
padding: 8px;
font-size: 13px;
font-family: 'Proxima-Nova', sans-serif;
background: #FFFFFF;
border: 1px solid #989898;
pointer-events: none;
}
.block {
width: 18%;
height: 15px;
display: inline-block;
}
</style>
<body>
<div class="g-chart"></div>
</body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/geo-albers-usa-territories#0.1.0/dist/geo-albers-usa-territories.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/queue-async/1.0.7/queue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.19/topojson.min.js"></script>
<script>
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var margin = { top: 10, left: 10, bottom: 10, right: 10 },
width = window.outerWidth,
width = width - margin.left - margin.right,
mapRatio = .5,
height = width * mapRatio;
const epsilon = 1e-6;
var projection = geoAlbersUsaTerritories.geoAlbersUsaTerritories()
.scale(width)
.translate([width / 2, height / 2]);
var path = d3.geoPath()
.projection(projection);
var map = d3.select(".g-chart").append("svg")
.style('height', height + 'px')
.style('width', width + 'px')
.call(d3.zoom().on("zoom", function () {
map.attr("transform", d3.event.transform)
d3.selectAll()
}))
.append("g");
queue()
.defer(d3.json, "us.json")
.defer(d3.csv, "test.csv")
.await(ready);
d3.selection.prototype.moveToFront = function () {
return this.each(function () {
this.parentNode.appendChild(this);
});
};
d3.selection.prototype.moveToBack = function () {
return this.each(function () {
var firstChild = this.parentNode.firstChild;
if (firstChild) {
this.parentNode.insertBefore(this, firstChild);
}
});
};
function ready(error, us, data) {
if (error) throw error;
data.forEach(function (d) {
d.amount = +d.amount;
})
map.append("g")
.attr("class", "states")
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path);
// sort by descending size so that the smaller circles are drawn on top - not working
map.append('g')
.attr('class', 'facility')
.selectAll("circle")
.data(data.sort((a, b) => +b.amount - +a.amount))
.enter()
.append("circle")
.attr("cx", function (d) {
return projection([d.longitude, d.latitude])[0];
})
.attr("cy", function (d) {
return projection([d.longitude, d.latitude])[1];
})
.attr('r', function (d) {
if (d.amount <= 25) { return 3 }
else if (d.amount > 25 && d.amount <= 50) { return 5 }
else if (d.amount > 50 && d.amount <= 75) { return 7 }
else { return 9 }
})
.style("fill", "#EF4136")
.style("stroke", "#BE2C2D")
.style("stroke-width", 1)
.style("opacity", 0.5)
.on("mouseover", function (d) {
var sel = d3.select(this);
sel.moveToFront();
d3.select(this).transition().duration(0)
.style("opacity", 0.8)
.style("stroke", "#FFFFFF")
div.transition().duration(0)
.style("opacity", .85)
div.text(d.amount)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 30) + "px");
})
.on("mouseout", function () {
var sel = d3.select(this);
d3.select(this)
.transition().duration(0)
.style("opacity", 0.5)
.style("stroke", "#BE2C2D")
div.transition().duration(0)
.style("opacity", 0);
});
//RESPONSIVENESS
d3.select(window).on('resize', resize);
function resize() {
var w = d3.select(".g-chart").node().clientWidth;
width = w - margin.left - margin.right;
height = width * mapRatio;
var newProjection = d3.geoAlbersUsa()
.scale(width)
.translate([width / 2, height / 2]);
path = d3.geoPath()
.projection(newProjection);
map.selectAll("circle")
.attr("cx", function (d) {
return newProjection([d.longitude, d.latitude])[0];
})
.attr("cy", function (d) {
return newProjection([d.longitude, d.latitude])[1];
})
.attr("r", 5)
map
.style('width', width + 'px')
.style('height', height + 'px');
map.selectAll("path").attr('d', path);
}
}
</script>
I would suggest you to split your data to a couple separate datasets grouped by size and create distinct group (g element) for each one. This will also fix issues with circles highlighting.
I slightly updated your plunker to make it work as described (check the lines 91-167) https://plnkr.co/edit/rayo5IZQrBqfqBWR?open=lib%2Fscript.js&preview
Also check the raise and lower methods. They might be a good replacement for your moveToFront and moveToBack methods.
https://riptutorial.com/d3-js/example/18029/svg--the-drawing-order

How do I get a tooltip on hovering over an SVG circle

I'm trying to get a tooltip while hovering over my svg nodes on d3. I have tried different ways to code it but it doesn't seem to be working. I'm also a little confused since I have already used the mouseover function for highlighting the circles. I've added a snippet from my code here
var tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.style("background", "white")
.text("a simple tooltip");
var svg = d3.select('body').append('svg')
.attr('height',h)
.attr('width',w)
var circles = svg.selectAll('circle')
.data(data.nodes)
.enter()
.append('circle')
.attr('cx', function (d) { return d.x })
.attr('cy', function (d) { return d.y })
.attr('r', function (d) { return d.amount/50 })
.attr('fill', function (d) { return "blue" })
.attr('stroke','yellow')
.attr('stroke-width',0)
.on('mouseover',function() {
d3.select(this)
.transition()
.duration(1000)
.attr('stroke-width',5)
.attr('opacity',1)
svg.selectAll('circle')
.attr('opacity',0);
svg.selectAll('line')
.attr('opacity',0)
})
.on('mouseout',function () {
d3.select(this)
.transition()
.duration(1000)
.attr('stroke-width',0)
svg.selectAll("circle") .attr("opacity", 1)
svg.selectAll('line')
.attr('opacity',1)
})
d3.select("body")
.selectAll('div')
.on("mouseover", function(d){tooltip.text(d); return tooltip.style("visibility", "visible");})
.on("mouseout", function(){return tooltip.style("visibility", "hidden");})
Here's one I made earlier ;) The tooltip style in this instance is defined in css (saves defining it in javascript!) Here's a fiddle (The graph shows a rectangle with multiple circles, each one showing a tooltip on hover)
var width = 1000;
var height = 500;
var range = 100;
var cntPoints = 50;
var padding = {
x: 50,
y: 20
};
var points = d3.range(cntPoints).map(function() {
return [Math.random() * range, Math.random() * range, Math.random() * 5];
})
var scaleX = d3.scale.linear().domain([0, range]).range([0, width - 20]);
var scaleY = d3.scale.linear().domain([0, range]).range([height - padding.y, 0]);
var scaleColor = d3.scale.linear().domain([0, 5]).range(["red", "green"]);
var axisX = d3.svg.axis();
axisX.scale(scaleX).orient('bottom');
var axisY = d3.svg.axis()
.scale(scaleY)
.orient('left')
.ticks(3);
var drag = d3.behavior.drag()
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height)
.style('padding-top', padding.y)
.style('padding-left', padding.x);
svg.append("clipPath")
.attr("id", "chart-area")
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height - padding.y);
rect = svg
.append('rect')
.attr('width', width)
.attr('height', height)
.attr('class', 'invisible')
.on('click', function() {
svg.selectAll('circle[fill="blue"]')
.transition()
.delay(function(d, i) {
return i * 300;
})
.duration(1000)
.attr('cx', function(d) {
return scaleX(d[0]);
})
.attr('cy', function(d) {
return scaleY(d[1]);
})
.attr('fill', function(d) {
return scaleColor(d[2]);
})
});
svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(0, ' + (height - padding.y) + ')')
.call(axisX);
svg.append('g')
.attr('class', 'axis')
.call(axisY);
circles = svg
.append('g')
.attr('id', 'main-container')
.attr('clip-path', 'url(#chart-area)')
.selectAll('circle')
.data(points)
.enter()
.append('circle')
.attr('r', 14)
.attr('cx', function(d) {
return scaleX(d[0]);
})
.attr('cy', function(d) {
return scaleY(d[1]);
})
.attr('fill', function(d) {
return scaleColor(d[2]);
})
.attr("fill-opacity", 0.5)
.attr("stroke-opacity", 0.8)
.call(drag);
circles
.on('mouseover', function(d) {
coordinates = d3.mouse(this);
d3.select("#tooltip")
.style("left", coordinates[0] + padding.x + "px")
.style("top", coordinates[1] + padding.y + "px")
.select("#info")
.text(tooltipText(d));
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function() {
d3.select("#tooltip").classed("hidden", true);
})
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", 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("dragging", false).attr('fill', 'blue');
}
function tooltipText(d) {
return 'Color base: ' + Math.round(d[2] * 100) / 100 + ', Position: ' + Math.round(d[0] * 100) / 100 + ';' + Math.round(d[1] * 100) / 100;
}
#tooltip {
position: absolute;
width: 200px;
height: auto;
padding: 10px;
background-color: rgba(128, 128, 128, 0.4);
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
-webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
-moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
pointer-events: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.8/d3.min.js"></script>
<div id="tooltip" class="hidden">
<p><strong>Circle information</strong></p>
<p><span id="info"></span></p>
</div>
Hope this helps

D3.js - plotting images with nested array in json

I am a newbie in d3, the following is my input json file,
{
"y_axis_list":["A","B","C","D","E","F","G"],
"Points":[
{
"y": "A",
"x": 10,
"Persona":["link1"]
},
{
"y" : "B",
"x": 20,
"Persona":["link2","link1"]
},
{
"y" : "C",
"x": 30,
"Persona":["link2","link3"]
},
{
"y" : "D",
"x": 40,
"Persona":["link2","link3"]
},
{
"y" : "E",
"x" : 50,
"Persona":["link5","link6"]
},
{
"y" : "F",
"x" : 60,
"Persona":["link7","link8"]
},
{
"y" : "G",
"x" : 70,
"Persona":["link9","link10"]
}
]
}
the following is my code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>D3 v4 - linechart</title>
<style>
#graph {
width: 900px;
height: 500px;
}
.tick line {
stroke-dasharray: 2 2 ;
stroke: #ccc;
}
.x{
display: none;
}
.y path{
fill: none;
stroke: none;
/* set the CSS */
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 2px;
}
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;
}
</style>
</head>
<body>
<div id="graph"></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
!(function(){
"use strict"
var width,height
var chartWidth, chartHeight
var margin
var svg = d3.select("#graph").append("svg")
var axisLayer = svg.append("g").classed("axisLayer", true)
var chartLayer = svg.append("g").classed("chartLayer", true)
var xScale = d3.scaleLinear()
var yScale = d3.scalePoint()
var align = 0
var i=10;
//d3.tsv("data1.tsv", cast, main)
d3.json("http://localhost/newest.json",cast)
//データの方変換
function cast(datafull) {
console.log("got it");
var data=datafull["Points"];enter code here
data.forEach(function(data) {
console.log(data.y);
data.y = data.y;
data.x = +data.x;
});
main(data,datafull);
}
function main(data,datafull) {
console.log("in main");
setSize(data,datafull)
drawAxis()
drawChart(data,datafull)
}
function setSize(data,datafull) {
width = document.querySelector("#graph").clientWidth
height = document.querySelector("#graph").clientHeight
margin = {top:40, left:100, bottom:40, right:0 }
chartWidth = width - (margin.left+margin.right+8)
chartHeight = height - (margin.top+margin.bottom)
svg.attr("width", width).attr("height", height)
axisLayer.attr("width", width).attr("height", height)
chartLayer
.attr("width", chartWidth)
.attr("height", chartHeight)
.attr("transform", "translate("+[margin.left, margin.top]+")")
xScale.domain([0, d3.max(data, function(d) { return d.x; })]).range([0,chartWidth])
yScale.domain(datafull["y_axis_list"]).range([chartHeight, 0]).align(1)
}
function drawChart(data,datafull) {
console.log("in drawChart");
var t = d3.transition()
.duration(4000)
.ease(d3.easeLinear)
.on("start", function(d){ console.log("transiton start") })
.on("end", function(d){ console.log("transiton end") });
// var tooltip = d3.tip().attr('class', 'd3-tip').html(function(d) { return 10; });
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var lineGen = d3.line()
.x(function(d) { i=i+50;console.log(i); return xScale(d.x); })
.y(function(d) { return yScale(d.y) })
.curve(d3.curveStepAfter)
var line = chartLayer.selectAll(".line")
.data([data])
line.enter().append("path").classed("line", true)
.merge(line)
.attr("d", lineGen)
.attr("fill", "none")
.attr("stroke", "blue")
.attr("stroke-width","2px")
.attr("stroke-dasharray", function(d){ return this.getTotalLength() })
.attr("stroke-dashoffset", function(d){ return this.getTotalLength() })
chartLayer.selectAll(".line").transition(t)
.attr("stroke-dashoffset", 0)
var img = chartLayer.selectAll('image')
.data(data)
.enter()
.append('image')
.attr("class","logo")
.attr("xlink:href", "http://localhost/whatsapp-logo.jpg" )
.attr("x", function(d,i){ return xScale(d.x+5/2) })
.attr("y", function(d,i){ return yScale(d.y) })
.attr("width", 16)
.attr("height", 16)
.style("opacity",0);
img.transition().delay(4000).duration(500).ease(d3.easeLinear).style("opacity",1);
img.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div.html("Persona people" + "<br/>" + d.x)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
chartLayer.selectAll("circle").classed("circle",true)
.data(data)
.enter().append("circle")
.attr("class", "circle")
.attr("fill","none")
.attr("stroke","black")
.attr("cx", function(d) { return xScale(d.x); })
.attr("cy", function(d) { return yScale(d.y); })
.attr("r", 4)
}
function drawAxis(){
var yAxis = d3.axisLeft(yScale)
.tickSizeInner(-chartWidth)
axisLayer.append("g")
.attr("transform", "translate("+[margin.left, margin.top]+")")
.attr("class", "axis y")
.call(yAxis);
var xAxis = d3.axisBottom(xScale)
axisLayer.append("g")
.attr("class", "axis x")
.attr("transform", "translate("+[margin.left, chartHeight+margin.top]+")")
.call(xAxis);
}
function update(){
}
}());
</script>
</body>
</html>
I want the images given in the persona list to appear in the same line as that of the y coordinate in the json with x-axis changing minutely. The links are the links to images which are to be loaded.
I went through a lot of questions, but I was not able to figure out the method to do that.
My current output is given below.
Now there is one image in each line, I want the number of images in persona list (input json) to appear in each of the corresponding lines.
Rewrite your img variable this way (pay attention to the comments):
var img = chartLayer
.selectAll('g')
.data(data)
.enter()
.append('g') // append g element, here we will be append image as child
.attr("transform", function(d, i) {
var x = xScale(d.x + 5 / 2);
var y = yScale(d.y);
return 'translate(' + x + ', ' + y + ')'
})
.selectAll('image')
.data(function(d) { // here we format data as we need
return d.Persona.map(function(icon, index) {
return {
icon: icon,
tooltipText: d.Text[index] // set tooltip text
}
})
})
.enter()
.append('image') // append image element
.attr("class", "logo")
.attr("xlink:href", function(d) { // set appropriate href attribute
return d.icon;
})
.attr("x", function(d, i) { // move image by index
return 20 * i;
})
.attr("width", 16)
.attr("height", 16)
.style("opacity", 0);
...
img.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div.html("Persona people" + "<br/>" + d.tooltipText) // get tooltip text
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
...
Look at the working example - https://jsfiddle.net/levsha/og92k0gv/

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>

Combining D3 Visualization

I am trying to combine two D3 Visualizations. I found a question before, but it did not really have a solution.
When I combine the two files the visualizations overlap and produce this:
the streamgraph component:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.chart {
background: #fff;
}
p {
font: 12px helvetica;
}
.axis path, .axis line {
fill: none;
stroke: #000;
stroke-width: 2px;
shape-rendering: crispEdges;
}
button {
position: absolute;
right: 50px;
top: 10px;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.js"></script>
<div class="chart">
</div>
<script>
chart("Data.csv", "blue");
var datearray = [];
var colorrange = [];
function chart(csvpath, color) {
if (color == "blue") {
colorrange = ["#045A8D", "#2B8CBE", "#74A9CF", "#A6BDDB", "#D0D1E6", "#F1EEF6"];
}
else if (color == "pink") {
colorrange = ["#980043", "#DD1C77", "#DF65B0", "#C994C7", "#D4B9DA", "#F1EEF6"];
}
else if (color == "orange") {
colorrange = ["#B30000", "#E34A33", "#FC8D59", "#FDBB84", "#FDD49E", "#FEF0D9"];
}
strokecolor = colorrange[0];
var format = d3.time.format("%m/%d/%y");
var margin = {top: 20, right: 40, bottom: 30, left: 50};
var width = document.body.clientWidth - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
var tooltip = d3.select("body")
.append("div")
.attr("class", "remove")
.style("position", "absolute")
.style("z-index", "20")
.style("visibility", "hidden")
.style("top", "30px")
.style("left", "75px");
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height-10, 0]);
var z = d3.scale.ordinal()
.range(colorrange);
var xAxis = d3.svg.axis()
.orient("bottom")
.scale(x)
.ticks(d3.time.years, 10); //tick on every 10 years
/*.scale(x)
.orient("bottom")
.text(date)
//;*/
//. tickFormat(x)
//. tickValues(date)
//was already there but out of view -> changed the left margin
var yAxis = d3.svg.axis()
.scale(y);
var stack = d3.layout.stack()
.offset("silhouette")
.values(function(d) { return d.values; })
.x(function(d) { return d.date; })
.y(function(d) { return d.value; });
var nest = d3.nest()
.key(function(d) { return d.key; });
var area = d3.svg.area()
.interpolate("cardinal")
.x(function(d) { return x(d.date); })
.y0(function(d) { return y(d.y0); })
.y1(function(d) { return y(d.y0 + d.y); });
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 + ")");
/* correct this function
var graph = d3.csv(csvpath, function(data) {
data.forEach(function(d) {
d.date = format.parse(d.date);
d.value = +d.value;
});*/
var graph = d3.csv(csvpath, function(raw) {
var data = [];
raw.forEach(function (d) {
data.push({
key: d.Country,
date : new Date(1980,0,1), //I had a bug in creating the right dates
value : parseInt(d['1980-1989'].replace(',','')) //get rid of the thousand separator
});
data.push({
key: d.Country,
date : new Date(1990,0,1),
value : parseInt(d['1990-1999'].replace(',',''))
});
data.push({
key: d.Country,
date : new Date(2000,0,1),
value : parseInt(d['2000-2009'].replace(',','') )
});
});
var layers = stack(nest.entries(data));
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
svg.selectAll(".layer")
.data(layers)
.enter().append("path")
.attr("class", "layer")
.attr("d", function(d) { return area(d.values); })
.style("fill", function(d, i) { return z(i); });
//adding .text causes axis to dissapear
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
//.text(date)
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width + ", 0)")
//.text(value)
.call(yAxis.orient("right"));
svg.append("g")
.attr("class", "y axis")
.call(yAxis.orient("left"));
var pro;
svg.selectAll(".layer")
.attr("opacity", 1)
.on("mouseover", function(d, i) {
svg.selectAll(".layer").transition()
.duration(250)
.attr("opacity", function(d, j) {
return j != i ? 0.6 : 1;
})})
.on("mousemove", function(d, i) {
var mousex = d3.mouse(this);
mousex = mousex[0];
var invertedx = x.invert(mousex);
//find the largest smaller element
var dd = d.values.filter(function(d) { return d.date <= invertedx; });
dd = dd[dd.length -1]; //use the last element
pro = dd.value;
d3.select(this)
.classed("hover", true)
.attr("stroke", strokecolor)
.attr("stroke-width", "0.5px");
tooltip.html( "<p>" + d.key + "<br>" + pro + "</p>" ).style("visibility", "visible");
})
.on("mouseout", function(d, i) {
svg.selectAll(".layer")
.transition()
.duration(250)
.attr("opacity", "1");
d3.select(this)
.classed("hover", false)
.attr("stroke-width", "0px");
tooltip.html( "<p>" + d.key + "<br>" + pro + "</p>" ).style("visibility", "hidden");
})
var vertical = d3.select(".chart")
.append("div")
.attr("class", "remove")
.style("position", "absolute")
.style("z-index", "19")
.style("width", "1px")
.style("height", "380px")
.style("top", "10px")
.style("bottom", "30px")
.style("left", "0px")
.style("background", "#fff");
d3.select(".chart")
.on("mousemove", function(){
var mousex = d3.mouse(this);
mousex = mousex[0] + 5;
vertical.style("left", mousex + "px" )})
.on("mouseover", function(){
var mousex = d3.mouse(this);
mousex = mousex[0] + 5;
vertical.style("left", mousex + "px")});
});
}
</script>
Map component:
<!DOCTYPE html>
<meta charset="utf-8">
<title>U.S Immigration Data Visualization</title>
<style>
.country:hover{
stroke: #fff;
stroke-width: 1.5px;
}
.text{
font-size:10px;
text-transform:capitalize;
}
#container {
margin: 10px 10%;
border:2px solid #000;
border-radius: 5px;
height:100%;
overflow:hidden;
background: #e1eafe;
}
.hidden {
display: none;
}
div.tooltip {
color: #222;
background: #fff;
padding: .5em;
text-shadow: #f5f5f5 0 1px 0;
border-radius: 2px;
box-shadow: 0px 0px 2px 0px #a6a6a6;
opacity: 0.9;
position: absolute;
}
.graticule {
fill: none;
stroke: #bbb;
stroke-width: .5px;
stroke-opacity: .5;
}
.equator {
stroke: #ccc;
stroke-width: 1px;
}
</style>
</head>
<br>
<h1><center>U.S Immigration Data Visualization</center></h1>
<h2><b>Work in Progress</b></h2>
<h3><b>Ex-USSR countries included in Russia</b></h3>
<h3><b>Ex-Yugoslavia included in Macedonia</b></h3>
<div id="container"></div>
<script src="js/d3.min.js"></script>
<script src="js/topojson.v1.min.js"></script>
<script src="http://d3js.org/d3.geo.tile.v0.min.js"></script>
<script>
d3.select(window).on("resize", throttle);
var zoom = d3.behavior.zoom()
.scaleExtent([1, 9])
.on("zoom", move);
var width = document.getElementById('container').offsetWidth;
var height = width / 2;
var topo,projection,path,svg,g;
var graticule = d3.geo.graticule();
var tooltip = d3.select("#container").append("div").attr("class", "tooltip hidden");
setup(width,height);
function setup(width,height){
projection = d3.geo.mercator()
.translate([(width/2), (height/2)])
.scale( width / 2 / Math.PI);
path = d3.geo.path().projection(projection);
svg = d3.select("#container").append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom)
.on("click", click)
.append("g");
g = svg.append("g");
}
d3.json("data/world-topo-min.json", function(error, world) {
var countries = topojson.feature(world, world.objects.countries).features;
topo = countries;
draw(topo);
});
function draw(topo) {
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
g.append("path")
.datum({type: "LineString", coordinates: [[-180, 0], [-90, 0], [0, 0], [90, 0], [180, 0]]})
.attr("class", "equator")
.attr("d", path);
var country = g.selectAll(".country").data(topo);
country.enter().insert("path")
.attr("class", "country")
.attr("d", path)
.attr("id", function(d,i) { return d.id; })
.attr("title", function(d,i) { return d.properties.name; })
.style("fill", function(d, i) { return d.properties.color; });
//offsets for tooltips
var offsetL = document.getElementById('container').offsetLeft+20;
var offsetT = document.getElementById('container').offsetTop+10;
//tooltips
country
.on("mousemove", function(d,i) {
var mouse = d3.mouse(svg.node()).map( function(d) { return parseInt(d); } );
tooltip.classed("hidden", false)
.attr("style", "left:"+(mouse[0]+offsetL)+"px;top:"+(mouse[1]+offsetT)+"px")
.html(d.properties.name);
})
.on("mouseout", function(d,i) {
tooltip.classed("hidden", true);
});
//EXAMPLE: adding some capitals from external CSV file
d3.csv("Data.csv", function(err, capitals) {
capitals.forEach(function(i){
addpoint(i.CapitalLongitude, i.CapitalLatitude );
});
});
}
function redraw() {
width = document.getElementById('container').offsetWidth;
height = width / 2;
d3.select('svg').remove();
setup(width,height);
draw(topo);
}
function move() {
var t = d3.event.translate;
var s = d3.event.scale;
zscale = s;
var h = height/4;
t[0] = Math.min(
(width/height) * (s - 1),
Math.max( width * (1 - s), t[0] )
);
t[1] = Math.min(
h * (s - 1) + h * s,
Math.max(height * (1 - s) - h * s, t[1])
);
zoom.translate(t);
g.attr("transform", "translate(" + t + ")scale(" + s + ")");
//adjust the country hover stroke width based on zoom level
d3.selectAll(".country").style("stroke-width", 1.5 / s);
}
var throttleTimer;
function throttle() {
window.clearTimeout(throttleTimer);
throttleTimer = window.setTimeout(function() {
redraw();
}, 200);
}
//geo translation on mouse click in map
function click() {
var latlon = projection.invert(d3.mouse(this));
console.log(latlon);
}
//function to add points and text to the map (used in plotting capitals)
function addpoint(lat,lon,text) {
var gpoint = g.append("g").attr("class", "gpoint");
var x = projection([lat,lon])[0];
var y = projection([lat,lon])[1];
gpoint.append("svg:circle")
.attr("cx", x)
.attr("cy", y)
.attr("class","point")
.attr("r", 1);
//conditional in case a point has no associated text
//if(text.length>0){
// gpoint.append("text")
// .attr("x", x+2)
// .attr("y", y+2)
// .attr("class","text")
// .text(text);
//}
}
</script>
</body>
</html>
This is hard to answer without the code you actually use when you 'combine' the two SVG elements, and without the data or a working example.
What I've done is take the 2 basic components, the streamgraph (and svg node inside <div class="chart">) and the map (a separate svg node in <div id="container"></div>), and create working code that combines the 2:
http://plnkr.co/edit/WjlObRIasLYXOuEL4HDE?p=preview
This is the basic code:
<body>
<div class="chart">
</div>
<div id="container">
</div>
<script type="text/javascript">
var width = 300;
var height = width / 2;
// Equivalent of streamgraph code...
var svg_stream = d3.select(".chart")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("rect")
... // rect attributes
// Equivalent of map code...
var svg_map = d3.select("#container")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("circle")
... // circle attributes
</script>
</body>
What's inside each svg shouldn't make a difference to the positioning, so I've just used a rect to represent the streamgraph and a circle for the map. I've taken as much of the CSS and code from your snippets as it makes sense to. If you combine the two components as in the example above, you should not see any overlap. I'm not really able to correct your version as I don't know how you did it.
Note - You should also avoid defining duplicate variable names (like var svg for both SVGs) when combining the components

Categories