Skip to content

Instantly share code, notes, and snippets.

@jebeck
Last active August 29, 2015 14:10
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jebeck/5703573bd695c66b6e99 to your computer and use it in GitHub Desktop.
Save jebeck/5703573bd695c66b6e99 to your computer and use it in GitHub Desktop.
Browser-zoom robust foreignObject tooltips

Browser-zoom robust foreignObject tooltips in SVG

Based on this previous gist, but robust to changes in browser zoom level (text inside tooltip doesn't get cut off when zoom < 100%).

(function(root,ns,factory){"use strict";"undefined"!=typeof module&&module.exports?module.exports=factory(ns,root):"function"==typeof define&&define.amd?define("detect-zoom",function(){return factory(ns,root)}):root[ns]=factory(ns,root)})(window,"detectZoom",function(){var devicePixelRatio=function(){return window.devicePixelRatio||1},fallback=function(){return{zoom:1,devicePxPerCssPx:1}},ie8=function(){var zoom=Math.round(100*(screen.deviceXDPI/screen.logicalXDPI))/100;return{zoom:zoom,devicePxPerCssPx:zoom*devicePixelRatio()}},ie10=function(){var zoom=Math.round(100*(document.documentElement.offsetHeight/window.innerHeight))/100;return{zoom:zoom,devicePxPerCssPx:zoom*devicePixelRatio()}},webkitMobile=function(){var deviceWidth=90==Math.abs(window.orientation)?screen.height:screen.width,zoom=deviceWidth/window.innerWidth;return{zoom:zoom,devicePxPerCssPx:zoom*devicePixelRatio()}},webkit=function(){var important=function(str){return str.replace(/;/g," !important;")},div=document.createElement("div");div.innerHTML="1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>0",div.setAttribute("style",important("font: 100px/1em sans-serif; -webkit-text-size-adjust: none; text-size-adjust: none; height: auto; width: 1em; padding: 0; overflow: visible;"));var container=document.createElement("div");container.setAttribute("style",important("width:0; height:0; overflow:hidden; visibility:hidden; position: absolute;")),container.appendChild(div),document.body.appendChild(container);var zoom=1e3/div.clientHeight;return zoom=Math.round(100*zoom)/100,document.body.removeChild(container),{zoom:zoom,devicePxPerCssPx:zoom*devicePixelRatio()}},firefox4=function(){var zoom=mediaQueryBinarySearch("min--moz-device-pixel-ratio","",0,10,20,1e-4);return zoom=Math.round(100*zoom)/100,{zoom:zoom,devicePxPerCssPx:zoom}},firefox18=function(){return{zoom:firefox4().zoom,devicePxPerCssPx:devicePixelRatio()}},opera11=function(){var zoom=window.top.outerWidth/window.top.innerWidth;return zoom=Math.round(100*zoom)/100,{zoom:zoom,devicePxPerCssPx:zoom*devicePixelRatio()}},mediaQueryBinarySearch=function(property,unit,a,b,maxIter,epsilon){function binarySearch(a,b,maxIter){var mid=(a+b)/2;if(0>=maxIter||epsilon>b-a)return mid;var query="("+property+":"+mid+unit+")";return matchMedia(query).matches?binarySearch(mid,b,maxIter-1):binarySearch(a,mid,maxIter-1)}var matchMedia,head,style,div;window.matchMedia?matchMedia=window.matchMedia:(head=document.getElementsByTagName("head")[0],style=document.createElement("style"),head.appendChild(style),div=document.createElement("div"),div.className="mediaQueryBinarySearch",div.style.display="none",document.body.appendChild(div),matchMedia=function(query){style.sheet.insertRule("@media "+query+"{.mediaQueryBinarySearch "+"{text-decoration: underline} }",0);var matched="underline"==getComputedStyle(div,null).textDecoration;return style.sheet.deleteRule(0),{matches:matched}});var ratio=binarySearch(a,b,maxIter);return div&&(head.removeChild(style),document.body.removeChild(div)),ratio},detectFunction=function(){var func=fallback;return isNaN(screen.logicalXDPI)||isNaN(screen.systemXDPI)?window.navigator.msMaxTouchPoints?func=ie10:"orientation"in window&&"string"==typeof document.body.style.webkitMarquee?func=webkitMobile:"string"==typeof document.body.style.webkitMarquee?func=webkit:navigator.userAgent.indexOf("Opera")>=0?func=opera11:window.devicePixelRatio?func=firefox18:firefox4().zoom>.001&&(func=firefox4):func=ie8,func}();return{zoom:function(){return detectFunction().zoom},device:function(){return detectFunction().devicePxPerCssPx}}});
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>
SVG foreignObject tooltips in D3
</title>
<script src='http://d3js.org/d3.v3.min.js' charset='utf-8'></script>
<style type='text/css'>
svg {
display: block;
margin: 0 auto;
}
.svg-tooltip {
pointer-events: none;
}
.tooltip {
padding: 0.8333333333em;
color: #4A22FF;
}
.lead {
font-style: italic;
}
p {
margin: 0.4166666667em 0em;
font-size: 1em;
}
polygon {
pointer-events: none;
}
</style>
</head>
<body>
<script type="text/javascript" src='detect-zoom.min.js'></script>
<script type='text/javascript'>
var zoomPercentage = window.detectZoom.device();
d3.select(window).on('resize', function() {
zoomPercentage = window.detectZoom.device();
});
var margin = {top: 20, right: 10, bottom: 20, left: 10};
var width = 800 - margin.left - margin.right;
var height = 480 - margin.top - margin.bottom;
var svg = d3.select('body')
.append('svg')
.attr({
'width': width + margin.left + margin.right,
'height': height + margin.top + margin.bottom
})
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
svg.append('rect')
.attr({
'width': width * 0.8,
'height': height * 0.8,
'x': width * 0.1,
'y': height * 0.1,
'fill': '#F8F8F8'
});
var foWidth = 300;
var anchor = {'w': width/3, 'h': height/3};
var t = 50, k = 15;
var tip = {'w': (3/4 * t), 'h': k};
svg.append('circle')
.attr({
'r': 50,
'cx': anchor.w,
'cy': anchor.h,
'fill': '#7413E8',
'opacity': 0.35
})
.on('mouseover', function() {
var fo = svg.append('foreignObject')
.attr({
'x': anchor.w - tip.w,
'y': anchor.h + tip.h,
'width': foWidth,
'class': 'svg-tooltip'
});
var div = fo.append('xhtml:div')
.append('div')
.attr({
'class': 'tooltip'
});
div.append('p')
.attr('class', 'lead')
.html('Holmes was certainly not a difficult man to live with.');
div.append('p')
.html('He was quiet in his ways, and his habits were regular. It was rare for him to be up after ten at night, and he had invariably breakfasted and gone out before I rose in the morning.');
var foHeight = div[0][0].getBoundingClientRect().height;
console.log('Orig foHeight', foHeight);
console.log('zoomPercentage', zoomPercentage);
foHeight = foHeight/zoomPercentage;
console.log('New foHeight', foHeight);
fo.attr({
'height': foHeight
});
svg.insert('polygon', '.svg-tooltip')
.attr({
'points': "0,0 0," + foHeight + " " + foWidth + "," + foHeight + " " + foWidth + ",0 " + (t) + ",0 " + tip.w + "," + (-tip.h) + " " + (t/2) + ",0",
'height': foHeight + tip.h,
'width': foWidth,
'fill': '#D8D8D8',
'opacity': 0.75,
'transform': 'translate(' + (anchor.w - tip.w) + ',' + (anchor.h + tip.h) + ')'
});
})
.on('mouseout', function() {
svg.selectAll('.svg-tooltip').remove();
svg.selectAll('polygon').remove();
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment