Refactor project structure
This commit is contained in:
490
web/js/nvd3/src/tooltip.js
Normal file
490
web/js/nvd3/src/tooltip.js
Normal file
@@ -0,0 +1,490 @@
|
||||
/* Tooltip rendering model for nvd3 charts.
|
||||
window.nv.models.tooltip is the updated,new way to render tooltips.
|
||||
|
||||
window.nv.tooltip.show is the old tooltip code.
|
||||
window.nv.tooltip.* also has various helper methods.
|
||||
*/
|
||||
(function() {
|
||||
"use strict";
|
||||
window.nv.tooltip = {};
|
||||
|
||||
/* Model which can be instantiated to handle tooltip rendering.
|
||||
Example usage:
|
||||
var tip = nv.models.tooltip().gravity('w').distance(23)
|
||||
.data(myDataObject);
|
||||
|
||||
tip(); //just invoke the returned function to render tooltip.
|
||||
*/
|
||||
window.nv.models.tooltip = function() {
|
||||
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",
|
||||
series: [
|
||||
{
|
||||
key: "Series 1",
|
||||
value: "Value 1",
|
||||
color: "#000"
|
||||
},
|
||||
{
|
||||
key: "Series 2",
|
||||
value: "Value 2",
|
||||
color: "#00f"
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
*/
|
||||
, 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)
|
||||
, 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.
|
||||
, chartContainer = null //Parent DIV, of the SVG Container that holds the chart.
|
||||
, tooltipElem = null //actual DOM element representing the tooltip.
|
||||
, position = {left: null, top: null} //Relative position of the tooltip inside chartContainer.
|
||||
, enabled = true //True -> tooltips are rendered. False -> don't render tooltips.
|
||||
//Generates a unique id when you create a new tooltip() object
|
||||
, id = "nvtooltip-" + Math.floor(Math.random() * 100000)
|
||||
;
|
||||
|
||||
//CSS class to specify whether element should not have mouse events.
|
||||
var nvPointerEventsClass = "nv-pointer-events-none";
|
||||
|
||||
//Format function for the tooltip values column
|
||||
var valueFormatter = function(d,i) {
|
||||
return d;
|
||||
};
|
||||
|
||||
//Format function for the tooltip header value.
|
||||
var headerFormatter = function(d) {
|
||||
return d;
|
||||
};
|
||||
|
||||
//By default, the tooltip model renders a beautiful table inside a DIV.
|
||||
//You can override this function if a custom tooltip is desired.
|
||||
var contentGenerator = function(d) {
|
||||
if (content != null) return content;
|
||||
|
||||
if (d == null) return '';
|
||||
|
||||
var table = d3.select(document.createElement("table"));
|
||||
var theadEnter = table.selectAll("thead")
|
||||
.data([d])
|
||||
.enter().append("thead");
|
||||
theadEnter.append("tr")
|
||||
.append("td")
|
||||
.attr("colspan",3)
|
||||
.append("strong")
|
||||
.classed("x-value",true)
|
||||
.html(headerFormatter(d.value));
|
||||
|
||||
var tbodyEnter = table.selectAll("tbody")
|
||||
.data([d])
|
||||
.enter().append("tbody");
|
||||
var trowEnter = tbodyEnter.selectAll("tr")
|
||||
.data(function(p) { return p.series})
|
||||
.enter()
|
||||
.append("tr")
|
||||
.classed("highlight", function(p) { return p.highlight})
|
||||
;
|
||||
|
||||
trowEnter.append("td")
|
||||
.classed("legend-color-guide",true)
|
||||
.append("div")
|
||||
.style("background-color", function(p) { return p.color});
|
||||
trowEnter.append("td")
|
||||
.classed("key",true)
|
||||
.html(function(p) {return p.key});
|
||||
trowEnter.append("td")
|
||||
.classed("value",true)
|
||||
.html(function(p,i) { return valueFormatter(p.value,i) });
|
||||
|
||||
|
||||
trowEnter.selectAll("td").each(function(p) {
|
||||
if (p.highlight) {
|
||||
var opacityScale = d3.scale.linear().domain([0,1]).range(["#fff",p.color]);
|
||||
var opacity = 0.6;
|
||||
d3.select(this)
|
||||
.style("border-bottom-color", opacityScale(opacity))
|
||||
.style("border-top-color", opacityScale(opacity))
|
||||
;
|
||||
}
|
||||
});
|
||||
|
||||
var html = table.node().outerHTML;
|
||||
if (d.footer !== undefined)
|
||||
html += "<div class='footer'>" + d.footer + "</div>";
|
||||
return html;
|
||||
|
||||
};
|
||||
|
||||
var dataSeriesExists = function(d) {
|
||||
if (d && d.series && d.series.length > 0) return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
//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);
|
||||
if (svg.node().tagName !== "svg") {
|
||||
svg = svg.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Creates new tooltip container, or uses existing one on DOM.
|
||||
function getTooltipContainer(newContent) {
|
||||
var body;
|
||||
if (chartContainer)
|
||||
body = d3.select(chartContainer);
|
||||
else
|
||||
body = d3.select("body");
|
||||
|
||||
var container = body.select(".nvtooltip");
|
||||
if (container.node() === null) {
|
||||
//Create new tooltip div if it doesn't exist on DOM.
|
||||
container = body.append("div")
|
||||
.attr("class", "nvtooltip " + (classes? classes: "xy-tooltip"))
|
||||
.attr("id",id)
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
container.node().innerHTML = newContent;
|
||||
container.style("top",0).style("left",0).style("opacity",0);
|
||||
container.selectAll("div, table, td, tr").classed(nvPointerEventsClass,true)
|
||||
container.classed(nvPointerEventsClass,true);
|
||||
return container.node();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Draw the tooltip onto the DOM.
|
||||
function nvtooltip() {
|
||||
if (!enabled) return;
|
||||
if (!dataSeriesExists(data)) return;
|
||||
|
||||
convertViewBoxRatio();
|
||||
|
||||
var left = position.left;
|
||||
var top = (fixedTop != null) ? fixedTop : position.top;
|
||||
var container = getTooltipContainer(contentGenerator(data));
|
||||
tooltipElem = container;
|
||||
if (chartContainer) {
|
||||
var svgComp = chartContainer.getElementsByTagName("svg")[0];
|
||||
var boundRect = (svgComp) ? svgComp.getBoundingClientRect() : chartContainer.getBoundingClientRect();
|
||||
var svgOffset = {left:0,top:0};
|
||||
if (svgComp) {
|
||||
var svgBound = svgComp.getBoundingClientRect();
|
||||
var chartBound = chartContainer.getBoundingClientRect();
|
||||
var svgBoundTop = svgBound.top;
|
||||
|
||||
//Defensive code. Sometimes, svgBoundTop can be a really negative
|
||||
// number, like -134254. That's a bug.
|
||||
// If such a number is found, use zero instead. FireFox bug only
|
||||
if (svgBoundTop < 0) {
|
||||
var containerBound = chartContainer.getBoundingClientRect();
|
||||
svgBoundTop = (Math.abs(svgBoundTop) > containerBound.height) ? 0 : svgBoundTop;
|
||||
}
|
||||
svgOffset.top = Math.abs(svgBoundTop - chartBound.top);
|
||||
svgOffset.left = Math.abs(svgBound.left - chartBound.left);
|
||||
}
|
||||
//If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets.
|
||||
//You need to also add any offset between the <svg> element and its containing <div>
|
||||
//Finally, add any offset of the containing <div> on the whole page.
|
||||
left += chartContainer.offsetLeft + svgOffset.left - 2*chartContainer.scrollLeft;
|
||||
top += chartContainer.offsetTop + svgOffset.top - 2*chartContainer.scrollTop;
|
||||
}
|
||||
|
||||
if (snapDistance && snapDistance > 0) {
|
||||
top = Math.floor(top/snapDistance) * snapDistance;
|
||||
}
|
||||
|
||||
nv.tooltip.calcTooltipPosition([left,top], gravity, distance, container);
|
||||
return nvtooltip;
|
||||
};
|
||||
|
||||
nvtooltip.nvPointerEventsClass = nvPointerEventsClass;
|
||||
|
||||
nvtooltip.content = function(_) {
|
||||
if (!arguments.length) return content;
|
||||
content = _;
|
||||
return nvtooltip;
|
||||
};
|
||||
|
||||
//Returns tooltipElem...not able to set it.
|
||||
nvtooltip.tooltipElem = function() {
|
||||
return tooltipElem;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
nvtooltip.gravity = function(_) {
|
||||
if (!arguments.length) return gravity;
|
||||
gravity = _;
|
||||
return nvtooltip;
|
||||
};
|
||||
|
||||
nvtooltip.distance = function(_) {
|
||||
if (!arguments.length) return distance;
|
||||
distance = _;
|
||||
return nvtooltip;
|
||||
};
|
||||
|
||||
nvtooltip.snapDistance = function(_) {
|
||||
if (!arguments.length) return snapDistance;
|
||||
snapDistance = _;
|
||||
return nvtooltip;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
nvtooltip.headerFormatter = function(_) {
|
||||
if (!arguments.length) return headerFormatter;
|
||||
if (typeof _ === 'function') {
|
||||
headerFormatter = _;
|
||||
}
|
||||
return nvtooltip;
|
||||
};
|
||||
|
||||
//id() is a read-only function. You can't use it to set the id.
|
||||
nvtooltip.id = function() {
|
||||
return id;
|
||||
};
|
||||
|
||||
|
||||
return nvtooltip;
|
||||
};
|
||||
|
||||
|
||||
//Original tooltip.show function. Kept for backward compatibility.
|
||||
// pos = [left,top]
|
||||
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];
|
||||
}
|
||||
|
||||
container.style.left = 0;
|
||||
container.style.top = 0;
|
||||
container.style.opacity = 0;
|
||||
container.innerHTML = content;
|
||||
body.appendChild(container);
|
||||
|
||||
//If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets.
|
||||
if (parentContainer) {
|
||||
pos[0] = pos[0] - parentContainer.scrollLeft;
|
||||
pos[1] = pos[1] - parentContainer.scrollTop;
|
||||
}
|
||||
nv.tooltip.calcTooltipPosition(pos, gravity, dist, container);
|
||||
};
|
||||
|
||||
//Looks up the ancestry of a DOM element, and returns the first NON-svg node.
|
||||
nv.tooltip.findFirstNonSVGParent = function(Elem) {
|
||||
while(Elem.tagName.match(/^g|svg$/i) !== null) {
|
||||
Elem = Elem.parentNode;
|
||||
}
|
||||
return Elem;
|
||||
};
|
||||
|
||||
//Finds the total offsetTop of a given DOM element.
|
||||
//Looks up the entire ancestry of an element, up to the first relatively positioned element.
|
||||
nv.tooltip.findTotalOffsetTop = function ( Elem, initialTop ) {
|
||||
var offsetTop = initialTop;
|
||||
|
||||
do {
|
||||
if( !isNaN( Elem.offsetTop ) ) {
|
||||
offsetTop += (Elem.offsetTop);
|
||||
}
|
||||
} while( Elem = Elem.offsetParent );
|
||||
return offsetTop;
|
||||
};
|
||||
|
||||
//Finds the total offsetLeft of a given DOM element.
|
||||
//Looks up the entire ancestry of an element, up to the first relatively positioned element.
|
||||
nv.tooltip.findTotalOffsetLeft = function ( Elem, initialLeft) {
|
||||
var offsetLeft = initialLeft;
|
||||
|
||||
do {
|
||||
if( !isNaN( Elem.offsetLeft ) ) {
|
||||
offsetLeft += (Elem.offsetLeft);
|
||||
}
|
||||
} while( Elem = Elem.offsetParent );
|
||||
return offsetLeft;
|
||||
};
|
||||
|
||||
//Global utility function to render a tooltip on the DOM.
|
||||
//pos = [left,top] coordinates of where to place the tooltip, relative to the SVG chart container.
|
||||
//gravity = how to orient the tooltip
|
||||
//dist = how far away from the mouse to place tooltip
|
||||
//container = tooltip DIV
|
||||
nv.tooltip.calcTooltipPosition = function(pos, gravity, dist, container) {
|
||||
|
||||
var height = parseInt(container.offsetHeight),
|
||||
width = parseInt(container.offsetWidth),
|
||||
windowWidth = nv.utils.windowSize().width,
|
||||
windowHeight = nv.utils.windowSize().height,
|
||||
scrollTop = window.pageYOffset,
|
||||
scrollLeft = window.pageXOffset,
|
||||
left, top;
|
||||
|
||||
windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16;
|
||||
windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16;
|
||||
|
||||
gravity = gravity || 's';
|
||||
dist = dist || 20;
|
||||
|
||||
var tooltipTop = function ( Elem ) {
|
||||
return nv.tooltip.findTotalOffsetTop(Elem, top);
|
||||
};
|
||||
|
||||
var tooltipLeft = function ( Elem ) {
|
||||
return nv.tooltip.findTotalOffsetLeft(Elem,left);
|
||||
};
|
||||
|
||||
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);
|
||||
var tLeft = tooltipLeft(container);
|
||||
var tTop = tooltipTop(container);
|
||||
if (tLeft + width > windowWidth) left = pos[0] - width - dist;
|
||||
if (tTop < scrollTop) top = scrollTop + 5;
|
||||
if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
|
||||
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;
|
||||
case 'none':
|
||||
left = pos[0];
|
||||
top = pos[1] - dist;
|
||||
var tLeft = tooltipLeft(container);
|
||||
var tTop = tooltipTop(container);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
container.style.left = left+'px';
|
||||
container.style.top = top+'px';
|
||||
container.style.opacity = 1;
|
||||
container.style.position = 'absolute';
|
||||
|
||||
return container;
|
||||
};
|
||||
|
||||
//Global utility function to remove tooltips from the DOM.
|
||||
nv.tooltip.cleanup = function() {
|
||||
|
||||
// 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() {
|
||||
|
||||
while (purging.length) {
|
||||
var removeMe = purging.pop();
|
||||
removeMe.parentNode.removeChild(removeMe);
|
||||
}
|
||||
}, 500);
|
||||
};
|
||||
|
||||
})();
|
||||
Reference in New Issue
Block a user