// 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}; // Module pattern (function (win, doc, undefined) { var ua = navigator.userAgent; // // This is used in two functions, hence it's here // RGraph.tooltips = {}; RGraph.tooltips.style = {}; RGraph.tooltips.styleDefaults = { display: 'inline-block', position: 'absolute', padding: '6px', fontFamily: 'Arial', fontSize: '10pt', 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', opacity: 0, lineHeight: RGraph.ISIE ? 'normal' : 'initial' }; // // Shows a tooltip next to the mouse pointer // // @param object object The canvas element object // @param string text The tooltip text // @param int x The X position that the tooltip should appear at. Combined with the canvases offsetLeft // gives the absolute X position // @param int y The Y position the tooltip should appear at. Combined with the canvases offsetTop // gives the absolute Y position // @param int index The index of the tooltip in the graph objects tooltip array // @param object event The event object // RGraph.tooltip = function () { var args = RGraph.getArgs(arguments, 'object,text,x,y,index,event'); // Set the CSS transition effect on the tooltips default // object if the effect is set to slide if (args.object.properties.tooltipsEffect === 'slide') { RGraph.tooltips.styleDefaults.transition = 'left ease-out .25s, top ease-out .25s' } if (RGraph.SHOW_TOOLTIP_TIMER) { clearTimeout(RGraph.SHOW_TOOLTIP_TIMER); } if (RGraph.trim(args.text).length === 0) { return; } // // Fire the beforetooltip event // RGraph.fireCustomEvent(args.object, 'onbeforetooltip'); // // tooltipOverride allows you to totally take control of rendering the tooltip yourself // if (typeof args.object.get('tooltipsOverride') == 'function') { return args.object.get('tooltipsOverride')( args.object, args.text, args.x, args.y, args.index ); } // // Save the X/Y coords // var originalX = args.x; var originalY = args.y; // // This facilitates the "id:xxx" format // args.text = RGraph.getTooltipTextFromDIV(args.text); // // First clear any exising timers // var timers = RGraph.Registry.get('tooltip.timers'); if (timers && timers.length) { for (i=0; iMl' + '{1}'.format(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 () { var obj = document.getElementById('RGraph_tooltipsFormattedKeyLabel_' + index); // Align the label left if (obj && obj.style) { obj.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 properties.tooltipsFormattedKeyColorsCss) { if (typeof property === 'string') { colorBlob.style[property] = properties.tooltipsFormattedKeyColorsCss[property]; } } }, 5); })(i, color, borderRadius); } str = str.join(''); // Add the key to the tooltip text - replacing the placeholder text = text.replace('%{key}', '' + str + '
'); setTimeout(function () { var obj = document.getElementById('rgraph_tooltip_key'); if (obj && obj.style) { obj.style.color = 'inherit'; } }, 1); })(); // 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); 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} substitution 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.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 = /%{p(?:rop)?(?:erty)?:([_a-z0-9]+)\[([0-9]+)\]}/i; while (text.match(reg)) { var property = RegExp.$1; var index = parseInt(RegExp.$2); if (args.object.properties[property]) { text = text.replace( reg, args.object.properties[property][index] || '' ); // Get rid of the text } else { text = text.replace(reg,''); } RegExp.lastIndex = null; } // Replace this: %{property:xxx} while (text.match(/%{property:([_a-z0-9]+)}/i)) { var str = '%{property:' + RegExp.$1 + '}'; text = text.replace(str, args.object.properties[RegExp.$1]); } // Replace this: %{prop:xxx} while (text.match(/%{prop:([_a-z0-9]+)}/i)) { var str = '%{prop:' + RegExp.$1 + '}'; text = text.replace(str, args.object.properties[RegExp.$1]); } // Replace this: %{prop:xxx} while (text.match(/%{p:([_a-z0-9]+)}/i)) { var str = '%{p:' + RegExp.$1 + '}'; text = text.replace(str, args.object.properties[RegExp.$1]); } // THIS IS ONLY FOR A NON-EQUI-ANGULAR ROSE CHART // // Replace this: %{value2} if (args.object.type === 'rose' && args.object.properties.variant === 'non-equi-angular') { while (text.match(/%{value2}/i)) { text = text.replace('%{value2}', specific.value2); } } // Replace this: %{value} and this: %{value_formatted} while (text.match(/%{value(?:_formatted)?}/i)) { var value = specific.value; if (text.match(/%{value_formatted}/i)) { text = text.replace( '%{value_formatted}', typeof value === 'number' ? RGraph.numberFormat({ object: args.object, number: value.toFixed(args.object.properties.tooltipsFormattedDecimals), thousand: args.object.properties.tooltipsFormattedThousand || ',', point: args.object.properties.tooltipsFormattedPoint || '.', unitspre: args.object.properties.tooltipsFormattedUnitsPre || '', unitspost: args.object.properties.tooltipsFormattedUnitsPost || '' }) : null ); } else { text = text.replace('%{value}', value); } } //////////////////////////////////////////////////////////////// // Do global substitution when there's an index to the global // //////////////////////////////////////////////////////////////// var reg = /%{global:([_a-z0-9.]+)\[([0-9]+)\]}/i; while (text.match(reg)) { var name = RegExp.$1, index = parseInt(RegExp.$2); if (eval(name)) { text = text.replace( reg, eval(name)[index] || '' ); // Get rid of the text if there was nothing to replace the template bit with } else { text = text.replace(reg,''); } RegExp.lastIndex = null; } ////////////////////////////////////////////////// // Do global substitution when there's no index // ////////////////////////////////////////////////// var reg = /%{global:([_a-z0-9.]+)}/i; while (text.match(reg)) { var name = RegExp.$1; if (eval(name)) { text = text.replace( reg, eval(name) || '' ); // Get rid of the text if there was nothing to replace the template bit with } else { text = text.replace(reg,''); } RegExp.lastIndex = null; } // Call any functions var regexp = /%{function:([_A-Za-z0-9]+)\((.*?)\)}/; // Temporarily replace carriage returns and line feeds with CR and LF // so the the s option is not needed text = text.replace(/\r/,'|CR|'); text = text.replace(/\n/,'|LF|'); while (text.match(regexp)) { var str = RegExp.$1 + '(' + RegExp.$2 + ')'; for (var i=0,len=str.length; 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__ = args.text; args.text = substitute(args.text); // Add the pointer if requested. The background color is updated to match the // tooltip further down. if (args.object.properties.tooltipsPointer) { // Styles are applied to the pointer below to // circumvent a Content-Security-Policy header // problem args.text += '
'; // // If the tooltipsPointerCss property is populated then do the // substitution on those too // if (!RGraph.isNull(args.object.properties.tooltipsPointerCss)) { // This is in a timer to allow the tooltip (and the pointer) to be // added to the document. Then the document.getElementById('') method // will be able to retrieve it setTimeout(function () { var pointerObj = document.getElementById('RGraph_tooltipsPointer_' + args.object.id); for (var i in args.object.properties.tooltipsPointerCss) { if (typeof i === 'string') { pointerObj.style[i] = substitute(args.object.properties.tooltipsPointerCss[i]); } } }, 50); } } tooltipObj.innerHTML = args.text; tooltipObj.__text__ = args.text; // This is set because the innerHTML can change when it's set tooltipObj.__canvas__ = args.object.canvas; tooltipObj.__event__ = args.object.get('tooltipsEvent') || 'click'; tooltipObj.__object__ = args.object; tooltipObj.id = '__rgraph_tooltip_' + args.object.canvas.id + '_' + args.object.uid + '_'+ args.index; // 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 setTimeout(function () { var pointerObj = document.getElementById('RGraph_tooltipsPointer_' + args.object.id); var styles = window.getComputedStyle(tooltipObj, false); if (pointerObj) { pointerObj.style.backgroundColor = styles.backgroundColor; pointerObj.style.color = 'transparent'; pointerObj.style.position = 'absolute'; pointerObj.style.bottom = -5 + (RGraph.isNumber(args.object.properties.tooltipsPointerOffsety) ? args.object.properties.tooltipsPointerOffsety : 0) + 'px'; pointerObj.style.left = 'calc(50% + ' + (RGraph.isNumber(args.object.properties.tooltipsPointerOffsetx) ? args.object.properties.tooltipsPointerOffsetx : 0) + 'px )'; pointerObj.style.transform = 'translateX(-50%) rotate(45deg)'; pointerObj.style.width = '10px'; pointerObj.style.height = '10px'; } }, 16.666); if (typeof args.index === 'number') { tooltipObj.__index__ = args.index; origIdx = args.index; } if (args.object.type === 'line' || args.object.type === 'radar') { for (var ds=0; ds= args.object.data[ds].length) { args.index -= args.object.data[ds].length; } else { break; } } tooltipObj.__dataset__ = ds; tooltipObj.__index2__ = args.index; } document.body.appendChild(tooltipObj); var width = tooltipObj.offsetWidth; var height = tooltipObj.offsetHeight; // // 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 (args.object.properties.tooltipsPointer) { var styles = window.getComputedStyle(tooltipObj, false); var pointer = document.getElementById('RGraph_tooltipsPointer_' + args.object.id); pointer.style.backgroundColor = styles['background-color']; // 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 (args.object.properties.tooltipsPointerCss) { var pointerDiv = document.getElementById('RGraph_tooltipsPointer_' + args.object.id); for(property in args.object.properties.tooltipsPointerCss) { if (typeof property === 'string') { pointerDiv.style[property] = args.object.properties.tooltipsPointerCss[property]; } } } } // // position the tooltip on the mouse pointers position // var mouseXY = RGraph.getMouseXY(args.event); var canvasXY = RGraph.getCanvasXY(args.object.canvas); // Set these properties to 0 (ie an integer) in case chart libraries are missing // default values for them args.object.properties.tooltipsOffsetx = args.object.properties.tooltipsOffsetx || 0; args.object.properties.tooltipsOffsety = args.object.properties.tooltipsOffsety || 0; // Position based on the mouse pointer coords on the page tooltipObj.style.left = args.event.pageX - (parseFloat(tooltipObj.style.paddingLeft) + (width / 2)) + args.object.properties.tooltipsOffsetx + 'px'; tooltipObj.style.top = args.event.pageY - height - 10 + args.object.properties.tooltipsOffsety + 'px'; // If the left is less than zero - set it to 5 if (parseFloat(tooltipObj.style.left) <= 5) { tooltipObj.style.left = 5 + args.object.properties.tooltipsOffsetx + 'px'; } // If the top is less than zero - set it to 5 if (parseFloat(tooltipObj.style.top) <= 5) { tooltipObj.style.top = 5 + args.object.properties.tooltipsOffsety + '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 + args.object.properties.tooltipsOffsetx + 'px'; } // // Allow for static positioning. // if (args.object.properties.tooltipsPositionStatic && typeof args.object.positionTooltipStatic === 'function') { args.object.positionTooltipStatic({ object: args.object, event: args.event, tooltip: tooltipObj, index: origIdx }); } // // Move the tooltip and its pointer if they'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_' + args.object.id); (function (pointer) { setTimeout(function () { if (pointer) { pointer.style.left = 'calc(10% + ' + (5 + (typeof args.object.properties.tooltipsPointerOffsetx === 'number' ? args.object.properties.tooltipsPointerOffsetx : 0)) + 'px)'; //pointer.style.left = 'calc(10% + 5px)'; } }, 25); })(pointer) // // Move the tooltip and its pointer if they're off-screen RHS // //NB but only if the LHS checking was negative // } 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_' + args.object.id); (function (pointer) { setTimeout(function () { if (pointer) { pointer.style.left = 'calc(90% - ' + (5 + (typeof args.object.properties.tooltipsPointerOffsetx === 'number' ? args.object.properties.tooltipsPointerOffsetx : 0)) + 'px)'; //pointer.style.left = 'calc(90% - 5px)'; } }, 25) })(pointer); } // If the canvas has fixed positioning then set the tooltip position to // fixed too if (RGraph.isFixed(args.object.canvas)) { tooltipObj.style.position = 'fixed'; } // If the effect is fade: // Increase the opacity from its default 0 up to 1 - fading the tooltip in if (args.object.get('tooltipsEffect') === 'fade') { //setTimeout(function () //{ for (var i=1; i<=10; ++i) { (function (index) { setTimeout(function () { tooltipObj.style.opacity = index / 10; }, index * 25); })(i); } //}, 1000) } else { tooltipObj.style.opacity = 1; } // // If the tooltip itself is clicked, cancel it // tooltipObj.onmousedown = function (e){e.stopPropagation();} tooltipObj.onmouseup = function (e){e.stopPropagation();} tooltipObj.onclick = function (e){if (e.button == 0) {e.stopPropagation();}} // If the window is resized then hide the tooltip window.addEventListener('resize', function () { if (tooltipObj) { RGraph.hideTooltip(); RGraph.redrawCanvas(args.object.canvas); } }); // // Keep a reference to the tooltip in the registry // RGraph.Registry.set('tooltip', tooltipObj); // // Store the final X/Y coordinates so that sliding works // if (args.object.properties.tooltipsEffect === 'slide') { RGraph.tooltip_slide_effect_previous_x_coordinate = tooltipObj.style.left; RGraph.tooltip_slide_effect_previous_y_coordinate = tooltipObj.style.top; } // // Fire the tooltip event // RGraph.fireCustomEvent(args.object, 'ontooltip'); }; // // // RGraph.getTooltipTextFromDIV = function () { var args = RGraph.getArgs(arguments, 'text'); // This regex is duplicated firher down on roughly line 888 var result = /^id:(.*)/.exec(args.text); if (result && result[1] && document.getElementById(result[1])) { args.text = document.getElementById(result[1]).innerHTML; } else if (result && result[1]) { args.text = ''; } return args.text; }; // // Get the width that is set on the tooltip DIV based on the text // that has been given // RGraph.getTooltipWidth = function () { var args = RGraph.getArgs(arguments, 'text,object'); var div = document.createElement('DIV'); div.className = args.object.get('tooltipsCssClass'); div.style.paddingLeft = RGraph.tooltips.padding; div.style.paddingRight = RGraph.tooltips.padding; div.style.fontFamily = RGraph.tooltips.font_face; div.style.fontSize = RGraph.tooltips.font_size; div.style.visibility = 'hidden'; div.style.position = 'absolute'; div.style.top = '300px'; div.style.left = 0; div.style.display = 'inline'; div.innerHTML = RGraph.getTooltipTextFromDIV(args.text); document.body.appendChild(div); return div.offsetWidth; }; // // Hides the currently shown tooltip // RGraph.hideTooltip = function () { var tooltip = RGraph.Registry.get('tooltip'); var uid = arguments[0] && arguments[0].uid ? arguments[0].uid : null; if (tooltip && tooltip.parentNode && (!uid || uid == tooltip.__canvas__.uid)) { // Delete references, that were put there by RGraph, // from the tooltip for (var v of ['__canvas__', '__event__', '__index__', '__object__','__original_text__','__shape__','__text__']) { if (typeof tooltip[v] !== 'undefined') { delete tooltip[v]; } } // Delete references, that were put there by RGraph, // from the tooltip (that start with "rgraph_" for (i in tooltip) { if (i.substr(0,7) === 'rgraph_') { delete tooltip[i]; } } tooltip.parentNode.removeChild(tooltip); tooltip.style.display = 'none'; tooltip.style.visibility = 'hidden'; RGraph.Registry.set('tooltip', null); } }; // // This (as the name suggests preloads any images it can find in the tooltip text // // @param object obj The chart object // RGraph.preLoadTooltipImages = function () { var args = RGraph.getArgs(arguments, 'object'); var tooltips = args.object.get('tooltips'); if (RGraph.hasTooltips(args.object)) { if (args.object.type == 'rscatter') { tooltips = []; for (var i=0; i