Related
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
I put together the following treemap using d3.js. It's the top 20 states in terms of voter turnout. Link
I'm unsure how to add the 'values' into the hover. Ideally, it should show the unique value for each state. I'm able to do it for one (California in the example), but I'm not sure how to make it flexible so that it generates other values for different states.
let margin = {
top: 0,
right: 0,
bottom: 0,
left: 0
},
width = 1100 - margin.left - margin.right,
height = 900 - margin.top - margin.bottom;
// append the svg object to the body of the page
let svg = d3
.select('#treemap')
.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 + ')');
// Read data
d3.csv('https://raw.githubusercontent.com/oihamza/Interactive-Data-Vis-Fall2020/master/Project%201/stateVoterTurnout.csv', function(data) {
// stratify the data: reformatting for d3.js
var root = d3
.stratify()
.id(function(d) {
return d.name;
}) // Name of the entity (column name is name in csv)
.parentId(function(d) {
return d.parent;
})(
// Name of the parent (column name is parent in csv)
data
);
// data is an object
console.log(data);
let values = data[1].value;
let tooltip = d3
.select('body')
.append('div')
.style('position', 'absolute')
.style('z-index', '10')
.style('visibility', 'hidden')
.style('background-color', 'white')
.style('border', 'solid')
.style('border-width', '2px')
.style('border-radius', '5px')
.style('padding', '5px')
.text(`${values} voters`);
root.sum(function(d) {
return +d.value;
}); // Compute the numeric value for each entity
// Then d3.treemap computes the position of each element of the hierarchy
// The coordinates are added to the root object above
d3.treemap().size([width, height]).padding(3)(root);
console.log(root.leaves());
// use this information to add rectangles:
svg
.selectAll('rect')
.data(root.leaves())
.enter()
.append('rect')
.attr('x', function(d) {
return d.x0;
})
.attr('y', function(d) {
return d.y0;
})
.attr('width', function(d) {
return d.x1 - d.x0;
})
.attr('height', function(d) {
return d.y1 - d.y0;
})
.style('stroke', 'black')
.style('fill', '#945f04')
.on('mouseover', function() {
return tooltip.style('visibility', 'visible');
})
.on('mousemove', function() {
return tooltip
.style('top', d3.event.pageY - 10 + 'px')
.style('left', d3.event.pageX + 10 + 'px');
})
.on('mouseout', function() {
return tooltip.style('visibility', 'hidden');
});
// and to add the text labels
svg
.selectAll('text')
.data(root.leaves())
.enter()
.append('text')
.attr('x', function(d) {
return d.x0 + 10;
}) // +10 to adjust position (more right)
.attr('y', function(d) {
return d.y0 + 20;
}) // +20 to adjust position (lower)
.text(function(d) {
return d.data.name;
})
.attr('font-size', '15px')
.attr('fill', 'white');
});
body,
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: "Montserrat", sans-serif
}
.styling {
transform-origin: center center;
align-content: center;
position: relative;
}
.tooltip {
position: relative;
display: inline-block;
/* border-bottom: 1px dotted black; */
}
.tooltip .tooltiptext {
visibility: hidden;
width: 180px;
background-color: black;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px 0;
/* Position the tooltip */
position: absolute;
z-index: 1;
}
.tooltip:hover .tooltiptext {
visibility: visible;
}
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Montserrat">
<span class="styling"><div id="treemap"></div></span>
<!-- Loading this version of d3 -->
<script src="https://d3js.org/d3.v4.js"></script>
When you mouseover or mousemove the rectangle, you can find it's assigned datum with the first argument (often called d) - just like you do when you set attributes with .attr() or styles with .style().
You can set the .text() of the tooltip dynamically, using this d:
let margin = {
top: 0,
right: 0,
bottom: 0,
left: 0
},
width = 1100 - margin.left - margin.right,
height = 900 - margin.top - margin.bottom;
// append the svg object to the body of the page
let svg = d3
.select('#treemap')
.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 + ')');
// Read data
d3.csv('https://raw.githubusercontent.com/oihamza/Interactive-Data-Vis-Fall2020/master/Project%201/stateVoterTurnout.csv', function(data) {
// stratify the data: reformatting for d3.js
var root = d3
.stratify()
.id(function(d) {
return d.name;
}) // Name of the entity (column name is name in csv)
.parentId(function(d) {
return d.parent;
})(
// Name of the parent (column name is parent in csv)
data
);
let tooltip = d3
.select('body')
.append('div')
.style('position', 'absolute')
.style('z-index', '10')
.style('visibility', 'hidden')
.style('background-color', 'white')
.style('border', 'solid')
.style('border-width', '2px')
.style('border-radius', '5px')
.style('padding', '5px');
root.sum(function(d) {
return +d.value;
}); // Compute the numeric value for each entity
// Then d3.treemap computes the position of each element of the hierarchy
// The coordinates are added to the root object above
d3.treemap().size([width, height]).padding(3)(root);
// use this information to add rectangles:
svg
.selectAll('rect')
.data(root.leaves())
.enter()
.append('rect')
.attr('x', function(d) {
return d.x0;
})
.attr('y', function(d) {
return d.y0;
})
.attr('width', function(d) {
return d.x1 - d.x0;
})
.attr('height', function(d) {
return d.y1 - d.y0;
})
.style('stroke', 'black')
.style('fill', '#945f04')
.on('mouseover', function() {
tooltip.style('visibility', 'visible');
})
.on('mousemove', function(d) {
tooltip
.style('top', d3.event.pageY - 10 + 'px')
.style('left', d3.event.pageX + 10 + 'px')
.text(`${d.data.value} voters`);
})
.on('mouseout', function() {
tooltip.style('visibility', 'hidden');
});
// and to add the text labels
svg
.selectAll('text')
.data(root.leaves())
.enter()
.append('text')
.attr('x', function(d) {
return d.x0 + 10;
}) // +10 to adjust position (more right)
.attr('y', function(d) {
return d.y0 + 20;
}) // +20 to adjust position (lower)
.text(function(d) {
return d.data.name;
})
.attr('font-size', '15px')
.attr('fill', 'white');
});
body,
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: "Montserrat", sans-serif
}
.styling {
transform-origin: center center;
align-content: center;
position: relative;
}
.tooltip {
position: relative;
display: inline-block;
/* border-bottom: 1px dotted black; */
}
.tooltip .tooltiptext {
visibility: hidden;
width: 180px;
background-color: black;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px 0;
/* Position the tooltip */
position: absolute;
z-index: 1;
}
.tooltip:hover .tooltiptext {
visibility: visible;
}
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Montserrat">
<span class="styling"><div id="treemap"></div></span>
<!-- Loading this version of d3 -->
<script src="https://d3js.org/d3.v4.js"></script>
It's not necessary to return anything inside these .on() functions.
I'm trying to make a choropleth map in d3 js.
In my code I'm using gejson to draw french departments (counties) and then I want to color them using data from a csv file.
First I populate every counties with their official ID which is a number of 5 digits (ex : 75001).
Then I want to color them using a colorScale. To do so, I do a for each loop where I select counties using their ID (in the csv file this time) and I use the color scale and the data form the csv to get the color of the countie on the map.
I think that the problem problem is d3.select("#d" + e.insee) doesn't work.
I have taken the problem in every way possible and I really can"t figure out what is wrong in the code.
Here is my entire code. The data are loaded from a github so every one can execute it.
I apologized for the long code.
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr">
<head>
<meta charset="utf-8">
<title>All in One</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
<style type="text/css">
#info {
margin-top: 50px;
}
#deptinfo {
margin-top: 30px;
}
.department {
cursor: pointer;
stroke: black;
stroke-width: .5px;
}
.department:hover {
stroke-width: 2px;
}
div.tooltip {
position: absolute;
opacity:0.8;
z-index:1000;
text-align:left;
border-radius:4px;
-moz-border-radius:4px;
-webkit-border-radius:4px;
padding:8px;
color:#fff;
background-color:#000;
font: 12px sans-serif;
max-width: 300px;
height: 40px;
}
#svg {
display: block;
margin: auto;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>
<script type="text/javascript">
const width = 850,
const height = 800,
colors = ['#d4eac7', '#c6e3b5', '#b7dda2', '#a9d68f', '#9bcf7d', '#8cc86a', '#7ec157', '#77be4e', '#70ba45', '#65a83e', '#599537', '#4e8230', '#437029', '#385d22', '#2d4a1c', '#223815'];
const path = d3.geoPath();
const projection = d3.geoMercator()
.center([2.332978, 48.860117])
.scale(40000)
.translate([width / 2, height / 2]);
path.projection(projection);
const svg = d3.select('#map').append("svg")
.attr("id", "svg")
.attr("width", width)
.attr("height", height)
.attr("class", "Blues");
// Append the group that will contain our paths
const deps = svg.append("g");
var promises = [];
promises.push(d3.json('https://raw.githubusercontent.com/cerezamo/dataviz/master/Graphique_bokeh/pop_comgeo.geojson'))
promises.push(d3.csv("https://raw.githubusercontent.com/cerezamo/dataviz/master/variables.csv"))
Promise.all(promises).then(function(values){
const geojson = values[0];
const csv = values[1];
var features = deps
.selectAll("path")
.data(geojson.features)
.enter()
.append("path")
.attr('id', function(d) {return "d" + d.properties.insee;})// Creation of the id as (ex :"d75005")
// I add a d so the id is not a pure number as it could create error when selecting it
.attr("d", path);
var quantile = d3.scaleQuantile()
.domain([0, d3.max(csv, function(e) { return + e.densitehabkm2; })])
.range(colors);
var legend = svg.append('g')
.attr('transform', 'translate(725, 150)')
.attr('id', 'legend');
legend.selectAll()
.data(d3.range(colors.length))
.enter().append('svg:rect')
.attr('height', '20px')
.attr('width', '20px')
.attr('x', 5)
.attr('y', function(d) { return d * 20; })
.style("fill", function(d) { return colors[d]; });
var legendScale = d3.scaleLinear()
.domain([0, d3.max(csv, function(e) { return +e.densitehabkm2; })])
.range([0, colors.length * 20]);
var legendAxis = svg.append("g")
.attr('transform', 'translate(750, 150)')
.call(d3.axisRight(legendScale).ticks(3));
csv.forEach(function(e,i) {
d3.select(("#d" + e.insee)) // Line where I think the problem is
// Here I'm trying to select Id's using the same code but reading it in the csv file. I have check and id's in geojson and csv do correspond
.style("fill", function(d) { return quantile(+e.densitehabkm2); })
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div.html("IZI")
.style("left", (d3.event.pageX + 30) + "px")
.style("top", (d3.event.pageY - 30) + "px");
})
.on("mouseout", function(d) {
div.style("opacity", 0);
div.html("")
.style("left", "-500px")
.style("top", "-500px");
});
});
//console.log(csv.insee);
});
// Append a DIV for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
</script>
Thank you very much for your time.
SVG <path> elements have, indeed, a black fill by default. However, that's not a problem here: your style("fill", ...) should work, regardless.
The problem here is that you have several paths with the same ID. If you have a look at the original GeoJson, you'll see that you have several insee properties, for different years. So, your code is painting several black paths, one on top of the other. When you select by ID you select only one of them (by the way, it goes without saying that IDs should be unique in the document), and the other black paths avoid you seeing the painted path. Also, your solution simply makes all paths transparent, so when you paint any one of them it will be visible, but all other transparent paths are still there, over the path you selected.
All that being said, the simplest solution is filtering the original data, for instance:
geojson.features = geojson.features.filter(function(d) {
return d.properties.year === 1962;
});
Basically, by selecting just one year, you avoid all those paths stacked one on top of the other (and also the browser will render the page way faster).
With that change alone, your style method will work. Here is the running demo:
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr">
<head>
<meta charset="utf-8">
<title>All in One</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
<style type="text/css">
#info {
margin-top: 50px;
}
#deptinfo {
margin-top: 30px;
}
.department {
cursor: pointer;
stroke: black;
stroke-width: .5px;
}
.department:hover {
stroke-width: 2px;
}
div.tooltip {
position: absolute;
opacity: 0.8;
z-index: 1000;
text-align: left;
border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
padding: 8px;
color: #fff;
background-color: #000;
font: 12px sans-serif;
max-width: 300px;
height: 40px;
}
#svg {
display: block;
margin: auto;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>
<script type="text/javascript">
const width = 850,
height = 800,
colors = ['#d4eac7', '#c6e3b5', '#b7dda2', '#a9d68f', '#9bcf7d', '#8cc86a', '#7ec157', '#77be4e', '#70ba45', '#65a83e', '#599537', '#4e8230', '#437029', '#385d22', '#2d4a1c', '#223815'];
const path = d3.geoPath();
const projection = d3.geoMercator()
.center([2.332978, 48.860117])
.scale(40000)
.translate([width / 2, height / 2]);
path.projection(projection);
const svg = d3.select('#map').append("svg")
.attr("id", "svg")
.attr("width", width)
.attr("height", height)
.attr("class", "Blues");
// Append the group that will contain our paths
const deps = svg.append("g");
var promises = [];
promises.push(d3.json('https://raw.githubusercontent.com/cerezamo/dataviz/master/Graphique_bokeh/pop_comgeo.geojson'))
promises.push(d3.csv("https://raw.githubusercontent.com/cerezamo/dataviz/master/variables.csv"))
Promise.all(promises).then(function(values) {
const geojson = values[0];
const csv = values[1];
geojson.features = geojson.features.filter(function(d) {
return d.properties.year === 1962;
})
var features = deps
.selectAll("path")
.data(geojson.features)
.enter()
.append("path")
.attr('id', function(d) {
return "d" + d.properties.insee;
}) // Creation of the id as (ex :"d75005")
// I add a d so the id is not a pure number as it could create error when selecting it
.attr("d", path);
var quantile = d3.scaleQuantile()
.domain([0, d3.max(csv, function(e) {
return +e.densitehabkm2;
})])
.range(colors);
var legend = svg.append('g')
.attr('transform', 'translate(725, 150)')
.attr('id', 'legend');
legend.selectAll()
.data(d3.range(colors.length))
.enter().append('svg:rect')
.attr('height', '20px')
.attr('width', '20px')
.attr('x', 5)
.attr('y', function(d) {
return d * 20;
})
.style("fill", function(d) {
return colors[d];
});
var legendScale = d3.scaleLinear()
.domain([0, d3.max(csv, function(e) {
return +e.densitehabkm2;
})])
.range([0, colors.length * 20]);
var legendAxis = svg.append("g")
.attr('transform', 'translate(750, 150)')
.call(d3.axisRight(legendScale).ticks(3));
csv.forEach(function(e, i) {
d3.select(("path#d" + e.insee)) // Line where I think the problem is
// Here I'm trying to select Id's using the same code but reading it in the csv file. I have check and id's in geojson and csv do correspond
.style("fill", function() {
return quantile(+e.densitehabkm2);
})
.on("mouseover", function(d) {
console.log(d);
div.transition()
.duration(200)
.style("opacity", .9);
div.html("IZI")
.style("left", (d3.event.pageX + 30) + "px")
.style("top", (d3.event.pageY - 30) + "px");
})
.on("mouseout", function(d) {
div.style("opacity", 0);
div.html("")
.style("left", "-500px")
.style("top", "-500px");
});
});
//console.log(csv.insee);
});
// Append a DIV for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
</script>
Well the problem was in fact in the css.
Thanks to Ryan Morton for the answer.
Add .attr('fill', 'none') to when you first create the map objects.
It's autofilling with black and somehow preventing your colors later:
https://jsfiddle.net/bz3o5yah/
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
I'm trying to add a tooltip to a rect. It does pop up on mouse pointer hover over a bar but it doesn't want to disappear on mouseout event. I've also tried to use div.style("display", "none"), but it doesn't work either. For some reason it doesn't want to trigger mouseover event again after mouseout. It just keep displaying a tooltip.
http://bl.ocks.org/edkiljak/dc85bf51a27122380c68909cdd09d388
div.tooltip {
position: absolute;
text-align: left;
padding: 4px;
font-family: Lato, arial, sans-serif;
font-size: 14px;
background: #eee;
border-radius: 2px;
border: 1px solid gray;
pointer-events: none;
}
var div = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var bars = barGroup.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", 0)
.attr("y", function (d) {
return heightScale(d.Vendor);
})
.attr("width", function (d) {
return widthScale(+d.Share2016)
})
.attr("height", heightScale.bandwidth() / 1.1)
.style("fill", function (d, i) {
return color(i);
})
.on("mouseover",function (d){
div.transition()
.duration(200)
div
.style("opacity", .9)
.html("Vendor: " + "<strong>" + d.Vendor + "</strong>" + "<br>" + "Market share in 2016: " + d.Share2016 + "%")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
d3.select(this)
.style("fill", "#93ceff")
})
.on("mouseout", function(){
d3.select(this)
.transition()
.duration(50)
.style("fill", function(d,i){
return color(i);
})
d3.select(div).remove()
})
What am I doing wrong here?
The problem lies here:
d3.select(div).remove()
As div is itself a selection, you're selecting a selection, and that makes little sense.
Instead of that, just use div in the mouseout:
div.remove()
Or, even better, just set its opacity to zero:
div.style("opacity", 0)
Here is the updated bl.ocks with just that change: http://bl.ocks.org/anonymous/raw/13ce2445b248fb9e44dcd33cfc3dff36/dff0c60423927960cab8aaf9e613c2c3ae205808/