// 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.Effects = RGraph.Effects || {}; RGraph.Effects.Rose = RGraph.Effects.Rose || {}; // // The rose chart constuctor // RGraph.Rose = function (conf) { this.id = conf.id; this.canvas = document.getElementById(this.id); this.context = this.canvas.getContext ? this.canvas.getContext("2d") : null; this.data = conf.data; this.unmodified_data = RGraph.arrayClone(this.data); this.canvas.__object__ = this; this.type = 'rose'; this.isRGraph = true; this.isrgraph = true; this.rgraph = true; this.uid = RGraph.createUID(); this.canvas.uid = this.canvas.uid ? this.canvas.uid : RGraph.createUID(); this.colorsParsed = false; this.coordsText = []; this.original_colors = []; this.firstDraw = true; // After the first draw this will be false this.stopAnimationRequested = false;// Used to control the animations this.centerx = 0; this.centery = 0; this.radius = 0; this.max = 0; this.angles = []; this.angles2 = []; this.properties = { axes: false, axesColor: 'black', axesLinewidth: 1, axesTickmarks: true, backgroundGrid: true, backgroundGridColor: '#ccc', backgroundGridSize: null, backgroundGridRadialsCount: null, backgroundGridRadialsOffset: 0, backgroundGridCirclesCount: 5, // [TODO] Need linewidth setting centerx: null, centery: null, radius: null, anglesStart: 0, anglesOffset: 0, linewidth: 1, colors: ['rgba(255,0,0,0.5)', 'rgba(255,255,0,0.5)', 'rgba(0,255,255,0.5)', 'rgb(0,255,0)', 'gray', 'blue', 'rgb(255,128,255)','green', 'pink', 'gray', 'aqua'], colorsSequential: false, colorsAlpha: null, colorsStroke: 'rgba(0,0,0,0)', margin: 5, marginLeft: 35, marginRight: 35, marginTop: 35, marginBottom: 35, shadow: false, shadowColor: '#aaa', shadowOffsetx: 0, shadowOffsety: 0, shadowBlur: 15, title: '', titleBold: true, titleFont: null, titleSize: null, titleItalic: null, titleColor: null, titleX: null, titleY: null, titleHalign: null, titleValign: null, titleOffsetx: 0, titleOffsety: 0, titleSubtitle: '', titleSubtitleSize: null, titleSubtitleColor: '#aaa', titleSubtitleFont: null, titleSubtitleBold: null, titleSubtitleItalic: null, titleSubtitleOffsetx: 0, titleSubtitleOffsety: 0, labels: null, labelsFormattedDecimals: 0, labelsFormattedPoint: '.', labelsFormattedThousand: ',', labelsFormattedUnitsPre: '', labelsFormattedUnitsPost: '', labelsColor: null, labelsFont: null, labelsSize: null, labelsBold: null, labelsItalic: null, labelsPosition: 'center', labelsBoxed: false, labelsOffsetRadius: 0, labelsOffsetAngle: 0, labelsAxes: 'n', labelsAxesFont: null, labelsAxesSize: null, labelsAxesColor: null, labelsAxesBold: null, labelsAxesItalic: null, labelsAxesBackground: 'rgba(255,255,255,0.8)', labelsAxesCount: 5, labelsAxesOffsetx: 0, labelsAxesOffsety: 0, textColor: 'black', textFont: 'Arial, Verdana, sans-serif', textSize: 12, textBold: false, textItalic: false, textAccessible: false, textAccessibleOverflow: 'visible', textAccessiblePointerevents: false, text: null, key: null, keyBackground: 'white', keyPosition: 'graph', keyHalign: 'right', keyShadow: false, keyShadowColor: '#666', keyShadowBlur: 3, keyShadowOffsetx: 2, keyShadowOffsety: 2, keyPositionGutterBoxed: false, keyPositionX: null, keyPositionY: null, keyColorShape: 'square', keyRounded: true, keyLinewidth: 1, keyColors: null, keyInteractive: false, keyinteractiveKeyHighlightChartLinewidth: 20, keyInteractiveHighlightChartStroke: 'black', keyInteractiveHighlightChartFill: 'rgba(255,255,255,0.7)', keyInteractiveHighlightLabel: 'rgba(255,0,0,0.2)', keyLabelsColor: null, keyLabelsFont: null, keyLabelsSize: null, keyLabelsBold: null, keyLabelsItalic: null, keyLabelsOffsetx: 0, keyLabelsOffsety: 0, keyFormattedDecimals: 0, keyFormattedPoint: '.', keyFormattedThousand: ',', keyFormattedUnitsPre: '', keyFormattedUnitsPost: '', keyFormattedValueSpecific: null, keyFormattedItemsCount: null, contextmenu: null, tooltips: null, tooltipsEvent: 'onclick', tooltipsEffect: 'slide', tooltipsCssClass: 'RGraph_tooltip', tooltipsCss: null, tooltipsHighlight: true, tooltipsPersistent: false, tooltipsFormattedThousand: ',', tooltipsFormattedPoint: '.', tooltipsFormattedDecimals: 0, tooltipsFormattedUnitsPre: '', tooltipsFormattedUnitsPost: '', tooltipsFormattedKeyColors: null, tooltipsFormattedKeyColorsShape: 'square', tooltipsFormattedKeyLabels: [], tooltipsFormattedListType: 'ul', tooltipsFormattedListItems: null, tooltipsFormattedTableHeaders: null, tooltipsFormattedTableData: null, tooltipsPointer: true, tooltipsPointerOffsetx: 0, tooltipsPointerOffsety: 0, tooltipsPositionStatic: true, tooltipsHotspotIgnore: null, highlightStroke: 'rgba(0,0,0,0)', highlightFill: 'rgba(255,255,255,0.7)', annotatable: false, annotatableColor: 'black', annotatableLinewidth: 1, adjustable: false, scaleMax: null, scaleMin: 0, scaleDecimals: null, scalePoint: '.', scaleThousand: ',', scaleUnitsPre: '', scaleUnitsPost: '', variant: 'stacked', variantThreedDepth: 10, exploded: 0, animationRoundrobinFactor: 1, animationRoundrobinRadius: true, animationGrowMultiplier: 1, segmentHighlight: false, segmentHighlightCount: null, segmentHighlightFill: 'rgba(0,255,0,0.5)', segmentHighlightStroke: 'rgba(0,0,0,0)', clearto: 'rgba(0,0,0,0)', events: {}, scale: true, scaleFactor: 2, antialiasTranslate: false }; // // These are the properties that get scaled up if the // scale option is enabled. // this.properties_scale = [ //'margin', // Don't double this - it's an angle 'marginLeft', 'marginRight', 'marginTop', 'marginBottom', 'linewidth', 'labelsSize', 'labelsOffsetRadius', 'labelsAxesSize', 'labelsAxesOffsetx', 'labelsAxesOffsety', 'textSize', 'titleSize', 'titleX', 'titleY', 'titleOffsetx', 'titleOffsety', 'titleSubtitleSize', 'titleSubtitleOffsetx', 'titleSubtitleOffsety', 'keyShadowBlur', 'keyShadowOffsetx', 'keyShadowOffsety', 'keyPositionX', 'keyPositionY', 'keyLinewidth', 'keyLabelsSize', 'keyLabelsOffsetx', 'keyLabelsOffsety', 'annotateLinewidth', 'centerx', 'centery', 'radius', 'variantThreedDepth', 'exploded' ]; // // Add the reverse look-up table for property names // so that property names can be specified in any case. // this.properties_lowercase_map = []; for (var i in this.properties) { if (typeof i === 'string') { this.properties_lowercase_map[i.toLowerCase()] = i; } } // Go through the data converting it to numbers this.data = RGraph.stringsToNumbers(this.data); // // Create the $ objects. In the case of non-equi-angular rose charts it actually creates too many $ objects, // but it doesn't matter. // var linear_data = RGraph.arrayLinearize(this.data); this.data_seq = linear_data; // Add .data_seq this.data_arr = linear_data; // Add .data_arr for (var i=0; i { // Note that we're in an arrow function so the // 'this' variable is OK to be used and refers // to the RGraph Line chart object. RGraph.scale(this); }); // // Fire the onbeforedraw event // RGraph.fireCustomEvent(this, 'onbeforedraw'); // Translate half a pixel for antialiasing purposes - but // only if it hasn't been done already // // The old style antialias fix // if ( !this.properties.scale && this.properties.antialiasTranslate && !this.canvas.__rgraph_aa_translated__) { this.context.translate(0.5,0.5); this.canvas.__rgraph_aa_translated__ = true; } // // Make the margins easy ro access // this.marginLeft = properties.marginLeft; this.marginRight = properties.marginRight; this.marginTop = properties.marginTop; this.marginBottom = properties.marginBottom; // Calculate the radius this.radius = (Math.min(this.canvas.width - this.marginLeft - this.marginRight, this.canvas.height - this.marginTop - this.marginBottom) / 2); this.centerx = ((this.canvas.width - this.marginLeft - this.marginRight) / 2) + this.marginLeft; this.centery = ((this.canvas.height - this.marginTop - this.marginBottom) / 2) + this.marginTop; this.angles = []; this.angles2 = []; this.total = 0; this.startRadians = properties.anglesStart; this.coordsText = []; // // Change the centerx marginally if the key is defined // if (properties.key && properties.key.length > 0 && properties.key.length >= 3) { this.centerx = this.centerx - this.marginRight + 5; } // User specified radius, centerx and centery if (typeof properties.centerx == 'number') this.centerx = properties.centerx; if (typeof properties.centery == 'number') this.centery = properties.centery; if (typeof properties.radius == 'number') this.radius = properties.radius; // // Allow the centerx/centery/radius to be a plus/minus // var scaleFactor = RGraph.getScaleFactor(this); if (typeof properties.radius === 'string' && properties.radius.match(/^\+|-\d+$/) ) this.radius += parseFloat(properties.radius) * scaleFactor; if (typeof properties.centerx === 'string' && properties.centerx.match(/^\+|-\d+$/) ) this.centerx += parseFloat(properties.centerx) * scaleFactor; if (typeof properties.centery === 'string' && properties.centery.match(/^\+|-\d+$/) ) this.centery += parseFloat(properties.centery) * scaleFactor; // // Parse the colors for gradients. Its down here so that the center X/Y can be used // if (!this.colorsParsed) { this.parseColors(); // Don't want to do this again this.colorsParsed = true; } // 3D variant if (properties.variant.indexOf('3d') !== -1) { var scaleX = 1.5; this.context.setTransform( scaleX, 0, 0, 1, (this.canvas.width * scaleX - this.canvas.width) * -0.5, 0 ); } // // Work out the maximum value and the sum // if (RGraph.isNullish(properties.scaleMax)) { // Work out the max var max = 0, data = this.data; for (var i=0; i0; i-=1) { this.centery -= 1; this.drawRose({storeAngles: false}); //RGraph.setShadow(this,'rgba(0,0,0,0)',0,0,0); RGraph.noShadow(this); // Make the segments darker for (var j=0,len=this.angles.length; j 0) { var num = (360 / properties.backgroundGridRadialsCount); var offset = properties.backgroundGridRadialsOffset; for (var i=0; i<=360; i+=num) { // Radius must be greater than 0 for Opera to work this.context.arc( this.centerx, this.centery, this.radius, ((i / (180 / RGraph.PI)) - RGraph.HALFPI) + this.startRadians + offset, (((i + 0.0001) / (180 / RGraph.PI)) - RGraph.HALFPI) + this.startRadians + offset, false ); this.context.lineTo(this.centerx, this.centery); } this.context.stroke(); } } if (properties.axes) { this.context.beginPath(); this.context.strokeStyle = properties.axesColor; this.context.lineWidth = properties.axesLinewidth; // Draw the X axis this.context.moveTo(this.centerx - this.radius, Math.round(this.centery) ); this.context.lineTo(this.centerx + this.radius, Math.round(this.centery) ); if (properties.axesTickmarks) { // Draw the X ends this.context.moveTo(Math.round(this.centerx - this.radius), this.centery - 5); this.context.lineTo(Math.round(this.centerx - this.radius), this.centery + 5); this.context.moveTo(Math.round(this.centerx + this.radius), this.centery - 5); this.context.lineTo(Math.round(this.centerx + this.radius), this.centery + 5); // Draw the X check marks for (var i=(this.centerx - this.radius); i<(this.centerx + this.radius); i+=(this.radius / 5)) { this.context.moveTo(Math.round(i), this.centery - 3); this.context.lineTo(Math.round(i), this.centery + 3.5); } // Draw the Y check marks for (var i=(this.centery - this.radius); i<(this.centery + this.radius); i+=(this.radius / 5)) { this.context.moveTo(this.centerx - 3, Math.round(i)); this.context.lineTo(this.centerx + 3, Math.round(i)); } } // Draw the Y axis this.context.moveTo(Math.round(this.centerx), this.centery - this.radius); this.context.lineTo(Math.round(this.centerx), this.centery + this.radius); if (properties.axesTickmarks) { // Draw the Y ends this.context.moveTo(this.centerx - 5, Math.round(this.centery - this.radius)); this.context.lineTo(this.centerx + 5, Math.round(this.centery - this.radius)); this.context.moveTo(this.centerx - 5, Math.round(this.centery + this.radius)); this.context.lineTo(this.centerx + 5, Math.round(this.centery + this.radius)); } // Stroke it this.context.closePath(); this.context.stroke(); } this.path('b c'); }; // // This method draws the data on the graph // this.drawRose = function () { var max = 0, data = this.data, margin = RGraph.toRadians(properties.margin), opt = arguments[0] || {}; this.context.lineWidth = properties.linewidth; // Move to the centre this.context.moveTo(this.centerx, this.centery); this.context.stroke(); // Stroke the background so it stays grey // Transparency if (properties.colorsAlpha) { this.context.globalAlpha = properties.colorsAlpha; } var sequentialIndex = 0; // // A non-equi-angular Rose chart // if (typeof properties.variant === 'string' && properties.variant.indexOf('non-equi-angular') !== -1) { var total=0; for (var i=0; i 1) { this.restrokeRose(); } } else { var sequentialColorIndex = 0; if (properties.shadow) { RGraph.setShadow( this, properties.shadowColor, properties.shadowOffsetx, properties.shadowOffsety, properties.shadowBlur ); } // // Draw regular segments here // for (var i=0; i 1) { this.restrokeRose(); } // // Now redraw the rose if the shadow is enabled so that // the rose appears over the shadow // if (properties.shadow) { this.redrawRose(); } } // Turn off the transparency if (properties.colorsAlpha) { this.context.globalAlpha = 1; } // Draw the title if any has been set if (properties.title) { RGraph.drawTitle(this); } }; // // This function redraws the stroke on the chart so that // the strokes appear above the fill // this.restrokeRose = function () { var angles = this.angles; for (var i=0; i -1) { // The offset for the labels if (properties.backgroundAxes) { var offset = -10; var halign = 'right'; } else { var offset = 0; var halign = 'center'; } var textConf = RGraph.getTextConf({ object: this, prefix: 'labelsAxes' }); for (var i=0; i -1) { // The offset for the labels if (properties.backgroundAxes) { var offset = -10; var halign = 'right'; } else { var offset = 0; var halign = 'center'; } for (var i=0; i -1) { for (var i=0; i -1) { for (var i=0; i 0) { RGraph.text({ object: this, font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic, 'x':centerx + properties.labelsAxesOffsetx, 'y':centery + properties.labelsAxesOffsety, 'text':typeof properties.scaleMin === 'number' ? RGraph.numberFormat({ object: this, number: Number(properties.scaleMin).toFixed(properties.scaleMin === 0 ? '0' : properties.scaleDecimals), unitspre: units_pre, unitspost: units_post }) : '0', 'valign':'center', 'halign':'center', 'bounding':true, 'bounding.fill':color, 'bounding.stroke':'rgba(0,0,0,0)', 'tag': 'scale' }); } }; // // Draws the circular labels that go around the charts // // @param labels array The labels that go around the chart // this.drawCircularLabels = function (context, labels, font, size, radius) { var scaleFactor = RGraph.getScaleFactor(this); var variant = properties.variant, position = properties.labelsPosition, radius = radius + (5 * scaleFactor) + properties.labelsOffsetRadius, centerx = this.centerx, centery = this.centery + (properties.variant.indexOf('3d') !== -1 ? properties.variantThreedDepth : 0), labelsColor = properties.labelsColor || properties.textColor, angles = this.angles; var textConf = RGraph.getTextConf({ object: this, prefix: 'labels' }); for (var i=0; i centerx) { halign = 'left'; } else if (Math.round(x) == centerx) { halign = 'center'; } else { halign = 'right'; } RGraph.text({ object: this, font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic, x: x, y: y, text: String(labels[i] || ''), halign: halign, valign: 'center', tag: 'labels', cssClass: RGraph.getLabelsCSSClassName({ object: this, name: 'labelsClass', index: i }) }); } }; // // This function is for use with circular graph types, eg the Pie or Rose. Pass it your event object // and it will pass you back the corresponding segment details as an array: // // [x, y, r, startAngle, endAngle] // // Angles are measured in degrees, and are measured from the "east" axis (just like the canvas). // // @param object e Your event object // @param object Options (OPTIONAL): // radius - whether to take into account // the radius of the segment // this.getShape = function (e) { var angles = this.angles; var ret = []; var opt = arguments[1] ? arguments[1] : {radius: true}; // // Go through all of the angles checking each one // for (var i=0; i 0) { this.path( 'a % % % % % true', this.angles[i][4], this.angles[i][5],this.angles[i][2],this.angles[i][1], this.angles[i][0] ); } else { this.path( 'l % %', this.angles[i][4], this.angles[i][5] ); } this.path( 'c s % f %', properties.highlightStroke, properties.highlightFill ); } } return; } // Add the new segment highlight this.path('b a % % % % % false',shape.x, shape.y, shape.radiusEnd, shape.angleStart, shape.angleEnd); if (shape.radiusStart > 0) { this.path('a % % % % % true',shape.x, shape.y, shape.radiusStart, shape.angleEnd, shape.angleStart); } else { this.path('l % %',shape.x, shape.y); } this.path('c s % f %', properties.highlightStroke, properties.highlightFill); } }; // // The getObjectByXY() worker method. Don't call this call: // // RGraph.ObjectRegistry.getObjectByXY(e) // // @param object e The event object // this.getObjectByXY = function (e) { var mouseXY = RGraph.getMouseXY(e); // Work out the radius var radius = RGraph.getHypLength(this.centerx, this.centery, mouseXY[0], mouseXY[1]); // Account for the 3D stretching effect if (properties.variant.indexOf('3d') !== -1) { radius /= -1; // Can be 100 because there's a radius check done as well var additional3D = 100; } else { var additional3D = 0; } if ( mouseXY[0] > (this.centerx - this.radius - additional3D) && mouseXY[0] < (this.centerx + this.radius + additional3D) && mouseXY[1] > (this.centery - this.radius) && mouseXY[1] < (this.centery + this.radius) && radius <= this.radius ) { return this; } }; // // This method gives you the relevant radius for a particular value // // @param number value The relevant value to get the radius for // this.getRadius = function (value) { // Range checking (the Rose minimum is always 0) if (value < 0 || value > this.max) { return null; } var r = (value / this.max) * this.radius; return r; }; // // This allows for easy specification of gradients // this.parseColors = function () { // Save the original colors so that they can be restored when the canvas is reset if (this.original_colors.length === 0) { this.original_colors.colors = RGraph.arrayClone(properties.colors); this.original_colors.keyColors = RGraph.arrayClone(properties.keyColors); this.original_colors.highlightStroke = RGraph.arrayClone(properties.highlightStroke); this.original_colors.highlightFill = RGraph.arrayClone(properties.highlightFill); } for (var i=0; i opt.startFrames[i]) { if (typeof obj.data[i] === 'number') { obj.data[i] = Math.min( Math.abs(original[i]), Math.abs(original[i] * ( (opt.counters[i]++) / framespersegment)) ); // Make the number negative if the original was if (original[i] < 0) { obj.data[i] *= -1; } } else if (!RGraph.isNullish(obj.data[i])) { for (let j=0,len2=obj.data[i].length; j= opt.frames) { callback(obj); } else { RGraph.redrawCanvas(obj.canvas); RGraph.Effects.updateCanvas(iterator); } } iterator(); return this; }; // // Couple of functions that allow you to control the // animation effect // this.stopAnimation = function () { // Reset the clip this.set('animationGrowMultiplier', 1); // Reset the RoundRobin factor this.set('animationRoundrobinFactor', 1); // Set the original margin if (RGraph.isNumber(this.originalMargin)) { this.set('margin', this.originalMargin); } // Reset the exploded this.set('exploded', []); this.stopAnimationRequested = true; }; this.cancelStopAnimation = function () { this.stopAnimationRequested = false; }; // // A worker function that handles Bar chart specific tooltip substitutions // this.tooltipSubstitutions = function (opt) { var indexes = RGraph.sequentialIndexToGrouped(opt.index, this.data); var value = this.data_arr[opt.index]; var values = typeof this.data[indexes[0]] === 'number' ? [this.data[indexes[0]]] : this.data[indexes[0]]; if (properties.variant === 'non-equi-angular' && RGraph.isArray(this.data[indexes[0]])) { value = this.data[opt.index][0]; value2 = this.data[opt.index][1]; indexes = [opt.index, 0]; } return { index: indexes[1], dataset: indexes[0], sequentialIndex: opt.index, value: value, value2: typeof value2 === 'number' ? value2 : null, values: values }; }; // // A worker function that returns the correct color/label/value // // @param object specific The indexes that are applicable // @param number index The appropriate index // this.tooltipsFormattedCustom = function (specific, index, colors) { var color = properties.colors[index]; // Accommodate colorsSequential if (properties.colorsSequential) { color = colors[specific.sequential]; } // Different variations of the Rose chart // REGULAR CHART if (typeof this.data[specific.dataset] === 'number') { var label = properties.tooltipsFormattedKeyLabels[0] || ''; var color = properties.colors[0]; if (properties.tooltipsFormattedKeyColors && properties.tooltipsFormattedKeyColors[0]) { color = properties.tooltipsFormattedKeyColors[0]; } // NON-EQUI-ANGULAR CHART } else if (typeof this.data[specific.dataset] === 'object' && properties.variant === 'non-equi-angular') { // Don't show the second value on a non-equi-angular chart if (index === 0) { var color = colors[0]; var value = this.data[specific.dataset][0]; // Allow for specific tooltip key colors if (properties.tooltipsFormattedKeyColors && properties.tooltipsFormattedKeyColors[specific.index]) { color = properties.tooltipsFormattedKeyColors[specific.index]; } } else { var skip = true; } // STACKED CHART } else if (typeof this.data[specific.dataset] === 'object' && properties.variant !== 'non-equi-angular') { // Allow for specific tooltip key colors if (properties.tooltipsFormattedKeyColors && properties.tooltipsFormattedKeyColors[index]) { color = properties.tooltipsFormattedKeyColors[index]; } } //label = ( (typeof properties.tooltipsFormattedKeyLabels === 'object' && typeof properties.tooltipsFormattedKeyLabels[specific.index] === 'string') ? properties.tooltipsFormattedKeyLabels[specific.index] : ''); return { continue: skip, label: label, color: color, value: value }; }; // // This allows for static tooltip positioning // this.positionTooltipStatic = function (args) { var obj = args.object, e = args.event, tooltip = args.tooltip, index = args.index, canvasXY = RGraph.getCanvasXY(obj.canvas) segment = this.angles[args.index], shape = this.getShape(e), angle = ((shape.angleEnd - shape.angleStart) / 2) + shape.angleStart, multiplier = 0.5, scaleFactor = RGraph.getScaleFactor(this);; var endpoint = RGraph.getRadiusEndPoint( shape.x, shape.y, angle, ((shape.radiusEnd - shape.radiusStart) / 2) + shape.radiusStart ); // Allow for the 3D stretching of the canvas if (properties.variant.indexOf('3d') !== -1) { var width = this.radius / 2; endpoint[0] = (endpoint[0] - this.centerx) * 1.5 + this.centerx; } // Position the tooltip in the X direction args.tooltip.style.left = ( canvasXY[0] // The X coordinate of the canvas + (endpoint[0] / scaleFactor) // The X coordinate of the bar on the chart - (tooltip.offsetWidth / 2) // Subtract half of the tooltip width + obj.properties.tooltipsOffsetx // Add any user defined offset ) + 'px'; args.tooltip.style.top = ( canvasXY[1] // The Y coordinate of the canvas + (endpoint[1] / scaleFactor) // The Y coordinate of the bar on the chart - tooltip.offsetHeight // The height of the tooltip + obj.properties.tooltipsOffsety // Add any user defined offset - 10 // Account for the pointer ) + 'px'; }; // // This returns the relevant value for the formatted key // macro %{value}. THIS VALUE SHOULD NOT BE FORMATTED. // // @param number index The index in the dataset to get // the value for // this.getKeyValue = function (index) { if ( RGraph.isArray(this.properties.keyFormattedValueSpecific) && RGraph.isNumber(this.properties.keyFormattedValueSpecific[index])) { return this.properties.keyFormattedValueSpecific[index]; // Allow for non-equi-angular Rose charts } else if (this.properties.variant === 'non-equi-angular') { var total = 0; for (let i=0; i