// o---------------------------------------------------------------------------------o // | This file is part of the RGraph package - you can learn more at: | // | | // | https://www.rgraph.net/license.html | // | | // | RGraph is dual-licensed under the Open Source GPL license. That means that it's | // | free to use and there are no restrictions on what you can use RGraph for! | // | If the GPL license does not suit you however, then there's an inexpensive | // | commercial license option available. See the URL above for more details. | // o---------------------------------------------------------------------------------o RGraph = window.RGraph || {isrgraph:true,isRGraph:true,rgraph:true}; RGraph.SVG = RGraph.SVG || {}; // Module pattern (function (win, doc, undefined) { // // This is used in two functions, hence it's here // RGraph.SVG.tooltips = {}; RGraph.SVG.tooltips.css = RGraph.SVG.tooltips.style = { display: 'inline-block', position: 'absolute', padding: '6px', lineHeight: 'initial', fontFamily: 'Arial', fontSize: '12pt', fontWeight: 'normal', textAlign: 'center', left: 0, top: 0, backgroundColor: 'black', color: 'white', visibility: 'visible', zIndex: 3, borderRadius: '5px', boxShadow: 'rgba(96,96,96,0.5) 0 0 5px', transition: 'left ease-out .25s, top ease-out .25s' }; // // Shows a tooltip // // @param obj The chart object // @param opt The options // RGraph.SVG.tooltip = function (opt) { var obj = opt.object; var properties = obj.properties; // Fire the beforetooltip event RGraph.SVG.fireCustomEvent(obj, 'onbeforetooltip'); if (!opt.text || typeof opt.text === 'undefined' || RGraph.SVG.trim(opt.text).length === 0) { if (typeof properties.tooltipsOverride !== 'function') { return; } } // // chart.tooltip.override allows you to totally take control of rendering the tooltip yourself // if (RGraph.SVG.isFunction(properties.tooltipsOverride)) { // Add the body click handler that clears the highlight if necessary // document.body.addEventListener('mouseup', function (e) { obj.removeHighlight(); }, false); return (properties.tooltipsOverride)(obj, opt); } // Create the tooltip DIV element if (!RGraph.SVG.REG.get('tooltip')) { var tooltipObj = document.createElement('DIV'); tooltipObj.className = properties.tooltipsCssClass; // Add the default CSS to the tooltip for (var i in RGraph.SVG.tooltips.style) { if (typeof i === 'string') { tooltipObj.style[i] = substitute(RGraph.SVG.tooltips.style[i]); } } for (var i in RGraph.SVG.tooltips.css) { if (typeof i === 'string') { tooltipObj.style[i] = substitute(RGraph.SVG.tooltips.css[i]); } } // // If the tooltipsCss property is populated the add those values // to the tooltip // if (!RGraph.SVG.isNull(obj.properties.tooltipsCss)) { for (var i in obj.properties.tooltipsCss) { if (typeof i === 'string') { tooltipObj.style[i] = substitute(obj.properties.tooltipsCss[i]); } } } // Reuse an existing tooltip } else { var tooltipObj = RGraph.SVG.REG.get('tooltip'); tooltipObj.__object__.removeHighlight(); // This prevents the object from continuously growing tooltipObj.style.width = ''; } if (RGraph.SVG.REG.get('tooltip-lasty')) { tooltipObj.style.left = RGraph.SVG.REG.get('tooltip-lastx') + 'px'; tooltipObj.style.top = RGraph.SVG.REG.get('tooltip-lasty') + 'px'; } /////////////////////////////////////// // Do tooltip text substitution here // /////////////////////////////////////// function substitute (original) { // Ensure that it's a string first original = String(original); if (typeof opt.object.tooltipSubstitutions !== 'function') { return original; } // Get hold of the indexes from the sequentialIndex that we have. // if (typeof opt.object.tooltipSubstitutions === 'function') { var specific = opt.object.tooltipSubstitutions({ index: opt.sequentialIndex }); } // This allows for escaping the percent var text = original.replace(/%%/g, '___--PERCENT--___') // // Draws the key in the tooltip // var keyReplacementFunction = function () { if (!specific.values) { return; } // // Allow the user to specify the key colors // var colors = properties.tooltipsFormattedKeyColors ? properties.tooltipsFormattedKeyColors : properties.colors; // Build up the HTML table that becomes the key for (var i=0,str=[]; iMl' + '' + label + '' + ' ' + value + ''; // Now that styles can't be applied inline // (due to the CSP header) then apply them with // JavaScript after a small delay. (function (index, color, borderRadius) { setTimeout(function () { // Align the label left var node = document.getElementById('RGraph_tooltipsFormattedKeyLabel_' + index); if (node) { node.style.textAlign = 'left' } // Add some styles to the color blob var colorBlob = document.getElementById('RGraph_tooltipsFormattedKeyColor_' + index); if (colorBlob) { colorBlob.style.textAlign = 'left'; colorBlob.style.backgroundColor = color; colorBlob.style.color = 'transparent'; colorBlob.style.pointerEvents = 'none'; colorBlob.style.borderRadius = borderRadius; // Add user specified styles from the // tooltipsFormattedKeyColorsCss property for (var property in tooltipsFormattedKeyColorsCss) { if (typeof property === 'string') { colorBlob.style[property] = tooltipsFormattedKeyColorsCss[property]; } } } // Set the default color for the table to inherit var node = document.getElementById('RGraph_tooltipsFormattedKey_table') if (node) { node.style.color = 'inherit'; } }, 1); })(i, color, borderRadius); } str = str.join(''); // Add the key to the tooltip text - replacing the placeholder text = text.replace('%{key}', '' + str + '
'); }; keyReplacementFunction(); // Replace the index of the tooltip text = text.replace(/%{index}/g, specific.index); // Replace the dataset/group of the tooltip text = text.replace(/%{dataset2}/g, specific.dataset2); // Used by the Bipolar text = text.replace(/%{dataset}/g, specific.dataset); text = text.replace(/%{group2}/g, specific.dataset2); // Used by the Bipolar text = text.replace(/%{group}/g, specific.dataset); // Replace the sequentialIndex of the tooltip text = text.replace(/%{sequential_index}/g, specific.sequentialIndex); text = text.replace(/%{seq}/g, specific.sequentialIndex); //Do %{list} sunstitution if (text.indexOf('%{list}') !== -1) { (function () { if (properties.tooltipsFormattedListType === 'unordered') properties.tooltipsFormattedListType = 'ul'; if (properties.tooltipsFormattedListType === ''; // Add the list to the tooltip text = text.replace(/%{list}/, str); })(); } // Do table substitution (ie %{table} ) if (text.indexOf('%{table}') !== -1) { (function () { var str = ''; // Add the headers if they're defined if (properties.tooltipsFormattedTableHeaders && properties.tooltipsFormattedTableHeaders.length) { str += ''; for (var i=0; i'; } str += ''; } // Add each row of data if (typeof properties.tooltipsFormattedTableData === 'object' && !RGraph.SVG.isNull(properties.tooltipsFormattedTableData)) { str += ''; for (var i=0; i'; } str += ''; } str += ''; } // Close the table str += '
'; text = text.replace(/%{table}/g, str); })(); } // Do property substitution when there's an index to // the property var reg = /%{pr?o?p?(?:erty)?:([_a-z0-9]+)\[([0-9]+)\]}/i; while (text.match(reg)) { var property = RegExp.$1; var index = parseInt(RegExp.$2); if (opt.object.properties[property]) { text = text.replace( reg, opt.object.properties[property][index] || '' ); // Get rid of the text } else { text = text.replace(reg,''); } RegExp.lastIndex = null; } // Third, replace this: %{property:xxx} (but there's no index to the property) while (text.match(/%{property:([_a-z0-9]+)}/i)) { var str = '%{property:' + RegExp.$1 + '}'; text = text.replace(str, opt.object.properties[RegExp.$1]); } // Fourth, replace this: %%prop:xxx%% while (text.match(/%{prop:([_a-z0-9]+)}/i)) { var str = '%{prop:' + RegExp.$1 + '}'; text = text.replace(str, opt.object.properties[RegExp.$1]); } // Fourth, replace this: %%p:xxx%% while (text.match(/%{p:([_a-z0-9]+)}/i)) { var str = '%{p:' + RegExp.$1 + '}'; text = text.replace(str, opt.object.properties[RegExp.$1]); } // THIS IS ONLY FOR A NON-EQUI-ANGULAR ROSE CHART // // Replace this: %{value2} if (opt.object.type === 'rose' && opt.object.properties.variant === 'non-equi-angular') { while (text.match(/%{value2}/i)) { text = text.replace('%{value2}', specific.value2); } } // Fifth and sixth, replace this: %{value} and this: %{value_formatted} while (text.match(/%{value(?:_formatted)?}/i)) { var value = specific.value; // // Special case for the Waterfall chart and mid totals // if (opt.object.type === 'waterfall' && specific.index != opt.object.data.length - 1 && RGraph.SVG.isNull(value)) { for (var i=0,tot=0; iMj'); // Replace CR and LF with a space text = text.replace(/\|CR\|/, ' '); text = text.replace(/\|LF\|/, ' '); // Replace line returns with br tags text = text.replace(/\r?\n/g, '
'); text = text.replace(/___--PERCENT--___/g, '%') return text.toString(); } // Save the original text on the tooltip tooltipObj.__original_text__ = opt.text; opt.text = substitute(opt.text); // Add the pointer if requested. The background color is updated to match the // tooltip a further down. if (opt.object.properties.tooltipsPointer) { opt.text += '
'; } tooltipObj.innerHTML = opt.text; tooltipObj.__text__ = opt.text; // This is set because the innerHTML can change when it's set tooltipObj.id = '__rgraph_tooltip_' + obj.id + '_' + obj.uid + '_'+ opt.index; tooltipObj.__event__ = properties.tooltipsEvent || 'click'; tooltipObj.__object__ = obj; // Add the index if (typeof opt.index === 'number') { tooltipObj.__index__ = opt.index; } // Add the dataset if (typeof opt.dataset === 'number') { tooltipObj.__dataset__ = opt.dataset; } // Add the group if (typeof opt.group === 'number' || RGraph.SVG.isNull(opt.group)) { tooltipObj.__group__ = opt.group; } // Add the sequentialIndex if (typeof opt.sequentialIndex === 'number') { tooltipObj.__sequentialIndex__ = opt.sequentialIndex; } // Add the tooltip to the document document.body.appendChild(tooltipObj); // Set styles on the pointer. It's done this way // (not adding the style to the HTML above) to // prevent an error bein thrown should a // Content-Security-Policy header using style-src // be in place var pointerObj = document.getElementById('RGraph_tooltipsPointer_' + opt.object.id + ''); if (pointerObj) { var styles = window.getComputedStyle(tooltipObj, false); pointerObj.style.backgroundColor = styles.backgroundColor; pointerObj.style.color = 'transparent'; pointerObj.style.position = 'absolute'; pointerObj.style.bottom = -5 + (RGraph.SVG.isNumber(obj.properties.tooltipsPointerOffsety) ? obj.properties.tooltipsPointerOffsety : 0) + 'px'; pointerObj.style.left = 'calc(50% + ' + (RGraph.SVG.isNumber(obj.properties.tooltipsPointerOffsetx) ? obj.properties.tooltipsPointerOffsetx : 0) + 'px)'; pointerObj.style.transform = 'translateX(-50%) rotate(45deg)'; pointerObj.style.width = '10px'; pointerObj.style.height = '10px'; } var width = tooltipObj.offsetWidth, height = tooltipObj.offsetHeight; // Set these properties to 0 (ie an integer) in case chart libraries are missing // default values for them obj.properties.tooltipsOffsetx = obj.properties.tooltipsOffsetx || 0; obj.properties.tooltipsOffsety = obj.properties.tooltipsOffsety || 0; // Move the tooltip into position tooltipObj.style.left = opt.event.pageX - (width / 2) + obj.properties.tooltipsOffsetx + 'px'; // Prevent the top of the tooltip from being placed off the top of the page var y = opt.event.pageY - height - 15; if (y < 0) { y = 5; } tooltipObj.style.top = y + obj.properties.tooltipsOffsety + 'px'; // // Set the width on the tooltip so it doesn't resize if the window is resized // tooltipObj.style.width = width + 'px'; // // Now that the tooltip pointer has been added, determine the background-color and update // the color of the pointer if (opt.object.properties.tooltipsPointer) { var styles = window.getComputedStyle(tooltipObj, false); var pointer = document.getElementById('RGraph_tooltipsPointer_' + opt.object.id + ''); pointer.style.backgroundColor = styles.backgroundColor; // Add the pointer to the tooltip as a property tooltipObj.__pointer__ = pointer; // Facilitate the property that allows CSS to be added to // the tooltip key color blob var tooltipsPointerCss = ''; if (opt.object.properties.tooltipsPointerCss) { var pointerDiv = document.getElementById('RGraph_tooltipsPointer_' + opt.object.id + ''); for(property in opt.object.properties.tooltipsPointerCss) { if (typeof property === 'string') { pointerDiv.style[property] = opt.object.properties.tooltipsPointerCss[property]; } } } } // Fade the tooltip in if the tooltip is the first view //if (!RGraph.SVG.REG.get('tooltip-lastx')) { // for (var i=0; i<=30; ++i) { // (function (idx) // { // setTimeout(function () // { // tooltipObj.style.opacity = (idx / 30) * 1; // }, (idx / 30) * 200); // })(i); // } //} // If the left is less than zero - set it to 5 if (parseFloat(tooltipObj.style.left) <= 5) { tooltipObj.style.left = 5 + obj.properties.tooltipsOffsetx + 'px'; } // If the tooltip goes over the right hand edge then // adjust the positioning if (parseFloat(tooltipObj.style.left) + parseFloat(tooltipObj.style.width) > window.innerWidth) { tooltipObj.style.left = '' tooltipObj.style.right = 5 + obj.properties.tooltipsOffsety + 'px' } // // Allow for static positioning. // if (opt.object.properties.tooltipsPositionStatic && RGraph.SVG.isFunction(opt.object.positionTooltipStatic)) { opt.object.positionTooltipStatic({ object: opt.object, event: opt.event, tooltip: tooltipObj, index: tooltipObj.__sequentialIndex__ }); // Allow for offsetting the position tooltipObj.style.left = parseFloat(tooltipObj.style.left) + obj.properties.tooltipsOffsetx + 'px'; tooltipObj.style.top = parseFloat(tooltipObj.style.top) + obj.properties.tooltipsOffsety + 'px'; } // // Move the tooltip and its pointer ifthey're off-screen LHS // if (parseInt(tooltipObj.style.left) < 0) { var left = parseInt(tooltipObj.style.left); var width = parseInt(tooltipObj.style.width) left = left + (width * 0.1 * 4); tooltipObj.style.left = left + 'px'; var pointer = document.getElementById('RGraph_tooltipsPointer_' + opt.object.id + ''); if (pointer) { (function (pointer) { setTimeout(function () { pointer.style.left = 'calc(10% + 5px)'; }, 1); })(pointer); } // // Move the tooltip and its pointer ifthey're off-screen RHS // } else if ( (parseInt(tooltipObj.style.left) + parseInt(tooltipObj.offsetWidth)) > document.body.offsetWidth) { var left = parseInt(tooltipObj.style.left); var width = parseInt(tooltipObj.style.width) left = left - (width * 0.1 * 4); tooltipObj.style.left = left + 'px'; var pointer = document.getElementById('RGraph_tooltipsPointer_' + opt.object.id + ''); (function (pointer) { setTimeout(function () { if (pointer) { pointer.style.left = 'calc(90% - 5px)'; } }, 1); })(pointer); } // If the canvas has fixed positioning then set the tooltip position to // fixed too if (RGraph.SVG.isFixed(obj.svg)) { var scrollTop = window.scrollY || document.documentElement.scrollTop; tooltipObj.style.position = 'fixed'; tooltipObj.style.top = opt.event.pageY - scrollTop - height - 10 + obj.properties.tooltipsOffsety + 'px'; } // Cancel the mousedown event tooltipObj.onmousedown = function (e) { e.stopPropagation(); }; // Cancel the mouseup event tooltipObj.onmouseup = function (e) { e.stopPropagation(); }; // Cancel the click event tooltipObj.onclick = function (e) { if (e.button == 0) { e.stopPropagation(); } }; // Add the body click handler that clears the tooltip document.body.addEventListener('mouseup', function (e) { RGraph.SVG.hideTooltip(); }, false); // // Keep a reference to the tooltip in the registry // RGraph.SVG.REG.set('tooltip', tooltipObj); RGraph.SVG.REG.set('tooltip-lastx', parseFloat(tooltipObj.style.left)); RGraph.SVG.REG.set('tooltip-lasty', parseFloat(tooltipObj.style.top)); // // Fire the tooltip event // RGraph.SVG.fireCustomEvent(obj, 'ontooltip'); }; // End module pattern })(window, document);