I'm building a D3 treemap diagram that requires a tooltip. The tooltip is a div
element that I'm toggling on and off with the 'mouseover' and 'mouseleave' events attached to each leaf of the treemap. The developer console confirms that the element is being added to the DOM, that it has the correct text to display within the div
, that 'display' is set to 'block' when hovering, 'z-index' is 10, and that the 'position' is 'absolute', however, I can't get the tooltip to actually appear as expected.
Here's a link to the Codepen.
Here's my actual code:
HTML:
<script src="https://cdn.freecodecamp.org/testable-projects-fcc/v1/bundle.js"></script>
<div>
<h1 id='title'>JSON Data Treemaps</h1>
<h2 id='description'>Top 100 Most Pledged Kickstarter Campaigns</h2>
<svg id="canvas"></svg>
<svg id="legend"></svg>
</div>
CSS:
@import url('https://fonts.googleapis.com/css?family=Noto+Sans');
body {
background-color: ghostwhite;
display: flex;
flex-direction: column;
font-size: 16px;
font-family: 'Noto Sans', sans-serif;
div#tooltip {
position: absolute;
}
div {
display: flex;
flex-direction: column;
h1 {
margin: 4.6rem auto 0;
font-size: 3.2rem;
}
h2 {
margin: 1rem auto 2rem;
font-size: 1.4rem;
}
svg {
margin: auto;
}
svg#legend {
margin: 2rem auto;
}
}
}
JS:
const margin = {
top: 30,
bottom: 100,
left: 30,
right: 30
},
width = 960 - margin.left - margin.right,
height = 570 - margin.top - margin.bottom;
const KICK_STARTER_DATA_URL = 'https://cdn.rawgit.com/freeCodeCamp/testable-projects-fcc/a80ce8f9/src/data/tree_map/kickstarter-funding-data.json';
const MOVIE_DATA_URL = 'https://cdn.rawgit.com/freeCodeCamp/testable-projects-fcc/a80ce8f9/src/data/tree_map/movie-data.json';
const VIDEO_GAME_DATA_URL = 'https://cdn.rawgit.com/freeCodeCamp/testable-projects-fcc/a80ce8f9/src/data/tree_map/video-game-sales-data.json';
// Load all data and assign to variables.
d3.queue()
.defer(d3.json, KICK_STARTER_DATA_URL)
.defer(d3.json, MOVIE_DATA_URL)
.defer(d3.json, VIDEO_GAME_DATA_URL)
.await(storeDataCategories);
let KICK_STARTER_DATA;
let MOVIE_DATA;
let VIDEO_GAME_DATA;
let currentDataSet;
function storeDataCategories(error, kickStarter, movies, videoGames) {
// console.log(...arguments);
if(error) throw error;
KICK_STARTER_DATA = kickStarter;
MOVIE_DATA = movies;
VIDEO_GAME_DATA = videoGames;
currentDataSet = kickStarter;
buildTreemap();
buildLegend();
}
var svg = d3.select('svg#canvas')
.attr('width', width)
.attr('height', height)
.style('background-color', 'ghostwhite')
.style('box-shadow', '0 6px 26px darkgray');
var fader = (color) => {
return d3.interpolateRgb(color, 'ghostwhite')(0.2);
},
test = d3.schemeCategory20.map(fader),
colorScale = d3.scaleOrdinal(d3.schemeCategory20.map(fader)),
format = d3.format(',d');
var treemap = d3.treemap()
.tile(d3.treemapResquarify)
.size([width, height]);
// Adding tooltip for info on hover
const tooltip = d3.select('svg#canvas')
.append('div')
.attr('id', 'tooltip')
.attr('width', 60 + 'px')
.attr('height', 40 + 'px')
.style('z-index', 10)
.style('display', 'none')
.style('position', 'absolute');
function buildTreemap() {
// console.log(KICK_STARTER_DATA);
// console.log(MOVIE_DATA);
// console.log(VIDEO_GAME_DATA);
// console.log(currentDataSet);
var root = d3.hierarchy(currentDataSet)
.sum(sumValue)
.sort((a, b) => b.height - a.height || b.value - a.value);
treemap(root);
var cell = svg.selectAll('g')
.data(root.leaves())
.enter().append('g')
.attr('transform', d => 'translate(' + [d.x0, d.y0] + ')')
.on('mouseover', d => {
// console.log('mouseover - d:\n', d);
const tooltipText = formatTooltip(d);
tooltip.transition().duration(200)
.style('position', 'absolute')
.style('display', 'block');
tooltip.html(tooltipText)
.attr('data-value', d.value)
.style('top', (d3.event.pageY - 50) + 'px')
.style('left', (d3.event.pageX + 20) + 'px');
})
.on('mouseout', d => {
// console.log('mouseout!');
tooltip.transition().duration(500)
.style('display', 'none');
});
cell.append('rect')
.attr('id', d => d.data.id)
.attr('class', 'tile')
.attr('width', d => d.x1 - d.x0)
.attr('height', d => d.y1 - d.y0)
.attr('fill', d => colorScale(d.parent.data.name))
.attr('data-name', d => d.data.name)
.attr('data-category', d => d.parent.data.name)
.attr('data-value', d => d.data.value);
cell.append('clipPath')
.attr('id', d => `clip-${d.data.name}`)
.append('use')
.attr('xlink:href', d => `#${d.data.name}`);
cell.append('text')
.attr('clip-path', d => `url(#clip-${d.data.name})`)
.selectAll('tspan')
.data(d => d.data.name.split(/(?=[A-Z][^A-Z])/g))
.enter().append('tspan')
.attr('x', 4)
.attr('y', (d, i) => 13 + i * 10)
.text(d => d)
.style('font-size', '10');
}
function buildLegend() {
console.log(`inside 'buildLegend'`);
// console.log(currentDataSet);
const rectWidth = width / 8,
rectHeight = 40;
const legend = d3.select('svg#legend')
.attr('width', width)
.attr('height', rectHeight);
legend.append('g')
.selectAll('g')
.data(colorScale.domain())
.enter()
.append('g')
.attr('class', 'legend')
.append('rect')
.attr('class', 'legend-item')
.attr('width', '40px')
.attr('height', '40px')
.attr('transform', (d, i) => {
return 'translate(' + i * 40 + ',' + 0 + ')';
})
.style('fill', d => colorScale(d))
.style('stroke', d => colorScale(d));
}
function sumValue(d) {
return d.value;
}
function formatTooltip(d) {
const name = d.data.name,
category = d.data.category,
value = d.data.value,
tooltipText = `
<span>Name:</span> ${name} <br>
<span>Category: </span> ${category} <br>
<span>Value:</span> ${value} <br>`;;
console.log('formatted tooltip:\n', tooltipText);
return tooltipText;
}
Minor mistake: You're appending a <div>
to a SVG which wouldn't work. Changing the code and appending it to the body, here's a fork:
https://codepen.io/anon/pen/KbVvwR
Code changes:
const tooltip = d3.select('body')
and a couple of minor CSS additions:
div#tooltip {
background: #FFF;
pointer-events: none; // important
padding: 4px;
border: 1px solid #CCC;
border-radius: 3px;
}
Hope this helps.