This is my first attempt at a reusable function for creating tooltips on D3 charts.
Built with blockbuilder.org
license: mit |
This is my first attempt at a reusable function for creating tooltips on D3 charts.
Built with blockbuilder.org
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<head> | |
<link href='https://fonts.googleapis.com/css?family=Play' rel='stylesheet' type='text/css'> | |
<style> | |
body { | |
margin:auto; | |
font-family: 'Play', sans-serif; | |
} | |
text { | |
font-family: 'Play', sans-serif; | |
} | |
</style> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script src="tooltip.js"></script> | |
</head> | |
<body> | |
</body> | |
<script> | |
// Dummy Data for the chart | |
var data = [ | |
{name:'A',count:64,description:"This is column A!"}, | |
{name:'B',count:32,description:"This is column B!"}, | |
{name:'C',count:16,description:"This is column C!"}, | |
{name:'D',count:8,description:"This is column D!"}, | |
{name:'E',count:4,description:"This is column E!"}, | |
{name:'F',count:2,description:"This is column F!"}, | |
]; | |
// Draw a basic bar chart | |
var margin = {top:20, left:50, bottom:50, right:20}; | |
var height = 500 - margin.top - margin.bottom, | |
width = 960 - margin.left - margin.right; | |
var svg = d3.select("body") | |
.append("svg") | |
.attr("height", height + margin.top + margin.bottom) | |
.attr("width", width + margin.left + margin.right) | |
.style("cursor","default") | |
.append("g") | |
.attr("transform","translate(" + margin.left + "," + margin.top + ")"); | |
var x = d3.scaleBand() | |
.domain(data.map(function(d) { return d.name;})) | |
.range([0,width]) | |
.padding(.1); | |
var y = d3.scaleLinear() | |
.domain([0,70]) | |
.range([height,0]); | |
var color = d3.scaleOrdinal(d3.schemeCategory10) | |
.domain(x.domain()) | |
var tooltip = d3.tooltip() // returns the tooltip function | |
.extent([[0,0],[width,height]]) // tells the tooltip how much area it has to work with | |
.tips(["name","description"],["Bar Name: ","Description: "]) // tells the tooltip which properties to display in the tip and what to label thme | |
.fontSize(12) // sets the font size for the tooltip | |
.padding([8,4]) // sets the amount of padding in the tooltip rectangle | |
.margin([10,10]); // set the distance H and V to keep the tooltip from the mouse pointer | |
svg.selectAll(".bar") | |
.data(data) | |
.enter() | |
.append("rect") | |
.attr("x", function(d) { return x(d.name)}) | |
.attr("y", function(d) { return y(d.count)}) | |
.attr("width", x.bandwidth()) | |
.attr("height", function(d) { return height - y(d.count)}) | |
.attr("fill", function(d) { return color(d.name)}) | |
.each(tooltip.events); // attaches the tooltips mouseenter/leave/move events but does not overwrite previously attached events | |
svg.append("g") | |
.attr("transform", "translate(0," + height + ")") | |
.call(d3.axisBottom(x)) | |
svg.append("g") | |
.call(d3.axisLeft(y)); | |
svg.call(tooltip) // draws the tooltip; | |
</script> |
d3.tooltip = function() { | |
var frameOfReference, | |
extent = new Array([[0,0],[960,500]]), | |
tips = new Array([]), | |
tipNames = new Array([]), | |
tipFormats = new Array([]), | |
margin = new Array([10,10]), | |
padding = new Array([4,4]), | |
translation = new Array([0,0]), | |
tooltipDims = new Array([0,0]), | |
fontSize = 12; | |
var tooltipGroup = d3.select(null), | |
tooltipRect = d3.select(null), | |
tooltipText = d3.select(null); | |
var tooltip = function(context) { | |
tooltipGroup.remove(); | |
frameOfReference = context.node() | |
tooltipGroup = context.append("g") | |
.classed("d3Tooltip",true) | |
.style("display","none"); | |
tooltipRect = tooltipGroup.append("rect") | |
.attr("width",100) | |
.attr("height",100) | |
.style("fill","black") | |
.style("fill-opacity",.70) | |
tooltipText = tooltipGroup.append("text") | |
.style("fill", "white") | |
.style("font-size",fontSize); | |
}; | |
function displayTooltip(d) { | |
tooltipGroup.style("display",null); | |
tooltipText.selectAll("tspan") | |
.remove(); | |
tooltipText.attr("y", padding[1]) | |
.selectAll("tspan") | |
.data(tips) | |
.enter() | |
.append("tspan") | |
.attr("x",padding[0]) | |
.attr("dy",fontSize*.9) | |
.text(function(e,i) { | |
var val; | |
if(typeof d[e] == 'undefined') { | |
// check the 'data' property too, for voronoi mouseover events | |
val = d.data[e]; | |
} else { | |
val = d[e]; | |
} | |
if(tipFormats[i]) { | |
val = tipFormats[i](val); | |
} | |
return tipNames[i] + val; | |
}); | |
updateTooltipDims(); | |
tooltipRect.attr("width", tooltipDims[0]) | |
.attr("height", tooltipDims[1]) | |
updateTranslation(); | |
tooltipGroup.attr("transform","translate(" + translation[0] + "," + translation[1] + ")") | |
} | |
function hideTooltip() { | |
tooltipGroup.style("display","none") | |
} | |
function moveTooltip() { | |
updateTranslation(); | |
tooltipGroup.attr("transform","translate(" + translation[0] + "," + translation[1] + ")"); | |
} | |
tooltip.events = function() { | |
var me = d3.select(this).on("mouseenter") || function() {}; | |
var ml = d3.select(this).on("mouseleave") || function() {}; | |
var mm = d3.select(this).on("mousemove") || function() {}; | |
d3.select(this) | |
.on("mouseenter", function(d,i) { me(d,i); displayTooltip(d,i);}) | |
.on("mouseleave", function(d,i) { ml(d,i); hideTooltip(d,i); }) | |
.on("mousemove", function(d,i) { mm(d,i); moveTooltip(d,i)}); | |
} | |
tooltip.extent = function(_extent){ | |
extent = _extent || extent; | |
return tooltip; | |
} | |
tooltip.fontSize = function(_fontSize) { | |
fontSize = _fontSize || fontSize; | |
return tooltip; | |
} | |
tooltip.margin = function(_margin) { | |
margin = _margin || margin; | |
return tooltip; | |
} | |
tooltip.padding = function(_padding) { | |
padding = _padding || padding; | |
return tooltip; | |
} | |
tooltip.tips = function(_tips,_tipNames,_tipFormats) { | |
tips = _tips || tips; | |
tipNames = _tipNames || tips; | |
tipFormats = _tipFormats || tips.map(function(d) { return null}); | |
return tooltip; | |
} | |
function updateTooltipDims() { | |
var bb = tooltipText.node().getBBox(); | |
tooltipDims = [bb.width + padding[0]*2, bb.height + padding[1]*2]; | |
} | |
function updateTranslation() { | |
var mouseCoordinates = d3.mouse(frameOfReference); | |
var quad = [0,0]; | |
if(mouseCoordinates[0] > (extent[1][0] - extent[0][0])/2) quad[0] = 1; | |
if(mouseCoordinates[1] > (extent[1][1] - extent[0][1])/2) quad[1] = 1; | |
if(quad[0] == 1) { | |
translation[0] = mouseCoordinates[0] - tooltipDims[0] - margin[0]; | |
} else { | |
translation[0] = mouseCoordinates[0] + margin[0]; | |
} | |
if(quad[1] == 1) { | |
translation[1] = mouseCoordinates[1] - tooltipDims[1] - margin[1]; | |
} else { | |
translation[1] = mouseCoordinates[1] + margin[1]; | |
} | |
} | |
return tooltip; | |
} |