Skip to content

Instantly share code, notes, and snippets.

@ajfarkas
Last active June 2, 2016 05:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ajfarkas/b2c366120ec400f80b1ea198355c252d to your computer and use it in GitHub Desktop.
Save ajfarkas/b2c366120ec400f80b1ea198355c252d to your computer and use it in GitHub Desktop.
Tool Tips for Line Graph

Tool Tips

An SVG hovering element that shows the value under the mouse point. This example generates random Y-axis values (-100 to 100) across a 120-second interval (X-axis).

A good amount of this code is for centering text, which I plan to streamline at some point.

var randomData = []
// create a bunch of values for the graph
function createrandomData() {
var now = Date.now()
for(var i = 0; i < 120; i++) {
randomData[i] = {
yValue: (Math.random() - 0.5) * 200,
time: now + (i * 1000)
}
}
}
function createGraph() {
createrandomData()
// define plot boundaries
var width = 300,
height = 60
var margin = {
top: 0,
right: 50,
bottom: 5,
left: 50
}
var plot = {
width: width - margin.right - margin.left,
height: height - margin.top - margin.bottom
}
// x-axis is time
var x = d3.time.scale()
.range([0, plot.width])
// y-axis is numerical
var y = d3.scale.linear()
.range([plot.height, 0])
// set axis scales
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom')
.tickFormat(d3.time.format('%M:%S'))
.tickSize(0, 0).ticks(6)
var yAxis = d3.svg.axis()
.scale(y)
.orient('left')
.tickSize(0, 0).ticks(3)
x.domain([randomData[0].time, randomData[randomData.length - 1].time])
y.domain([-100, 100])
var line = d3.svg.line()
.x(function(d) { return x(parseInt(d.time)) })
.y(function(d) { return y(d.yValue) })
// make the graph
var svg = d3.select('#graph')
var graph = undefined
if (d3.select('.graph-g').empty()) {
graph = svg.append('g')
.attr('class', 'graph-g')
.attr('width', plot.width)
.attr('height', plot.height)
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
//add axes
graph.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + plot.height + ')')
.call(xAxis)
graph.append('g')
.attr('class', 'y axis')
.call(yAxis)
.append('text')
.attr('transform', 'rotate(-90)')
.attr('dx', (0 - plot.height / 2))
.attr('dy', '-2.8em')
.style('text-anchor', 'middle')
.text('Value')
} else {
graph = d3.select('.graph-g')
}
//add data line
graph.append('path')
.datum(randomData)
.attr('class', 'line')
.attr('d', line)
// Tooltip for yValue value at point of hover on line.
// create tooltip
var tip = graph.append('g')
.attr('class', 'tool-tip')
.style('visibility', 'hidden')
var tipBg = tip.append('rect')
.attr('class', 'tool-tipBG')
var tipTitle = tip.append('text')
.attr('x', '0')
.attr('y', '0')
.attr('class', 'tip-title')
.text('00:00')
var titleBB = tipTitle.node().getBBox()
var tipVal = tip.append('text')
.attr('x', '0')
.attr('y', '0')
.attr('class', 'tip-number')
.text('+100')
tipVal
.attr('y', tipVal.node().getBBox().height - 2)
.attr('x', tipVal.node().getBBox().width - 3)
var valBB = tipVal.node().getBBox()
var tipText = tip.append('text')
.attr('x', valBB.width)
.attr('y', valBB.height - 2)
.attr('class', 'tip-text')
.text('sprokets')
var textBB = tipText.node().getBBox()
tipBg
.attr('x', '0')
.attr('y', -titleBB.height - 3)
.attr('dx', '-3')
.attr('rx', '4')
.attr('ry', '4')
.attr('width',
Math.max(valBB.width + textBB.width + 7, titleBB.width + 7)
)
.attr('height', titleBB.height + valBB.height + 6)
var bgBB = tipBg.node().getBBox()
tipTitle.attr('dx', bgBB.width/2)
tipTitle.attr('dy', -2)
tipVal.attr('dx', (bgBB.width - valBB.width - textBB.width)/2 )
tipText.attr('dx', (bgBB.width - valBB.width - textBB.width)/2 - 2)
tip.append('path')
.attr('class', 'tool-tipPoint')
.attr('d', 'M-6,0L6,0L0,8Z')
.attr('transform', 'translate('+bgBB.width/2+', '+(valBB.height+2.8)+')')
// set bisection data
var bisect = d3.bisector(function(d) { return d.time }).left
// show/hide tooltip
function updateTipData() {
var x0 = x.invert(d3.mouse(this)[0])
var i = bisect(randomData, x0, 1)
// find the closest value to the mouse
var dPre = randomData[i - 1],
dPost = randomData[i]
if (typeof dPost === 'undefined') {
return false
}
var d = x0 - dPre.time < dPost.time - x0
? dPre
: dPost
// apply value to tip text (with + for pos, - for neg, and null for 0)
tipVal.text(function() {
return (d.yValue > 0
? '+'+Math.round(d.yValue)
: Math.round(d.yValue)
)
})
tipTitle.text(function() {
var time = new Date(d.time)
return ('0'+time.getMinutes()).slice(-2)+':'+('0'+time.getSeconds()).slice(-2)
})
// show tip at nearest data point
tip.attr('transform', 'translate('+( x(d.time) - bgBB.width/2)+','+( y(d.yValue) - bgBB.height/2 - 9 )+')')
}
graph.on('mouseover', function() { tip.style('visibility', 'visible') })
d3.select('.line').on('mousemove', updateTipData)
svg.on('mouseout', function() { tip.style('visibility', 'hidden') })
}
// make it so
createGraph()
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<link href="styles.css" rel="stylesheet" type="text/css"/>
<title>Tool Tips</title>
</head>
<body>
<svg id="graph" viewBox="0 0 300 60"></svg>
</body>
<script src="graph.js" rel="javascript" type="text/javascript"></script>
</html>
#graph {
width: 960px;
height: 480px;
}
.line {
fill: none;
stroke: black;
}
text { font-size: 0.5rem; }
.tool-tipBG { fill: gold; }
.tool-tipPoint { fill: gold; }
.tip-title { text-anchor: middle; }
.tip-number { text-anchor: end; }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment