I am visualizing data using d3.js
. Within the visualization, I have built a HTML tooltip. I have overlaid a svg
rect to listen to the desired mouseevents before it can generate the tooltip. It has been good so far.
However, I desire the tooltip to adjust its position in a way so that it always stays inside the listening rect
. But I can't seem to find a way to do that.
The code is below
const data = [{ x: 1, y: .03 }, { x: 2, y: .04 }, { x: 3, y: .06 }, { x: 4, y: .09 }, { x: 5, y: .13 }, { x: 6, y: .18 }, { x: 7, y: .25 }, { x: 8, y: .33 }, { x: 9, y: .45 }, { x: 10, y: .59 }, { x: 11, y: .76 }, { x: 12, y: .97 }, { x: 13, y: 1.22 }, { x: 14, y: 1.52 }, { x: 15, y: 1.85 }, { x: 16, y: 2.24 }, { x: 17, y: 2.66 }, { x: 18, y: 3.13 }, { x: 19, y: 3.63 }, { x: 20, y: 4.16 }, { x: 21, y: 4.71 }, { x: 22, y: 5.27 }, { x: 23, y: 5.83 }, { x: 24, y: 6.38 }, { x: 25, y: 6.91 }, { x: 26, y: 7.4 }, { x: 27, y: 7.85 }, { x: 28, y: 8.25 }, { x: 29, y: 8.59 }, { x: 30, y: 8.86 }, { x: 31, y: 9.06 }, { x: 32, y: 9.18 }, { x: 33, y: 9.24 }, { x: 34, y: 9.22 }, { x: 35, y: 9.15 }, { x: 36, y: 9.01 }, { x: 37, y: 8.83 }, { x: 38, y: 8.6 }, { x: 39, y: 8.34 }, { x: 40, y: 8.05 }, { x: 41, y: 7.74 }, { x: 42, y: 7.41 }, { x: 43, y: 7.08 }, { x: 44, y: 6.76 }, { x: 45, y: 6.43 }, { x: 46, y: 6.11 }, { x: 47, y: 5.81 }, { x: 48, y: 5.51 }, { x: 49, y: 5.24 }, { x: 50, y: 4.98 }, { x: 51, y: 4.74 }, { x: 52, y: 4.52 }, { x: 53, y: 4.32 }, { x: 54, y: 4.15 }, { x: 55, y: 3.99 }, { x: 56, y: 3.84 }, { x: 57, y: 3.71 }, { x: 58, y: 3.59 }, { x: 59, y: 3.49 }, { x: 60, y: 3.39 }, { x: 61, y: 3.31 }, { x: 62, y: 3.23 }, { x: 63, y: 3.15 }, { x: 64, y: 3.09 }, { x: 65, y: 3.02 }, { x: 66, y: 2.97 }, { x: 67, y: 2.91 }, { x: 68, y: 2.86 }, { x: 69, y: 2.81 }, { x: 70, y: 2.77 }, { x: 71, y: 2.73 }, { x: 72, y: 2.68 }, { x: 73, y: 2.64 }, { x: 74, y: 2.6 }, { x: 75, y: 2.56 }, { x: 76, y: 2.52 }, { x: 77, y: 2.47 }, { x: 78, y: 2.43 }, { x: 79, y: 2.39 }, { x: 80, y: 2.34 }, { x: 81, y: 2.29 }, { x: 82, y: 2.24 }, { x: 83, y: 2.19 }, { x: 84, y: 2.13 }, { x: 85, y: 2.08 }, { x: 86, y: 2.02 }, { x: 87, y: 1.96 }, { x: 88, y: 1.89 }, { x: 89, y: 1.83 }, { x: 90, y: 1.76 }, { x: 91, y: 1.69 }, { x: 92, y: 1.62 }, { x: 93, y: 1.54 }, { x: 94, y: 1.47 }, { x: 95, y: 1.39 }, { x: 96, y: 1.32 }, { x: 97, y: 1.24 }, { x: 98, y: 1.17 }, { x: 99, y: 1.09 }, { x: 100, y: 1.02 }];
//------------------------0.DATA WRANGLING------------------------//
const xVal = [...new Set(data.map(d => d.x))];
const countX = xVal.length;
const midIndex = Math.floor(countX / 2);
const median = (countX % 2 === 1) ? xVal[midIndex] : ((xVal[midIndex] + xVal[midIndex - 1]) / 2);
//------------------------1.CREATE SVG------------------------//
//define dimension
const width = 1280;
const height = 600;
const main = d3.select('div')
.style('position', 'relative');
//HTML DIV for tooltip
const div =
main.append('div')
.attr('class', 'tooltip')
.style('opacity', '0')
.style('position', 'absolute')
.style('display', 'flex')
.style('background', 'yellow');
//namespace
const svgns = 'http://www.w3.org/2000/svg'
main
.append('svg')
.attr('xmlns', svgns)
.attr('viewBox', `0 0 ${width} ${height}`)
.attr('id', 'svg')
const svg = d3.select('svg')
svg.append('rect')
.attr('class', 'vBoxRect')
.attr('width', `${width}`)
.attr('height', `${height}`)
.attr('fill', 'none')
.attr('stroke', 'red');
//------------------------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 rect -- to be deleted later
svg.append('rect')
.attr('class', 'boundRect')
.attr('x', `${padding.left}`)
.attr('y', `${padding.top}`)
.attr('width', `${boundWidth}`)
.attr('height', `${boundHeight}`)
.attr('fill', 'none')
.attr('stroke', 'green')
//create bound element
const bound = svg.append('g')
.attr('class', 'bound')
.style('transform', `translate(${padding.left}px,${padding.top}px)`);
//------------------------3. CREATE SCALE------------------------//
const scaleX = d3.scaleLinear()
.range([0, boundWidth])
.domain(d3.extent(data, d => d.x));
const scaleY = d3.scaleLinear()
.range([boundHeight, 0])
.domain(d3.extent(data, d => d.y));
//------------------------4. CREATE AXIS------------------------//
//create Y Axis
bound.append('g').attr('class', 'yAxis')
.call(d3.axisLeft(scaleY))
.attr('class', 'yAxis');
//create X Axis Bottom
bound.append('g')
.attr('class', 'xAxis')
.append('g')
.attr('class', 'xAxisBottom')
.call(d3.axisBottom(scaleX))
.style('transform', `translateY(${boundHeight}px)`);
//------------------------5. CREATE PATH------------------------//
// Add the line
bound
.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(d => scaleX(d.x))
.y(d => scaleY(d.y)));
//------------------------6. CREATE INTERACTION------------------------//
// Create the text that travels along the curve of chart
const focusText = bound
.append('g')
.attr('class', 'travText')
.append('text')
.attr("opacity", 0);
// listening Rect
const listener =
bound
.append('rect')
.style("fill", "none")
.attr('class', 'listeningRect')
.style("pointer-events", "all")
.attr('width', boundWidth)
.attr('height', boundHeight);
//------------------------B. PROGRAM INTERACTION--------------//
listener
.on('mouseover', function() {
focusText.attr("opacity", 1);
div.style('opacity', '1');
})
.on('mouseout', function() {
focusText.attr("opacity", 0);
div.style('opacity', '0');
})
.on('mousemove', function(event) {
const mouseCoord = d3.pointer(event);
const mouseCoordX = mouseCoord[0];
const invertX = scaleX.invert(mouseCoordX);
const rnd = Math.round(invertX);
const src = data.filter(a => a.x === rnd)[0];
focusText
.html("SVG Element-x:" + src.x + " - " + "y:" + src.y)
.attr("x", () => ((src.x < median) ?
scaleX(src.x) :
scaleX(src.x)
))
.attr("y", scaleY(src.y))
.attr("text-anchor",
() => (src.x < median) ?
'start' : (src.x > median) ? 'end' : 'auto');
div.html("HTML Element-x:" + src.x + " - " + "y:" + src.y)
.style('top', `${event.pageY}px`)
.style(`left`, `${event.pageX}px`)
});
<!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>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
<body>
<div id="viz"></div>
</body>
</html>
To elaborate, if I also create a SVG
tooltip, I can do the following which can cheaply ensure that the svg
text auto-adjusts its position and width
wise it always stays inside the listening rect. How can I achieve the similar for the HTML
element?
Via Active questions tagged javascript - Stack Overflow https://ift.tt/htNsJ70.attr("text-anchor", () => (src.x < median) ? 'start' : (src.x > median) ? 'end' : 'auto');
Comments
Post a Comment