2012-03-20 23:43:21 +00:00
|
|
|
|
|
|
|
/*****
|
2013-01-09 15:51:53 +00:00
|
|
|
* A no-frills tooltip implementation.
|
2012-03-20 23:43:21 +00:00
|
|
|
*****/
|
|
|
|
|
|
|
|
|
2012-05-07 12:53:51 +00:00
|
|
|
(function() {
|
|
|
|
|
2013-07-02 18:37:54 +00:00
|
|
|
window.nv.tooltip = {};
|
|
|
|
|
|
|
|
/* Model which can be instantiated to handle tooltip rendering.
|
|
|
|
*/
|
|
|
|
window.nv.models.tooltip = function() {
|
2013-07-02 19:25:04 +00:00
|
|
|
var content = null //HTML contents of the tooltip. If null, the content is generated via the data variable.
|
|
|
|
, data = null /* Tooltip data. If data is given in the proper format, a consistent tooltip is generated.
|
|
|
|
Format of data:
|
|
|
|
{
|
|
|
|
key: "Date",
|
|
|
|
value: "August 2009",
|
2013-07-02 20:07:20 +00:00
|
|
|
seriesSelectedKey: "Series 2",
|
2013-07-02 19:25:04 +00:00
|
|
|
series: [
|
|
|
|
{
|
|
|
|
key: "Series 1",
|
2013-07-02 21:14:43 +00:00
|
|
|
value: "Value 1",
|
|
|
|
color: "#000"
|
2013-07-02 19:25:04 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
key: "Series 2",
|
2013-07-02 21:14:43 +00:00
|
|
|
value: "Value 2",
|
|
|
|
color: "#00f"
|
2013-07-02 19:25:04 +00:00
|
|
|
}
|
|
|
|
]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
*/
|
2013-07-04 03:17:08 +00:00
|
|
|
, gravity = 'w' //Can be 'n','s','e','w'. Determines how tooltip is positioned.
|
|
|
|
, distance = 50 //Distance to offset tooltip from the mouse location.
|
|
|
|
, snapDistance = 25 //Tolerance allowed before tooltip is moved from its current position (creates 'snapping' effect)
|
2013-07-02 18:37:54 +00:00
|
|
|
, fixedTop = null //If not null, this fixes the top position of the tooltip.
|
|
|
|
, classes = null //Attaches additional CSS classes to the tooltip DIV that is created.
|
2013-07-06 18:59:19 +00:00
|
|
|
, chartContainer = null //SVG Container that holds the chart.
|
2013-07-02 18:37:54 +00:00
|
|
|
, position = {left: null, top: null} //Relative position of the tooltip inside chartContainer.
|
2013-07-02 20:07:20 +00:00
|
|
|
, enabled = true //True -> tooltips are rendered. False -> don't render tooltips.
|
2013-07-02 18:37:54 +00:00
|
|
|
;
|
|
|
|
|
2013-07-02 20:07:20 +00:00
|
|
|
var valueFormatter = function(d,i) {
|
|
|
|
return d;
|
|
|
|
};
|
|
|
|
|
2013-07-02 19:25:04 +00:00
|
|
|
var contentGenerator = function(d) {
|
|
|
|
if (content != null) return content;
|
|
|
|
|
|
|
|
if (d == null) return '';
|
|
|
|
|
2013-07-02 21:14:43 +00:00
|
|
|
var html = "<table><thead><tr><td colspan='3'><strong class='x-value'>" + d.value + "</strong></td></tr></thead><tbody>";
|
2013-07-02 19:25:04 +00:00
|
|
|
if (d.series instanceof Array) {
|
2013-07-02 20:07:20 +00:00
|
|
|
d.series.forEach(function(item, i) {
|
|
|
|
var isSelected = (item.key === d.seriesSelectedKey) ? "selected" : "";
|
2013-07-02 21:14:43 +00:00
|
|
|
html += "<tr class='" + isSelected + "'>";
|
|
|
|
html += "<td class='legend'><div style='background-color: " + item.color + ";'></div></td>";
|
|
|
|
html += "<td class='key'>" + item.key + ":</td>";
|
2013-07-02 20:07:20 +00:00
|
|
|
html += "<td class='value'>" + valueFormatter(item.value,i) + "</td></tr>";
|
2013-07-02 19:25:04 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
html += "</tbody></table>";
|
|
|
|
return html;
|
|
|
|
};
|
|
|
|
|
2013-07-03 17:43:32 +00:00
|
|
|
var dataSeriesExists = function(d) {
|
|
|
|
if (d && d.series && d.series.length > 0) return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
2013-07-02 18:37:54 +00:00
|
|
|
//In situations where the chart is in a 'viewBox', re-position the tooltip based on how far chart is zoomed.
|
|
|
|
function convertViewBoxRatio() {
|
|
|
|
if (chartContainer) {
|
|
|
|
var svg = d3.select(chartContainer).select('svg');
|
|
|
|
var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
|
|
|
|
if (viewBox) {
|
|
|
|
viewBox = viewBox.split(' ');
|
|
|
|
var ratio = parseInt(svg.style('width')) / viewBox[2];
|
|
|
|
|
|
|
|
position.left = position.left * ratio;
|
|
|
|
position.top = position.top * ratio;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-03-20 23:43:21 +00:00
|
|
|
|
2013-07-04 03:17:08 +00:00
|
|
|
//Creates new tooltip container, or uses existing one on DOM.
|
|
|
|
function getTooltipContainer(newContent) {
|
|
|
|
var container = document.getElementsByClassName("nvtooltip");
|
|
|
|
if (container.length === 0) {
|
|
|
|
//Create new tooltip div if it doesn't exist on DOM.
|
|
|
|
container = document.createElement('div');
|
|
|
|
container.className = 'nvtooltip ' + (classes ? classes : 'xy-tooltip');
|
|
|
|
var body = document.getElementsByTagName('body')[0]; //All new tooltips are placed directly on <body>
|
|
|
|
body.appendChild(container);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
//Element already exists on DOM, so reuse it.
|
|
|
|
container = container[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
container.innerHTML = newContent;
|
|
|
|
return container;
|
|
|
|
}
|
2013-07-02 19:25:04 +00:00
|
|
|
|
2013-07-06 20:44:06 +00:00
|
|
|
|
2013-07-06 19:28:05 +00:00
|
|
|
|
2013-07-02 18:37:54 +00:00
|
|
|
//Draw the tooltip onto the DOM.
|
2013-07-04 03:17:08 +00:00
|
|
|
function nvtooltip() {
|
2013-07-02 20:07:20 +00:00
|
|
|
if (!enabled) return;
|
2013-07-03 17:43:32 +00:00
|
|
|
if (!dataSeriesExists(data)) return;
|
2013-07-02 20:07:20 +00:00
|
|
|
|
2013-07-02 18:37:54 +00:00
|
|
|
convertViewBoxRatio();
|
2012-05-07 12:53:51 +00:00
|
|
|
|
2013-07-02 18:37:54 +00:00
|
|
|
var left = position.left;
|
|
|
|
var top = (fixedTop != null) ? fixedTop : position.top;
|
2012-03-20 23:43:21 +00:00
|
|
|
|
2013-07-02 18:37:54 +00:00
|
|
|
if (chartContainer) {
|
2013-07-06 21:12:19 +00:00
|
|
|
left += chartContainer.getBoundingClientRect().left + window.pageXOffset;
|
|
|
|
top += chartContainer.getBoundingClientRect().top + window.pageYOffset;
|
2013-07-02 18:37:54 +00:00
|
|
|
}
|
2013-07-02 19:25:04 +00:00
|
|
|
|
2013-07-03 17:43:32 +00:00
|
|
|
if (snapDistance && snapDistance > 0) {
|
|
|
|
top = Math.floor(top/snapDistance) * snapDistance;
|
|
|
|
}
|
|
|
|
|
2013-07-04 03:17:08 +00:00
|
|
|
var container = getTooltipContainer(contentGenerator(data));
|
|
|
|
|
|
|
|
|
|
|
|
nv.tooltip.calcTooltipPosition([left,top], gravity, distance, container);
|
|
|
|
return nvtooltip;
|
2013-07-02 18:37:54 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
nvtooltip.content = function(_) {
|
|
|
|
if (!arguments.length) return content;
|
|
|
|
content = _;
|
|
|
|
return nvtooltip;
|
|
|
|
};
|
|
|
|
|
2013-07-02 19:25:04 +00:00
|
|
|
nvtooltip.contentGenerator = function(_) {
|
|
|
|
if (!arguments.length) return contentGenerator;
|
|
|
|
if (typeof _ === 'function') {
|
|
|
|
contentGenerator = _;
|
|
|
|
}
|
|
|
|
return nvtooltip;
|
|
|
|
};
|
|
|
|
|
|
|
|
nvtooltip.data = function(_) {
|
|
|
|
if (!arguments.length) return data;
|
|
|
|
data = _;
|
|
|
|
return nvtooltip;
|
|
|
|
};
|
|
|
|
|
2013-07-02 18:37:54 +00:00
|
|
|
nvtooltip.gravity = function(_) {
|
|
|
|
if (!arguments.length) return gravity;
|
|
|
|
gravity = _;
|
|
|
|
return nvtooltip;
|
|
|
|
};
|
|
|
|
|
|
|
|
nvtooltip.distance = function(_) {
|
|
|
|
if (!arguments.length) return distance;
|
|
|
|
distance = _;
|
|
|
|
return nvtooltip;
|
|
|
|
};
|
|
|
|
|
2013-07-03 17:43:32 +00:00
|
|
|
nvtooltip.snapDistance = function(_) {
|
|
|
|
if (!arguments.length) return snapDistance;
|
|
|
|
snapDistance = _;
|
|
|
|
return nvtooltip;
|
|
|
|
};
|
|
|
|
|
2013-07-02 18:37:54 +00:00
|
|
|
nvtooltip.classes = function(_) {
|
|
|
|
if (!arguments.length) return classes;
|
|
|
|
classes = _;
|
|
|
|
return nvtooltip;
|
|
|
|
};
|
|
|
|
|
|
|
|
nvtooltip.chartContainer = function(_) {
|
|
|
|
if (!arguments.length) return chartContainer;
|
|
|
|
chartContainer = _;
|
|
|
|
return nvtooltip;
|
|
|
|
};
|
|
|
|
|
|
|
|
nvtooltip.position = function(_) {
|
|
|
|
if (!arguments.length) return position;
|
|
|
|
position.left = (typeof _.left !== 'undefined') ? _.left : position.left;
|
|
|
|
position.top = (typeof _.top !== 'undefined') ? _.top : position.top;
|
|
|
|
return nvtooltip;
|
|
|
|
};
|
|
|
|
|
|
|
|
nvtooltip.fixedTop = function(_) {
|
|
|
|
if (!arguments.length) return fixedTop;
|
|
|
|
fixedTop = _;
|
|
|
|
return nvtooltip;
|
|
|
|
};
|
|
|
|
|
2013-07-02 20:07:20 +00:00
|
|
|
nvtooltip.enabled = function(_) {
|
|
|
|
if (!arguments.length) return enabled;
|
|
|
|
enabled = _;
|
|
|
|
return nvtooltip;
|
|
|
|
};
|
|
|
|
|
|
|
|
nvtooltip.valueFormatter = function(_) {
|
|
|
|
if (!arguments.length) return valueFormatter;
|
|
|
|
if (typeof _ === 'function') {
|
|
|
|
valueFormatter = _;
|
|
|
|
}
|
|
|
|
return nvtooltip;
|
|
|
|
};
|
2013-07-02 18:37:54 +00:00
|
|
|
|
|
|
|
|
|
|
|
return nvtooltip;
|
|
|
|
};
|
2012-03-20 23:43:21 +00:00
|
|
|
|
2013-07-02 18:37:54 +00:00
|
|
|
|
2013-07-04 03:17:08 +00:00
|
|
|
//Original tooltip.show function. Kept for backward compatibility.
|
|
|
|
nv.tooltip.show = function(pos, content, gravity, dist, parentContainer, classes) {
|
|
|
|
|
|
|
|
//Create new tooltip div if it doesn't exist on DOM.
|
|
|
|
var container = document.createElement('div');
|
|
|
|
container.className = 'nvtooltip ' + (classes ? classes : 'xy-tooltip');
|
|
|
|
|
|
|
|
var body = parentContainer;
|
|
|
|
if ( !parentContainer || parentContainer.tagName.match(/g|svg/i)) {
|
|
|
|
//If the parent element is an SVG element, place tooltip in the <body> element.
|
|
|
|
body = document.getElementsByTagName('body')[0];
|
2013-07-03 14:33:48 +00:00
|
|
|
}
|
2013-07-04 03:17:08 +00:00
|
|
|
|
|
|
|
container.style.left = 0;
|
|
|
|
container.style.top = 0;
|
|
|
|
container.style.opacity = 0;
|
2013-07-03 14:33:48 +00:00
|
|
|
container.innerHTML = content;
|
2013-07-04 03:17:08 +00:00
|
|
|
body.appendChild(container);
|
2013-07-03 14:33:48 +00:00
|
|
|
|
|
|
|
nv.tooltip.calcTooltipPosition(pos, gravity, dist, container);
|
|
|
|
};
|
|
|
|
|
2013-07-06 20:44:06 +00:00
|
|
|
nv.tooltip.findFirstNonSVGParent = function(Elem) {
|
|
|
|
while(Elem.tagName.match(/^g|svg$/i) !== null) {
|
|
|
|
Elem = Elem.parentNode;
|
|
|
|
}
|
|
|
|
return Elem;
|
|
|
|
};
|
|
|
|
|
2013-07-06 18:59:19 +00:00
|
|
|
nv.tooltip.findTotalOffsetTop = function ( Elem, initialTop ) {
|
|
|
|
var offsetTop = initialTop;
|
2013-07-06 19:28:05 +00:00
|
|
|
|
2013-07-06 18:59:19 +00:00
|
|
|
|
|
|
|
do {
|
|
|
|
if( !isNaN( Elem.offsetTop ) ) {
|
|
|
|
offsetTop += (Elem.offsetTop);
|
|
|
|
}
|
|
|
|
} while( Elem = Elem.offsetParent );
|
|
|
|
return offsetTop;
|
|
|
|
};
|
|
|
|
|
|
|
|
nv.tooltip.findTotalOffsetLeft = function ( Elem, initialLeft) {
|
|
|
|
var offsetLeft = initialLeft;
|
2013-07-06 19:28:05 +00:00
|
|
|
|
2013-07-06 18:59:19 +00:00
|
|
|
|
|
|
|
do {
|
|
|
|
if( !isNaN( Elem.offsetLeft ) ) {
|
|
|
|
offsetLeft += (Elem.offsetLeft);
|
|
|
|
}
|
|
|
|
} while( Elem = Elem.offsetParent );
|
|
|
|
return offsetLeft;
|
|
|
|
};
|
|
|
|
|
2013-07-03 14:33:48 +00:00
|
|
|
//Global utility function to render a tooltip on the DOM.
|
|
|
|
nv.tooltip.calcTooltipPosition = function(pos, gravity, dist, container) {
|
|
|
|
|
2013-07-02 18:37:54 +00:00
|
|
|
var height = parseInt(container.offsetHeight),
|
|
|
|
width = parseInt(container.offsetWidth),
|
|
|
|
windowWidth = nv.utils.windowSize().width,
|
|
|
|
windowHeight = nv.utils.windowSize().height,
|
2013-07-06 21:12:19 +00:00
|
|
|
scrollTop = window.pageYOffset,
|
|
|
|
scrollLeft = window.pageXOffset,
|
2013-07-02 18:37:54 +00:00
|
|
|
left, top;
|
|
|
|
|
|
|
|
windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16;
|
|
|
|
windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16;
|
|
|
|
|
2013-07-03 14:33:48 +00:00
|
|
|
gravity = gravity || 's';
|
|
|
|
dist = dist || 20;
|
|
|
|
|
2013-07-02 18:37:54 +00:00
|
|
|
var tooltipTop = function ( Elem ) {
|
2013-07-06 19:28:05 +00:00
|
|
|
if (Elem.parentNode.tagName === 'BODY') return top;
|
2013-07-06 18:59:19 +00:00
|
|
|
return nv.tooltip.findTotalOffsetTop(Elem, top);
|
|
|
|
};
|
2012-11-08 23:39:01 +00:00
|
|
|
|
2013-07-02 18:37:54 +00:00
|
|
|
var tooltipLeft = function ( Elem ) {
|
2013-07-06 19:28:05 +00:00
|
|
|
if (Elem.parentNode.tagName === 'BODY') return left;
|
2013-07-06 18:59:19 +00:00
|
|
|
return nv.tooltip.findTotalOffsetLeft(Elem,left);
|
|
|
|
};
|
2013-07-02 18:37:54 +00:00
|
|
|
|
|
|
|
switch (gravity) {
|
|
|
|
case 'e':
|
|
|
|
left = pos[0] - width - dist;
|
|
|
|
top = pos[1] - (height / 2);
|
|
|
|
var tLeft = tooltipLeft(container);
|
|
|
|
var tTop = tooltipTop(container);
|
|
|
|
if (tLeft < scrollLeft) left = pos[0] + dist > scrollLeft ? pos[0] + dist : scrollLeft - tLeft + left;
|
|
|
|
if (tTop < scrollTop) top = scrollTop - tTop + top;
|
|
|
|
if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
|
|
|
|
break;
|
|
|
|
case 'w':
|
|
|
|
left = pos[0] + dist;
|
|
|
|
top = pos[1] - (height / 2);
|
2013-07-03 17:43:32 +00:00
|
|
|
var tLeft = tooltipLeft(container);
|
|
|
|
var tTop = tooltipTop(container);
|
2013-07-02 18:37:54 +00:00
|
|
|
if (tLeft + width > windowWidth) left = pos[0] - width - dist;
|
|
|
|
if (tTop < scrollTop) top = scrollTop + 5;
|
2013-07-05 01:27:58 +00:00
|
|
|
if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
|
2013-07-02 18:37:54 +00:00
|
|
|
break;
|
|
|
|
case 'n':
|
|
|
|
left = pos[0] - (width / 2) - 5;
|
|
|
|
top = pos[1] + dist;
|
|
|
|
var tLeft = tooltipLeft(container);
|
|
|
|
var tTop = tooltipTop(container);
|
|
|
|
if (tLeft < scrollLeft) left = scrollLeft + 5;
|
|
|
|
if (tLeft + width > windowWidth) left = left - width/2 + 5;
|
|
|
|
if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
|
|
|
|
break;
|
|
|
|
case 's':
|
|
|
|
left = pos[0] - (width / 2);
|
|
|
|
top = pos[1] - height - dist;
|
|
|
|
var tLeft = tooltipLeft(container);
|
|
|
|
var tTop = tooltipTop(container);
|
|
|
|
if (tLeft < scrollLeft) left = scrollLeft + 5;
|
|
|
|
if (tLeft + width > windowWidth) left = left - width/2 + 5;
|
|
|
|
if (scrollTop > tTop) top = scrollTop;
|
|
|
|
break;
|
2012-11-08 23:39:01 +00:00
|
|
|
}
|
2012-03-20 23:43:21 +00:00
|
|
|
|
|
|
|
|
2013-07-02 18:37:54 +00:00
|
|
|
container.style.left = left+'px';
|
|
|
|
container.style.top = top+'px';
|
|
|
|
container.style.opacity = 1;
|
|
|
|
container.style.position = 'absolute'; //fix scroll bar issue
|
|
|
|
container.style.pointerEvents = 'none'; //fix scroll bar issue
|
2012-05-07 12:53:51 +00:00
|
|
|
|
2013-07-02 18:37:54 +00:00
|
|
|
return container;
|
|
|
|
};
|
2012-03-20 23:43:21 +00:00
|
|
|
|
2013-07-02 18:37:54 +00:00
|
|
|
//Global utility function to remove tooltips from the DOM.
|
|
|
|
nv.tooltip.cleanup = function() {
|
2012-03-20 23:43:21 +00:00
|
|
|
|
2013-07-02 18:37:54 +00:00
|
|
|
// Find the tooltips, mark them for removal by this class (so others cleanups won't find it)
|
|
|
|
var tooltips = document.getElementsByClassName('nvtooltip');
|
|
|
|
var purging = [];
|
|
|
|
while(tooltips.length) {
|
|
|
|
purging.push(tooltips[0]);
|
|
|
|
tooltips[0].style.transitionDelay = '0 !important';
|
|
|
|
tooltips[0].style.opacity = 0;
|
|
|
|
tooltips[0].className = 'nvtooltip-pending-removal';
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(function() {
|
2012-03-20 23:43:21 +00:00
|
|
|
|
2013-07-02 18:37:54 +00:00
|
|
|
while (purging.length) {
|
|
|
|
var removeMe = purging.pop();
|
|
|
|
removeMe.parentNode.removeChild(removeMe);
|
|
|
|
}
|
|
|
|
}, 500);
|
|
|
|
};
|
2012-05-07 12:53:51 +00:00
|
|
|
|
|
|
|
})();
|