I am trying to understand how can I assign the tooltip div
the exact same position as the element
.
For example,
The full code is below and a notebook. The notebook does not render the tooltip at all, but the following code in the browser does.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.svg-container {
position: relative;
}
.xAxisLabel,
.yAxisLabel {
font-size: medium;
}
.chartTitle {
font-size: medium;
text-decoration: underline;
}
.vornoiBound,
.vornoiOutline,
path.cell {
pointer-events: none;
}
#tooltip {
position: absolute;
width: auto;
height: auto;
padding: 10px;
background-color: white;
border-radius: 10px;
box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
pointer-events: none;
font-size: 1.5vh;
text-align: center;
}
</style>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
<body>
<div id="container" class="svg-container"></div>
</div>
<script type="text/javascript">
const tbl = [{ "Month": 1, "Value": 14841, "MonthName": "Jan" }, { "Month": 2, "Value": 24467, "MonthName": "Feb" }, { "Month": 3, "Value": 78423, "MonthName": "Mar" }, { "Month": 4, "Value": 60213, "MonthName": "Apr" }, { "Month": 5, "Value": 87257, "MonthName": "May" }, { "Month": 6, "Value": 21543, "MonthName": "Jun" }, { "Month": 7, "Value": 21373, "MonthName": "Jul" }, { "Month": 8, "Value": 87363, "MonthName": "Aug" }, { "Month": 9, "Value": 50378, "MonthName": "Sep" }, { "Month": 10, "Value": 29714, "MonthName": "Oct" }, { "Month": 11, "Value": 20171, "MonthName": "Nov" }, { "Month": 12, "Value": 70059, "MonthName": "Dec" }]
//the chart X Axis needs to start from 0
tbl.filter(a => (a.Month == 0)).length == 0 ? tbl.unshift({ "Month": 0, "Value": 0 }) : tbl
//define dimension
const width = 1280;
const height = 720;
//------------------------1.CREATE SVG------------------------//
//namespace
const svgns = 'http://www.w3.org/2000/svg'
d3.select('#container')
.append('svg')
.attr('xmlns', svgns)
.attr('viewBox', `0 0 ${width} ${height}`)
.attr('id', 'svg')
//---create a rect encompassing viewBox --- to be deleted later
const svg = d3.select('svg')
//------------------------2. CREATE BOUND------------------------//
const padding = {
top: 70,
bottom: 50,
left: 70,
right: 50
}
const boundHeight = height - padding.top - padding.bottom;
const boundWidth = width - padding.right - padding.left;
//create bound element
const bound = svg.append('g')
.attr('class', 'bound')
//specify transform, must be .style and not .attr, px needs to be mentioned
.style('transform', `translate(${padding.left}px,${padding.top}px)`)
//------------------------3. CREATE SCALE------------------------//
//scale converts a domain (data) to range (pixel)
const scaleX = d3.scaleLinear()
.range([0, boundWidth])
.domain(d3.extent(tbl, d => d.Month))
const scaleY = d3.scaleLinear()
.range([boundHeight, 0])
.domain(d3.extent(tbl, d => d.Value))
//create Y Axis
bound.append('g').attr('class', 'yAxis')
.call(d3.axisLeft(scaleY).tickSizeOuter(0))
.attr('class', 'yAxis')
//select the first tick and change the opacity to 0
.call(d => d3.select('.yAxis g:nth-of-type(1)')
.attr('opacity', '0')
)
//create Y axis label
.append('text')
.attr('class', 'yAxisLabel')
.style("text-anchor", "middle")
.text('Value')
.attr("transform", `translate(${(padding.left-20)*-1},${boundHeight/2}),rotate(-90)`)
//.attr('transfom', 'rotate(45deg)')
.attr('fill', 'black')
//------------------------4. CREATE AXIS------------------------//
//create X Axis Bottom
bound.append('g')
.attr('class', 'xAxis')
.append('g')
.attr('class', 'xAxisBottom')
.call(d3.axisBottom(scaleX).tickSizeOuter(0))
.style('transform', `translateY(${boundHeight}px)`)
.call(d => d.select('.xAxisBottom g:nth-of-type(1)')
.attr('opacity', '0')
)
//change the class name of the tick
.call(d => d.selectAll('.xAxisBottom>.tick')
.attr('class', (d, i) => { return `xAxisBottomTick${i}` })
)
.call(d => d.append('g').attr('class', 'rectContainer'))
//create X axis label
.append('text')
.attr('class', 'xAxisLabel')
.style("text-anchor", "middle")
.text('Month')
.attr("transform", `translate(${boundWidth/2},40)`)
.attr('fill', 'black')
//------------------------5. CREATE LINE------------------------//
const line = d3.line()
//.curve(eval(`${curves[3]}`))
.x(d => scaleX(d.Month))
.y(d => scaleY(d.Value))
bound.append('g')
.attr('class', 'valLine')
//.append('path')
.selectAll('path')
.data([tbl])
.join('path')
.attr('d', (d, i) => {
return d3.line()
.x(d => scaleX(d.Month))
.y(d => scaleY(d.Value))
(tbl)
})
.attr('class', (d, i) => `line${i}`)
.attr('fill', 'none')
.attr('stroke', 'white')
//create tooltip div
const tooltip = d3.select('body')
.append('div')
.attr('id', 'tooltip')
.attr('style', 'opacity: 0;')
//add circle
const circ =
bound.append('g')
.attr('class', 'valCirc')
.selectAll('circle')
.data(tbl)
.join('circle')
.attr('r', '5')
.attr('cx', d => scaleX(d.Month))
.attr('cy', d => scaleY(d.Value))
.attr('opacity', (d, i) => {
return (i == 0 && d.Month == 0) ? '0' : '1'
})
.attr('pointer-events', 'all')
.on('mouseenter', function(event, d) {
tooltip
.transition().duration(200).style('opacity', '1');
d3.select('#tooltip')
//assign circle's position to div
.style("left", d3.select(this).attr("cx") + "px")
.style("top", d3.select(this).attr("cy") + "px")
.text(d.Value.toLocaleString())
})
.on('mouseout', function(d) {
d3.select('#tooltip').style('opacity', 0)
})
//add inlineLabel
const some = bound.append('g')
.attr('class', 'inlineLabel')
.selectAll('text')
.data(tbl)
.join('text')
.attr('x', d => scaleX(d.Month))
.attr('y', d => scaleY(d.Value) + 12)
.attr('opacity', (d, i) => {
return (i == 0 && d.Month == 0) ? '0' : '1'
})
.text(d => { return d.Value.toLocaleString() })
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'middle')
// add a voronoi diagram on top of the existing elements
// following the docs specify the x and y points through functions referencing the values included in the visualization
const delaunay = d3.Delaunay.from(tbl, d => scaleX(d.Month), d => scaleY(d.Value));
// create a Vonoroi diagram describing its boundaries
const voronoi = delaunay.voronoi([0, 0, boundWidth, boundHeight]);
bound
.append('path')
.attr('class', 'vornoiOutline')
// hidden by default
.attr('opacity', 1)
.attr('d', voronoi.render())
.attr('fill', 'none')
.attr('stroke', 'currentColor');
// add the boundaries as a substitute to the axes,
//and to encase the visualization on four sides
bound
.append('path')
.attr('class', 'vornoiBound')
.attr('d', voronoi.renderBounds())
.attr('fill', 'none')
.attr('stroke', 'currentColor');
// for each data point add a cell
// ! make the cell fully transparent, since the path is included only for mouseover events
bound
.selectAll('path.cell')
.data(tbl)
.enter()
.append('path')
.attr('class', 'cell')
.attr('opacity', 0)
.attr('d', (d, i) => voronoi.renderCell(i))
</script>
<!--d3 script-->
</body>
</html>
What I am struggling with is the following bit of the code. I thought I could simply assign the circle’s position to the tooltip div
but the tooltip is not rendering at the exact same position as circles.
Can you please advise what am I doing wrong here or how to correct this?