// 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}; // // The bipolar/age frequency constructor. // RGraph.Bipolar = function (conf) { var id = conf.id, canvas = document.getElementById(id), left = conf.left, right = conf.right; // Get the canvas and context objects this.id = id; this.canvas = canvas; this.context = this.canvas.getContext('2d'); this.canvas.__object__ = this; this.type = 'bipolar'; this.coords = []; this.coords2 = []; this.coordsLeft = []; this.coordsRight = []; this.coords2Left = []; this.coords2Right = []; this.max = 0; 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.coordsText = []; this.original_colors = []; this.colorsParsed = false; this.firstDraw = true; // After the first draw this will be false this.stopAnimationRequested_left = false;// Used to control the animations this.stopAnimationRequested_right = false;// Used to control the animations // The left and right data respectively. Ensure that the data is an array // of numbers var data = [left, right]; // Convert strings to arrays data[0] = RGraph.stringsToNumbers(data[0]); data[1] = RGraph.stringsToNumbers(data[1]); this.left = data[0]; this.right = data[1]; this.data = [data[0], data[1]]; this.data2 = []; // Add all of the data to the data2 variable for (var i=0;i<left.length; ++i) this.data2.push(left[i]); for (var i=0;i<right.length; ++i) this.data2.push(right[i]); this.properties = { backgroundGrid: true, backgroundGridColor: '#ddd', backgroundGridVlines: true, backgroundGridHlines: true, backgroundGridLinewidth: 1, backgroundGridVlinesCount: null, backgroundGridHlinesCount: null, xaxis: true, xaxisTickmarks: true, xaxisTickmarksLength: 3, xaxisTickmarksCount: 5, xaxisScaleUnitsPre: '', xaxisScaleUnitsPost: '', xaxisScaleMax: null, xaxisScaleMin: 0, xaxisScaleZerostart: true, xaxisScaleDecimals: null, xaxisScalePoint: '.', xaxisScaleThousand: ',', xaxisLabels: true, xaxisLabelsFont: null, xaxisLabelsSize: null, xaxisLabelsColor: null, xaxisLabelsBold: null, xaxisLabelsItalic: null, xaxisLabelsCount: 5, xaxisLabelsOffsetx: 0, xaxisLabelsOffsety: 0, yaxis: true, yaxisTickmarks: true, yaxisTickmarksCount: null, yaxisTickmarksLength: 3, yaxisLabels: [], yaxisLabelsFont: null, yaxisLabelsSize: null, yaxisLabelsColor: null, yaxisLabelsBold: null, yaxisLabelsItalic: null, yaxisLabelsOffsetx: 0, yaxisLabelsOffsety: 0, yaxisLabelsFormattedDecimals: 0, yaxisLabelsFormattedPoint: '.', yaxisLabelsFormattedThousand: ',', yaxisLabelsFormattedUnitsPre: '', yaxisLabelsFormattedUnitsPost: '', labelsAbove: false, labelsAboveFont: null, labelsAboveSize: null, labelsAboveBold: null, labelsAboveItalic: null, labelsAboveColor: null, labelsAboveUnitsPre: '', labelsAboveUnitsPost: '', labelsAboveDecimals: 0, labelsAboveFormatter: null, labelsAboveOffsetx: 0, labelsAboveOffsety: 0, textBold: false, textItalic: false, textSize: 12, textColor: 'black', textFont: 'Arial, Verdana, sans-serif', textAccessible: false, textAccessibleOverflow: 'visible', textAccessiblePointerevents:false, text: null, titleLeft: '', titleLeftFont: null, titleLeftSize: null, titleLeftBold: null, titleLeftItalic: null, titleLeftColor: null, titleLeftOffsetx: 0, titleLeftOffsety: 0, titleRight: '', titleRightFont: null, titleRightSize: null, titleRightBold: null, titleRightItalic: null, titleRightColor: null, titleOffsetx: 0, titleOffsety: 0, titleRightOffsetx: 0, titleRightOffsety: 0, title: '', titleFont: null, titleSize: null, titleBold: 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, marginCenter: 0, marginCenterAuto: true, marginLeft: 35, marginRight: 35, marginTop: 35, marginBottom: 35, marginInner: 5, marginInnerGrouped: 3, colorsStroke: 'rgba(0,0,0,0)', colors: ['red','blue','yellow','#afa','#faa','#aaf','#aff','#ffa','#faf','cyan','brown','gray','black','pink','#afa','#faa','#aaf','#aff','#ffa','#faf','cyan','brown','gray','black','pink'], //colors: ['#afa','#faa','#aaf','#aff','#ffa','#faf','cyan','brown','gray','black','pink','#afa','#faa','#aaf','#aff','#ffa','#faf','cyan','brown','gray','black','pink'], colorsSequential: false, colorsLeft: null, colorsRight: null, contextmenu: null, tooltips: null, tooltipsEffect: 'slide', tooltipsCssClass: 'RGraph_tooltip', tooltipsCss: null, tooltipsHighlight: true, tooltipsPersistent: false, tooltipsEvent: 'click', 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, key: null, keyBackground: 'white', keyPosition: 'graph', keyShadow: false, keyShadowColor: '#666', keyShadowBlur: 3, keyShadowOffsetx: 2, keyShadowOffsety: 2, keyPositionMarginBoxed: false, keyPositionMarginHSpace: 0, keyPositionX: null, keyPositionY: null, keyInteractive: false, keyInteractiveHighlightChartStroke:'transparent', keyInteractiveHighlightChartFill:'rgba(255,255,255,0.7)', keyInteractiveHighlightLabel:'rgba(255,0,0,0.2)', keyHalign: 'right', keyColorShape: 'square', keyRounded: true, keyLinewidth: 1, keyColors: null, keyLabelsColor: null, keyLabelsSize: null, keyLabelsFont: null, keyLabelsBold: null, keyLabelsItalic: null, keyLabelsOffsetx: 0, keyLabelsOffsety: 0, keyFormattedDecimals: 0, keyFormattedPoint: '.', keyFormattedThousand: ',', keyFormattedUnitsPre: '', keyFormattedUnitsPost: '', keyFormattedValueSpecific: null, keyFormattedItemsCount: null, highlightStroke: 'rgba(0,0,0,0)', highlightFill: 'rgba(255,255,255,0.7)', shadow: false, shadowColor: '#ccc', shadowOffsetx: 3, shadowOffsety: 3, shadowOlur: 3, annotatable: false, annotatableColor: 'black', axes: true, axesColor: 'black', axesLinewidth: 1, linewidth: 1, variantThreedOffsetx: 10, variantThreedOffsety: 5, variantThreedAngle: 0.1, grouping: 'grouped', clearto: 'rgba(0,0,0,0)', leftVisible: true, rightVisible: true } // // 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; } } // Pad the arrays so they're the same size // // DON'T DO THIS NOW - 3/9/17 //while (this.left.length < this.right.length) this.left.push(null); //while (this.left.length > this.right.length) this.right.push(null); // // Set the default for the number of Y tickmarks // this.properties.yaxisTickmarksCount = this.left.length; // // Create the dollar objects so that functions can be // added to them // var linear_data = RGraph.arrayLinearize(this.left, this.right); for (var i=0; i<linear_data.length; ++i) { this['$' + i] = {}; } // Easy access to properties and the path function var properties = this.properties; this.path = RGraph.pathObjectFunction; // // "Decorate" the object with the generic effects if the effects library has been included // if (RGraph.Effects && typeof RGraph.Effects.decorate === 'function') { RGraph.Effects.decorate(this); } // Add the responsive method. This method resides in the common file. this.responsive = RGraph.responsive; // // The setter // // @param name string The name of the parameter to set // @param value mixed The value of the paraneter // this.set = function (name) { var value = typeof arguments[1] === 'undefined' ? null : arguments[1]; // Go through all of the properties and make sure // that they're using the correct capitalisation if (typeof name === 'string') { name = this.properties_lowercase_map[name.toLowerCase()] || name; } // Set the colorsParsed flag to false if the colors // property is being set if ( name === 'colors' || name === 'highlightStroke' || name === 'highlightFill' || name === 'axesColor' || name === 'colorsStroke' || name === 'colorsLeft' || name === 'colorsRight' || name === 'keyColors' ) { this.colorsParsed = false; } // the number of arguments is only one and it's an // object - parse it for configuration data and return. if (arguments.length === 1 && typeof arguments[0] === 'object') { for (i in arguments[0]) { if (typeof i === 'string') { this.set(i, arguments[0][i]); } } return this; } properties[name] = value; return this; }; // // The getter // // @param name string The name of the parameter to get // this.get = function (name) { // Go through all of the properties and make sure // that they're using the correct capitalisation name = this.properties_lowercase_map[name.toLowerCase()] || name; return this.properties[name]; }; // // Draws the graph // this.draw = function () { // // Fire the onbeforedraw event // RGraph.fireCustomEvent(this, 'onbeforedraw'); // Translate half a pixel for antialiasing purposes - but only if it hasn't been // done already // if (!this.canvas.__rgraph_aa_translated__) { this.context.translate(0.5,0.5); this.canvas.__rgraph_aa_translated__ = true; } // // Parse the colors. This allows for simple gradient syntax // if (!this.colorsParsed) { this.parseColors(); // Don't want to do this again this.colorsParsed = true; } // // Make the margins easy to access // this.marginLeft = properties.marginLeft; this.marginRight = properties.marginRight; this.marginTop = properties.marginTop; this.marginBottom = properties.marginBottom; this.marginCenter = properties.marginCenter; this.marginCenterAuto = properties.marginCenterAuto; if (properties.yaxisLabels && properties.yaxisLabels.length) { // // If the xaxisLabels option is a string then turn it // into an array. // if (typeof properties.yaxisLabels === 'string') { properties.yaxisLabels = RGraph.arrayPad({ array: [], length: this.left.length, value: properties.yaxisLabels }); } // // Label substitution // for (var i=0; i<properties.yaxisLabels.length; ++i) { properties.yaxisLabels[i] = RGraph.labelSubstitution({ object: this, text: properties.yaxisLabels[i], index: i, value: this.left[i], decimals: properties.yaxisLabelsFormattedDecimals || 0, unitsPre: properties.yaxisLabelsFormattedUnitsPre || '', unitsPost: properties.yaxisLabelsFormattedUnitsPost || '', thousand: properties.yaxisLabelsFormattedThousand || ',', point: properties.yaxisLabelsFormattedPoint || '.' }); } } // // Autosize the center margin to allow for big labels // if (properties.marginCenterAuto && !properties.marginCenter) { properties.marginCenter = this.getMarginCenter(); } this.marginCenter = properties.marginCenter; // Reset the data to what was initially supplied this.left = this.data[0]; this.right = this.data[1]; // // Reset the coords array // this.coords = []; this.coords2 = []; this.coordsLeft = []; this.coordsRight = []; this.coords2Left = []; this.coords2Right = []; // // Stop this growing uncontrollably // this.coordsText = []; if (properties.variant === '3d') { if (properties.textAccessible) { // Nada } else { this.context.setTransform(1,properties.variantThreedAngle,0,1,0.5,0.5); } } // Some necessary variables this.axisWidth = (this.canvas.width - properties.marginCenter - this.marginLeft - this.marginRight) / 2; this.axisHeight = this.canvas.height - this.marginTop - this.marginBottom; // Reset the sequential index this.sequentialFullIndex = 0; // Generate the scale this.getMax(); // // Install clipping // if (!RGraph.isNullish(this.properties.clip)) { RGraph.clipTo.start(this, this.properties.clip); } this.drawBackgroundGrid(); this.draw3DAxes(); this.drawAxes(); this.drawTickmarks(); this.drawLeftBars(); this.drawRightBars(); // Redraw the bars so that shadows on not on top if (properties.leftVisible) this.drawLeftBars({shadow: false}); if (properties.rightVisible) this.drawRightBars({shadow: false}); this.drawAxes(); this.drawLabels(); this.drawTitles(); // Call this so that 3D charts are redrawn (the bar faces) this.redraw3Dfaces(); // Draw the key if necessary if (properties.key && properties.key.length) { RGraph.drawKey( this, properties.key, properties.colors ); } // // Setup the context menu if required // if (properties.contextmenu) { RGraph.showContext(this); } // // Add custom text thats specified // RGraph.addCustomText(this); // // This installs the event listeners // RGraph.installEventListeners(this); // // End clipping // if (!RGraph.isNullish(this.properties.clip)) { RGraph.clipTo.end(); } // // Fire the onfirstdraw event // if (this.firstDraw) { this.firstDraw = false; RGraph.fireCustomEvent(this, 'onfirstdraw'); this.firstDrawFunc(); } // // Fire the RGraph draw event // RGraph.fireCustomEvent(this, 'ondraw'); // // Install any inline responsive configuration. This // should be last in the draw function - even after // the draw events. // RGraph.installInlineResponsive(this); return this; }; // // Used in chaining. Runs a function there and then - not waiting for // the events to fire (eg the onbeforedraw event) // // @param function func The function to execute // this.exec = function (func) { func(this); return this; }; // // Redraws 3D faces of bars so they look OK // this.redraw3Dfaces = function () { //return; // Redraw the faces of the bars so that if 3D mode is enabled the // faces appear over the sides of other bars if (properties.variant === '3d') { // LEFT HAND SIDE BARS // // // If the colorsLeft option is set then change // the colors option to that. // if (!RGraph.isNullish(properties.colorsLeft)) { // Save the initial colors value properties.colorsInitial = properties.colors; // Set the new value properties.colors = properties.colorsLeft; } // A regular chart if (this.coordsLeft.length > 0 && this.coords2Left.length === 0) { for (var i=0; i<this.coordsLeft.length; ++i) { this.path( 'b r % % % % f %', this.coordsLeft[i][0], this.coordsLeft[i][1], this.coordsLeft[i][2], this.coordsLeft[i][3], properties.colors[0] ); } // A grouped or stacked chart } else { for (var i=0; i<this.coords2Left.length; ++i) { for (var j=0; j<this.coords2Left[i].length; ++j) { this.path( 'b r % % % % f %', this.coords2Left[i][j][0], this.coords2Left[i][j][1], this.coords2Left[i][j][2], this.coords2Left[i][j][3], properties.colors[j] ); } } } // If the colorsLeft option is set then change // the colors option back to what it was. // if (!RGraph.isNullish(properties.colorsLeft)) { properties.colors = properties.colorsInitial; } // RIGHT HAND SIDE BARS // // // If the colorsRight option is set then change // the colors option to that. // if (!RGraph.isNullish(properties.colorsRight)) { // Save the initial colors value properties.colorsInitial = properties.colors; // Set the new value properties.colors = properties.colorsRight; } var offsetx = properties.variantThreedOffsetx, offsety = properties.variantThreedOffsety; // A regular chart if (this.coordsRight.length > 0 && this.coords2Right.length === 0) { for (var i=0; i<this.coordsRight.length; ++i) { var coords = this.coordsRight[i]; this.path( 'b r % % % % f %', coords[0], coords[1], coords[2], coords[3], properties.colors[0] ); // Draw the right hand side in the regular color this.path( 'b m % % l % % l % % l % % f %', coords[0] + coords[2], coords[1], coords[0] + coords[2] + offsetx, coords[1] - offsety, coords[0] + coords[2] + offsetx, coords[1] - offsety + coords[3], coords[0] + coords[2], coords[1] + coords[3], properties.colors[0] ); // Add the darker tint over thes top of the face to darken it this.path( 'b m % % l % % l % % l % % f %', coords[0] + coords[2], coords[1], coords[0] + coords[2] + offsetx, coords[1] - offsety, coords[0] + coords[2] + offsetx, coords[1] - offsety + coords[3], coords[0] + coords[2], coords[1] + coords[3], 'rgba(0,0,0,0.3)' ); } // A grouped or stacked chart } else { for (var i=0; i<this.coords2Right.length; ++i) { for (var j=(this.coords2Right[i].length - 1); j>=0; --j) { var coords = this.coords2Right[i][j]; this.path( 'b r % % % % f %', coords[0], coords[1], coords[2], coords[3], properties.colors[j] ); // Draw the top side in the regular color this.path( 'b m % % l % % l % % l % % f %', coords[0],coords[1], coords[0] + offsetx, coords[1] - offsety, coords[0] + coords[2] + offsetx, coords[1] - offsety, coords[0] + coords[2], coords[1], properties.colors[j] ); // Draw the lighter tint over the top this.path( 'b m % % l % % l % % l % % f rgba(255,255,255,0.6)', coords[0],coords[1], coords[0] + offsetx, coords[1] - offsety, coords[0] + coords[2] + offsetx, coords[1] - offsety, coords[0] + coords[2], coords[1] ); // Draw the right hand side in the regular color this.path( 'b m % % l % % l % % l % % f %', coords[0] + coords[2],coords[1], coords[0] + coords[2] + offsetx, coords[1] - offsety, coords[0] + coords[2] + offsetx, coords[1] - offsety + coords[3], coords[0] + coords[2], coords[1] + coords[3], properties.colors[j] ); // Add the darker tint over the top of the face to darken it this.path( 'b m % % l % % l % % l % % f %', coords[0] + coords[2], coords[1], coords[0] + coords[2] + offsetx, coords[1] - offsety, coords[0] + coords[2] + offsetx, coords[1] - offsety + coords[3], coords[0] + coords[2], coords[1] + coords[3], 'rgba(0,0,0,0.3)' ); } } } // If the colorsRight option is set then change // the colors option back to what it was. // if (!RGraph.isNullish(properties.colorsRight)) { properties.colors = properties.colorsInitial; } } }; // // Draws the axes // this.draw3DAxes = function () { if (properties.variant === '3d') { var offsetx = properties.variantThreedOffsetx, offsety = properties.variantThreedOffsety; // Set the linewidth this.context.lineWidth = properties.axesLinewidth + 0.001; // Draw the left set of axes this.context.beginPath(); this.context.strokeStyle = properties.axesColor; // Draw the horizontal 3d axis // The left horizontal axis this.path( 'b m % % l % % l % % l % % s #aaa f #ddd', this.marginLeft, this.canvas.height - this.marginBottom, this.marginLeft + offsetx, this.canvas.height - this.marginBottom - offsety, this.marginLeft + offsetx + this.axisWidth, this.canvas.height - this.marginBottom - offsety, this.marginLeft + this.axisWidth, this.canvas.height - this.marginBottom ); // The left vertical axis this.draw3DLeftVerticalAxis(); // Draw the right horizontal axes this.path( 'b m % % l % % l % % l % % s #aaa f #ddd', this.marginLeft + this.marginCenter + this.axisWidth, this.canvas.height - this.marginBottom, this.marginLeft + this.marginCenter + this.axisWidth + offsetx, this.canvas.height - this.marginBottom - offsety, this.marginLeft + this.marginCenter + this.axisWidth + this.axisWidth + offsetx, this.canvas.height - this.marginBottom - offsety, this.marginLeft + this.marginCenter + this.axisWidth + this.axisWidth, this.canvas.height - this.marginBottom ); // Draw the right vertical axes this.path( 'b m % % l % % l % % l % % s #aaa f #ddd', this.marginLeft + this.marginCenter + this.axisWidth, this.canvas.height - this.marginBottom, this.marginLeft + this.marginCenter + this.axisWidth, this.canvas.height - this.marginBottom - this.axisHeight, this.marginLeft + this.marginCenter + this.axisWidth + offsetx, this.canvas.height - this.marginBottom - this.axisHeight - offsety, this.marginLeft + this.marginCenter + this.axisWidth + offsetx, this.canvas.height - this.marginBottom - offsety ); } } // // Redraws the left vertical axis // this.draw3DLeftVerticalAxis = function () { if (properties.variant === '3d') { var offsetx = properties.variantThreedOffsetx, offsety = properties.variantThreedOffsety; // The left vertical axis this.path( 'b m % % l % % l % % l % % s #aaa f #ddd', this.marginLeft + this.axisWidth, this.marginTop, this.marginLeft + this.axisWidth + offsetx, this.marginTop - offsety, this.marginLeft + this.axisWidth + offsetx, this.canvas.height - this.marginBottom - offsety, this.marginLeft + this.axisWidth, this.canvas.height - this.marginBottom ); } }; // // Draws the axes // this.drawAxes = function () { // Set the linewidth this.context.lineWidth = properties.axesLinewidth + 0.001; this.context.lineCap = 'square'; // Draw the left set of axes this.context.beginPath(); this.context.strokeStyle = properties.axesColor; this.axisWidth = (this.canvas.width - properties.marginCenter - this.marginLeft - this.marginRight) / 2; this.axisHeight = this.canvas.height - this.marginTop - this.marginBottom; // This must be here so that the two above variables are calculated if (!properties.axes) { return; } if (properties.leftVisible) { if (properties.xaxis) { this.context.moveTo( this.marginLeft, this.canvas.height - this.marginBottom ); this.context.lineTo( this.marginLeft + this.axisWidth, this.canvas.height - this.marginBottom ); } if (properties.yaxis) { this.context.moveTo(this.marginLeft + this.axisWidth, this.canvas.height - this.marginBottom); this.context.lineTo(this.marginLeft + this.axisWidth, this.marginTop); } this.context.stroke(); } // Draw the right set of axes this.context.beginPath(); var x = this.marginLeft + this.axisWidth + properties.marginCenter; if (properties.rightVisible) { if (properties.yaxis) { this.context.moveTo(x, this.marginTop); this.context.lineTo(x, this.canvas.height - this.marginBottom); } if (properties.xaxis) { this.context.moveTo(x, this.canvas.height - this.marginBottom); this.context.lineTo(this.canvas.width - this.marginRight, this.canvas.height - this.marginBottom); } this.context.stroke(); } }; // // Draws the tick marks on the axes // this.drawTicks = this.drawTickmarks = function () { // Set the linewidth this.context.lineWidth = properties.axesLinewidth + 0.001; var numDataPoints = this.left.length; var barHeight = ( (this.canvas.height - this.marginTop - this.marginBottom) - (this.left.length * (properties.marginInner * 2) )) / numDataPoints; // Store this for later this.barHeight = barHeight; // If no axes - no tickmarks if (!properties.axes) { return; } // Draw the left Y tick marks if (properties.yaxis && properties.yaxisTickmarks) { var numTickmarks = this.left.length; var tickmarksLength = properties.yaxisTickmarksLength; // A custom number of tickmarks if (RGraph.isNumber(properties.yaxisTickmarksCount)) { numTickmarks = properties.yaxisTickmarksCount; } if (properties.leftVisible) { this.context.beginPath(); for (var i=0; i<numTickmarks; ++i) { var y = properties.marginTop + (((this.canvas.height - this.marginTop - this.marginBottom) / numTickmarks) * i); this.context.moveTo(this.marginLeft + this.axisWidth , y); this.context.lineTo(this.marginLeft + this.axisWidth + tickmarksLength, y); } this.context.stroke(); } if (properties.rightVisible) { //Draw the right axis Y tick marks this.context.beginPath(); for (var i=0; i<numTickmarks; ++i) { var y = properties.marginTop + (((this.canvas.height - this.marginTop - this.marginBottom) / numTickmarks) * i); this.context.moveTo(this.marginLeft + this.axisWidth + properties.marginCenter, y); this.context.lineTo(this.marginLeft + this.axisWidth + properties.marginCenter - tickmarksLength, y); } this.context.stroke(); } // Draw an exra tick if the X axis isn't being shown // on each of the sides if (!properties.xaxis) { if (properties.leftVisible) { this.path( 'b m % % l % % s %', this.marginLeft + this.axisWidth,this.canvas.height - this.marginBottom, this.marginLeft + this.axisWidth + properties.yaxisTickmarksLength, this.canvas.height - this.marginBottom, this.context.strokeStyle ); } if (properties.rightVisible) { this.path( 'b m % % l % % s %', this.marginLeft + this.axisWidth + properties.marginCenter, this.canvas.height - this.marginBottom, this.marginLeft + this.axisWidth + properties.marginCenter - properties.yaxisTickmarksLength, this.canvas.height - this.marginBottom, this.context.strokeStyle ); } } } // // X tickmarks // if (properties.xaxis && properties.xaxisTickmarks) { var numTickmarks = properties.xaxisTickmarksCount; var xInterval = this.axisWidth / numTickmarks; // Draw the left sides X tick marks if (properties.leftVisible) { for (i=0; i<properties.xaxisTickmarksCount; i++) { this.context.beginPath(); this.context.moveTo(i * xInterval + properties.marginLeft, this.canvas.height - this.marginBottom); this.context.lineTo(i * xInterval + properties.marginLeft, this.canvas.height - this.marginBottom + properties.xaxisTickmarksLength); this.context.closePath(); this.context.stroke(); } } if (properties.rightVisible) { for (i=(this.marginLeft + this.axisWidth + properties.marginCenter + xInterval); i<=(this.canvas.width - this.marginRight + 1); i+=xInterval) { this.context.beginPath(); this.context.moveTo(i, this.canvas.height - this.marginBottom); this.context.lineTo(i, (this.canvas.height - this.marginBottom) + properties.xaxisTickmarksLength); this.context.closePath(); this.context.stroke(); } } // Draw an exra tick if the Y axis isn't being shown // on each of the sides if (!properties.yaxis) { if (properties.leftVisible) { this.path( 'b m % % l % % s %', this.marginLeft + this.axisWidth,this.canvas.height - this.marginBottom, this.marginLeft + this.axisWidth,(this.canvas.height - this.marginBottom) + properties.xaxisTickmarksLength, this.context.strokeStyle ); } if (properties.rightVisible) { this.path( 'b m % % l % % s %', this.marginLeft + this.axisWidth + properties.marginCenter,this.canvas.height - this.marginBottom, this.marginLeft + this.axisWidth + properties.marginCenter,(this.canvas.height - this.marginBottom) + properties.xaxisTickmarksLength, this.context.strokeStyle ); } } } }; // // Figures out the maximum value, or if defined, uses xaxisScaleMax // this.getMax = function() { var dec = properties.xaxisScaleDecimals; // xaxisScaleMax defined if (properties.xaxisScaleMax) { var max = properties.xaxisScaleMax; var min = properties.xaxisScaleMin; this.scale2 = RGraph.getScale({object: this, options: { 'scale.max': max, 'scale.min': min, 'scale.strict': true, 'scale.thousand': properties.xaxisScaleThousand, 'scale.point': properties.xaxisScalePoint, 'scale.decimals': properties.xaxisScaleDecimals, 'scale.labels.count': properties.xaxisLabelsCount, 'scale.round': properties.xaxisScaleRound, 'scale.units.pre': properties.xaxisScaleUnitsPre, 'scale.units.post': properties.xaxisScaleUnitsPost }}); this.max = this.scale2.max; this.min = this.scale2.min; // // Generate the scale ourselves // } else { var max = 1; // Work out the max value for the left hand side for (var i=0; i<this.left.length; ++i) { if (typeof this.left[i] === 'number') { max = Math.max(max, this.left[i]); } else if (RGraph.isNullish(this.left[i])) { // Nada } else { max = Math.max(max, properties.grouping === 'stacked' ? RGraph.arraySum(this.left[i]) : RGraph.arrayMax(this.left[i])); } } // Work out the max value for the right hand side for (var i=0; i<this.right.length; ++i) { if (typeof this.right[i] === 'number') { max = Math.max(max, this.right[i]); } else if (RGraph.isNullish(this.right[i])) { // Nada } else { max = Math.max(max, properties.grouping === 'stacked' ? RGraph.arraySum(this.right[i]) : RGraph.arrayMax(this.right[i])); } } this.scale2 = RGraph.getScale({object: this, options: { 'scale.max': max, 'scale.min': properties.xaxisScaleMin, 'scale.thousand': properties.xaxisScaleThousand, 'scale.point': properties.xaxisScalePoint, 'scale.decimals': properties.xaxisScaleDecimals, 'scale.labels.count': properties.xaxisLabelsCount, 'scale.round': properties.xaxisScaleRound, 'scale.units.pre': properties.xaxisScaleUnitsPre, 'scale.units.post': properties.xaxisScaleUnitsPost }}); this.max = this.scale2.max; this.min = this.scale2.min; } // Don't need to return it as it is stored in this.max }; // Function to draw the left hand bars this.drawLeftBars = function () { // Allow the not-drawing of the left bars if (!properties.leftVisible) { return; } var opt = {}; if (typeof arguments[0] === 'object') { opt.shadow = arguments[0].shadow; } else { opt.shadow = true; } var offsetx = properties.variantThreedOffsetx, offsety = properties.variantThreedOffsety; // Set the stroke colour this.context.strokeStyle = properties.colorsStroke; // Set the linewidth this.context.lineWidth = properties.linewidth; // // If the colorsLeft option is set then change // the colors option to that. // if (!RGraph.isNullish(properties.colorsLeft)) { // Save the initial colors value properties.colorsInitial = properties.colors; // Set the new value properties.colors = properties.colorsLeft; } for (var i=0,sequentialColorIndex=0; i<this.left.length; ++i) { // // Turn on a shadow if requested // if (properties.shadow && properties.variant !== '3d' && opt.shadow) { RGraph.setShadow({ object: this, prefix: 'shadow' }); } if (typeof this.left[i] === 'number') { // If colorsSequential is specified - handle that // ** There's another instance of this further down ** if (properties.colorsSequential) { this.context.fillStyle = properties.colors[sequentialColorIndex]; } else { this.context.fillStyle = properties.colors[0]; // If there's only two colors then use them in the format of // one for each side. This facilitates easy coloring. //if (properties.colors.length === 2) { // this.context.fillStyle = properties.colors[0]; //} } // // Work out the coordinates // var width = (( (this.left[i] - this.min) / (this.max - this.min)) * this.axisWidth); var coords = [ this.marginLeft + this.axisWidth - width, this.marginTop + (i * ( this.axisHeight / this.left.length)) + properties.marginInner, width, this.barHeight ]; if (this.left[i] !== null) { this.context.strokeRect( coords[0], coords[1], coords[2], coords[3] ); this.context.fillRect( coords[0], coords[1], coords[2], coords[3] ); } // Draw the 3D sides if required if (properties.variant === '3d' && this.left[i] !== null) { // If the shadow is enabled draw the backface for // (that we don't actually see if (properties.shadow && opt.shadow) { this.context.shadowColor = properties.shadowColor; this.context.shadowBlur = properties.shadowBlur; this.context.shadowOffsetX = properties.shadowOffsetx; this.context.shadowOffsetY = properties.shadowOffsety; this.path( 'b m % % l % % l % % l % % f black sc rgba(0,0,0,0) sx 0 sy 0 sb 0', coords[0] + offsetx, coords[1] - offsety, coords[0] + offsetx + coords[2], coords[1] - offsety, coords[0] + offsetx + coords[2], coords[1] - offsety + coords[3], coords[0] + offsetx,coords[1] - offsety + coords[3] ); } // If colorsSequential is specified - handle that (again) // // ** There's another instance of this further up ** if (properties.colorsSequential) { this.context.fillStyle = properties.colors[i]; } else { this.context.fillStyle = properties.colors[0]; } this.path( 'b m % % l % % l % % l % % f', coords[0],coords[1], coords[0] + offsetx, coords[1] - offsety, coords[0] + offsetx + coords[2], coords[1] - offsety, coords[0] + coords[2], coords[1] ); this.path( 'b m % % l % % l % % l % % f rgba(255,255,255,0.4)', coords[0],coords[1], coords[0] + offsetx, coords[1] - offsety, coords[0] + offsetx + coords[2], coords[1] - offsety, coords[0] + coords[2], coords[1] ); } // Only store coordinates if this isn't a shadow iteration if (!opt.shadow) { // Add the coordinates to the coords array this.coords.push([ coords[0], coords[1], coords[2], coords[3] ]); this.coordsLeft.push([ coords[0], coords[1], coords[2], coords[3] ]); } sequentialColorIndex++; // A stacked Bipolar chart } else if (typeof this.left[i] === 'object' && properties.grouping === 'stacked') { for (var j=0,accumulatedWidth=0; j<this.left[i].length; ++j) { // If colorsSequential is specified - handle that // ** There's another instance of this further down ** if (properties.colorsSequential) { this.context.fillStyle = properties.colors[sequentialColorIndex]; } else { this.context.fillStyle = properties.colors[j]; } // // Work out the coordinates // var value = this.left[i][j], min = this.min, max = this.max, margin = properties.marginInner, width = (( (value - min) / (max - min)) * this.axisWidth), sectionHeight = (this.axisHeight / this.left.length), height = (sectionHeight - (2 * margin)), x = this.marginLeft + this.axisWidth - width - accumulatedWidth, y = this.marginTop + margin + (i * sectionHeight); accumulatedWidth += width; if (this.left[i] !== null) { this.context.strokeRect(x, y, width, height); this.context.fillRect(x, y, width, height); } // Draw the 3D sides if required ========================= if (properties.variant === '3d' && this.left[i] !== null) { // If the shadow is enabled draw the backface for // (that we don't actually see if (properties.shadow && opt.shadow) { this.context.shadowColor = properties.shadowColor; this.context.shadowBlur = properties.shadowBlur; this.context.shadowOffsetX = properties.shadowOffsetx; this.context.shadowOffsetY = properties.shadowOffsety; this.path( 'b m % % l % % l % % l % % f black sc rgba(0,0,0,0) sx 0 sy 0 sb 0', x + offsetx, y - offsety, x + offsetx + width, y - offsety, x + offsetx + width, y - offsety + height, x + offsetx,y - offsety + height ); } // If colorsSequential is specified - handle that (again) // // ** There's another instance of this further up ** if (properties.colorsSequential) { this.context.fillStyle = properties.colors[sequentialColorIndex]; } else { this.context.fillStyle = properties.colors[j]; } // Top side this.path( 'b m % % l % % l % % l % % f', x,y, x + offsetx, y - offsety, x + offsetx + width, y - offsety, x + width, y ); // top side again (to lighten it) this.path( 'b m % % l % % l % % l % % f rgba(255,255,255,0.4)', x,y, x + offsetx, y - offsety, x + offsetx + width, y - offsety, x + width, y ); } // ===== 3D ========================================== // Only store coordinates if this isn't a shadow iteration if (!opt.shadow) { // // Add the coordinates to the coords arrays // // The .coords array this.coords.push([ x, y, width, height ]); // The .coordsLeft array this.coordsLeft.push([ x, y, width, height ]); // The .coords2 array if (!RGraph.isArray(this.coords2[i])) { this.coords2[i] = []; } this.coords2[i].push([ x, y, width, height ]); // The .coords2Left array if (!RGraph.isArray(this.coords2Left[i])) { this.coords2Left[i] = []; } this.coords2Left[i].push([ x, y, width, height ]); } sequentialColorIndex++; } // A grouped Bipolar chart - and this is also the default } else if (typeof this.left[i] === 'object' && !RGraph.isNullish(this.left[i])) { for (var j=0; j<this.left[i].length; ++j) { // If colorsSequential is specified - handle that // ** There's another instance of this further down ** if (properties.colorsSequential) { this.context.fillStyle = properties.colors[sequentialColorIndex]; } else { this.context.fillStyle = properties.colors[j]; } // // Work out the coordinates // var value = this.left[i][j], min = this.min, max = this.max, margin = properties.marginInner, marginGrouped = properties.marginInnerGrouped, width = (( (value - min) / (max - min)) * this.axisWidth), sectionHeight = (this.axisHeight / this.left.length), height = (sectionHeight - (2 * margin) - ( (this.left[i].length - 1) * marginGrouped)) / this.left[i].length, x = this.marginLeft + this.axisWidth - width, y = this.marginTop + margin + (i * sectionHeight) + (height * j) + (j * marginGrouped); if (this.left[i] !== null) { this.context.strokeRect(x, y, width, height); this.context.fillRect(x, y, width, height); } // Draw the 3D sides if required if (properties.variant === '3d' && this.left[i] !== null) { // If the shadow is enabled draw the backface for // (that we don't actually see if (properties.shadow && opt.shadow) { this.context.shadowColor = properties.shadowColor; this.context.shadowBlur = properties.shadowBlur; this.context.shadowOffsetX = properties.shadowOffsetx; this.context.shadowOffsetY = properties.shadowOffsety; this.path( 'b m % % l % % l % % l % % f black sc rgba(0,0,0,0) sx 0 sy 0 sb 0', x + offsetx, y - offsety, x + offsetx + width, y - offsety, x + offsetx + width, y - offsety + height, x + offsetx,y - offsety + height ); } // If colorsSequential is specified - handle that (again) // // ** There's another instance of this further up ** if (properties.colorsSequential) { this.context.fillStyle = properties.colors[sequentialColorIndex]; } else { this.context.fillStyle = properties.colors[j]; } // Top side this.path( 'b m % % l % % l % % l % % f', x,y, x + offsetx, y - offsety, x + offsetx + width, y - offsety, x + width, y ); // top side again (to lighten it) this.path( 'b m % % l % % l % % l % % f rgba(255,255,255,0.4)', x,y, x + offsetx, y - offsety, x + offsetx + width, y - offsety, x + width, y ); } // Only store coordinates if this isn't a shadow iteration if (!opt.shadow) { // Add the coordinates to the coords arrays this.coords.push([ x, y, width, height ]); this.coordsLeft.push([ x, y, width, height ]); if (!RGraph.isArray(this.coords2[i])) { this.coords2[i] = []; } this.coords2[i].push([ x, y, width, height ]); if (!RGraph.isArray(this.coords2Left[i])) { this.coords2Left[i] = []; } this.coords2Left[i].push([ x, y, width, height ]); } sequentialColorIndex++; } } // Now draw the left vertical axis again so that it appears // over the bars this.draw3DLeftVerticalAxis(); } // // If the colorsLeft option is set then change // the colors option back to what it was. // if (!RGraph.isNullish(properties.colorsLeft)) { properties.colors = properties.colorsInitial; } // // Turn off any shadow // RGraph.noShadow(this); // Reset the linewidth this.context.lineWidth = 1; }; // // Function to draw the right hand bars // this.drawRightBars = function () { // Allow the not-drawing of the right bars if (!properties.rightVisible) { return; } var opt = {}; if (typeof arguments[0] === 'object') { opt.shadow = arguments[0].shadow; } else { opt.shadow = true; } var offsetx = properties.variantThreedOffsetx, offsety = properties.variantThreedOffsety; // Set the stroke colour this.context.strokeStyle = properties.colorsStroke; // Set the linewidth this.context.lineWidth = properties.linewidth; // // Turn on a shadow if requested // if (properties.shadow && properties.variant !== '3d' && opt.shadow) { this.context.shadowColor = properties.shadowColor; this.context.shadowBlur = properties.shadowBlur; this.context.shadowOffsetX = properties.shadowOffsetx; this.context.shadowOffsetY = properties.shadowOffsety; } // // If the colorsLeft option is set then change // the colors option to that. // if (!RGraph.isNullish(properties.colorsRight)) { // Save the initial colors value properties.colorsInitial = properties.colors; // Set the new value properties.colors = properties.colorsRight; } for (var i=0,sequentialColorIndex=RGraph.arrayLinearize(this.left).length; i<this.right.length; ++i) { if (typeof this.right[i] === 'number') { // If colorsSequential is specified - handle that if (properties.colorsSequential) { this.context.fillStyle = properties.colors[sequentialColorIndex]; } else { this.context.fillStyle = properties.colors[0]; } var width = (((this.right[i] - this.min) / (this.max - this.min)) * this.axisWidth); var coords = [ this.marginLeft + this.axisWidth + properties.marginCenter, properties.marginInner + (i * (this.axisHeight / this.right.length)) + this.marginTop, width, this.barHeight ]; if (this.right[i] !== null) { this.context.strokeRect( coords[0], coords[1], coords[2], coords[3] ); this.context.fillRect( coords[0], coords[1], coords[2], coords[3] ); } // Draw the 3D sides if required if (properties.variant === '3d' && this.right[i] !== null) { var color = this.context.fillStyle; // If the shadow is enabled draw the backface for // (that we don't actually see if (properties.shadow && opt.shadow) { this.context.shadowColor = properties.shadowColor; this.context.shadowBlur = properties.shadowBlur; this.context.shadowOffsetX = properties.shadowOffsetx; this.context.shadowOffsetY = properties.shadowOffsety; this.path( 'b m % % l % % l % % l % % f black sc rgba(0,0,0,0) sx 0 sy 0 sb 0', coords[0] + offsetx, coords[1] - offsety, coords[0] + offsetx + coords[2], coords[1] - offsety, coords[0] + offsetx + coords[2], coords[1] - offsety + coords[3], coords[0] + offsetx,coords[1] - offsety + coords[3] ); } // Draw the top this.path( 'b m % % l % % l % % l % % f %', coords[0],coords[1], coords[0] + offsetx, coords[1] - offsety, coords[0] + offsetx + coords[2], coords[1] - offsety, coords[0] + coords[2], coords[1], color ); // Draw the right hand side this.path( 'b m % % l % % l % % l % % f %', coords[0] + coords[2],coords[1], coords[0] + coords[2] + offsetx, coords[1] - offsety, coords[0] + coords[2] + offsetx, coords[1] - offsety + coords[3], coords[0] + coords[2],coords[1] + coords[3], color ); // Draw the LIGHTER top this.path( 'b m % % l % % l % % l % % f rgba(255,255,255,0.6)', coords[0],coords[1], coords[0] + offsetx, coords[1] - offsety, coords[0] + offsetx + coords[2], coords[1] - offsety, coords[0] + coords[2], coords[1] ); // Draw the DARKER right hand side this.path( 'b m % % l % % l % % l % % f rgba(0,0,0,0.3)', coords[0] + coords[2],coords[1], coords[0] + coords[2] + offsetx, coords[1] - offsety, coords[0] + coords[2] + offsetx, coords[1] - offsety + coords[3], coords[0] + coords[2],coords[1] + coords[3] ); } // Only store coordinates if this isn't a shadow iteration if (!opt.shadow) { // // Add the coordinates to the coords array // // The .coords array this.coords.push([ coords[0], coords[1], coords[2], coords[3] ]); // The .coordsRight array this.coordsRight.push([ coords[0], coords[1], coords[2], coords[3] ]); } // Does this need to be here? sequentialColorIndex++; // A stacked Bipolar chart } else if (typeof this.left === 'object' && properties.grouping === 'stacked') { for (var j=0,accumulatedWidth=0; j<this.right[i].length; ++j) { // If colorsSequential is specified - handle that // ** There's another instance of this further down ** if (properties.colorsSequential) { this.context.fillStyle = properties.colors[sequentialColorIndex]; } else { this.context.fillStyle = properties.colors[j]; } // // Work out the coordinates // var value = this.right[i][j], min = this.min, max = this.max, margin = properties.marginInner, width = (( (value - min) / (max - min)) * this.axisWidth), sectionHeight = (this.axisHeight / this.right.length), height = (sectionHeight - (2 * margin)), x = this.marginLeft + this.axisWidth + properties.marginCenter + accumulatedWidth, y = this.marginTop + margin + (i * sectionHeight); accumulatedWidth += width; if (this.right[i] !== null) { this.context.strokeRect(x, y, width, height); this.context.fillRect(x, y, width, height); } // Draw the 3D sides if required if (properties.variant === '3d' && this.right[i] !== null) { var color = this.context.fillStyle; // If the shadow is enabled draw the backface for // (that we don't actually see if (properties.shadow && opt.shadow) { RGraph.setShadow({ object: this, prefix: 'shadow' }); this.path( 'b m % % l % % l % % l % % f black sc rgba(0,0,0,0) sx 0 sy 0 sb 0', x + offsetx, y - offsety, x + offsetx + width, y - offsety, x + offsetx + width, y - offsety + height, x + offsetx, y - offsety + height ); } // Draw the top this.path( 'b m % % l % % l % % l % % f %', x, y, x + offsetx, y - offsety, x + offsetx + width, y - offsety, x + width, y, color ); // Draw the right hand side - but only // if this the most right hand side segment if (j === (this.right[i].length - 1)) { this.path( 'b m % % l % % l % % l % % f %', x + width,y, x + width + offsetx, y - offsety, x + width + offsetx, y - offsety + height, x + width,y + height, color ); // Draw the DARKER right hand side this.path( 'b m % % l % % l % % l % % f rgba(0,0,0,0.3)', x + width,y, x + width + offsetx, y - offsety, x + width + offsetx, y - offsety + height, x + width,y + height ); } // Draw the LIGHTER top this.path( 'b m % % l % % l % % l % % f rgba(255,255,255,0.6)', x,y, x + offsetx, y - offsety, x + offsetx + width, y - offsety, x + width, y ); } // Only store coordinates if this isn't a shadow iteration if (!opt.shadow) { // Add the coordinates to the coords arrays this.coords.push([ x, y, width, height ]); // The .coords2 array if (!RGraph.isArray(this.coords2[sequentialColorIndex])) { this.coords2[sequentialColorIndex] = []; } this.coords2[sequentialColorIndex].push([ x, y, width, height ]); this.coordsRight.push([ x, y, width, height ]); // The .coords2Right array if (!RGraph.isArray(this.coords2Right[i])) { this.coords2Right[i] = []; } this.coords2Right[i].push([ x, y, width, height ]); } sequentialColorIndex++; } // Draw a grouped Bipolar chart, this is also the default } else if (typeof this.right[i] === 'object') { for (var j=0; j<this.right[i].length; ++j) { // If colorsSequential is specified - handle that // ** There's another instance of this further down ** if (properties.colorsSequential) { this.context.fillStyle = properties.colors[sequentialColorIndex]; } else { this.context.fillStyle = properties.colors[j]; } // // Work out the coordinates // var value = this.right[i][j], min = this.min, max = this.max, margin = properties.marginInner, marginGrouped = properties.marginInnerGrouped, width = ( (value - min) / (max - min)) * this.axisWidth, sectionHeight = (this.axisHeight / this.right.length), height = (sectionHeight - (2 * margin) - ( (this.right[i].length - 1) * marginGrouped)) / this.right[i].length, x = this.marginLeft + this.axisWidth + properties.marginCenter, y = this.marginTop + margin + (i * sectionHeight) + (height * j) + (j * marginGrouped); if (this.right[i] !== null) { this.context.strokeRect(x, y, width, height); this.context.fillRect(x, y, width, height); } // Only store coordinates if this isn't a shadow iteration if (!opt.shadow) { // Add the coordinates to the coords arrays this.coords.push([ x, y, width, height ]); this.coordsRight.push([ x, y, width, height ]); // The .coords2 array if (!RGraph.isArray(this.coords2[this.left.length + i])) { this.coords2[this.left.length + i] = []; } this.coords2[this.left.length + i].push([ x, y, width, height ]); if (!RGraph.isArray(this.coords2Right[i])) { this.coords2Right[i] = []; } this.coords2Right[i].push([ x, y, width, height ]); } sequentialColorIndex++; // Draw the 3D sides if required if (properties.variant === '3d' && this.right[i] !== null) { var color = this.context.fillStyle; // If the shadow is enabled draw the backface for // (that we don't actually see if (properties.shadow && opt.shadow) { this.context.shadowColor = properties.shadowColor; this.context.shadowBlur = properties.shadowBlur; this.context.shadowOffsetX = properties.shadowOffsetx; this.context.shadowOffsetY = properties.shadowOffsety; this.path( 'b m % % l % % l % % l % % f black sc rgba(0,0,0,0) sx 0 sy 0 sb 0', x + offsetx, y - offsety, x + offsetx + width, y - offsety, x + offsetx + width, y - offsety + height, x + offsetx, y - offsety + height ); } // Draw the top this.path( 'b m % % l % % l % % l % % f %', x, y, x + offsetx, y - offsety, x + offsetx + width, y - offsety, x + width, y, color ); // Draw the right hand side this.path( 'b m % % l % % l % % l % % f %', x + width,y, x + width + offsetx, y - offsety, x + width + offsetx, y - offsety + height, x + width,y + height, color ); // Draw the LIGHTER top this.path( 'b m % % l % % l % % l % % f rgba(255,255,255,0.6)', x,y, x + offsetx, y - offsety, x + offsetx + width, y - offsety, x + width, y ); // Draw the DARKER right hand side this.path( 'b m % % l % % l % % l % % f rgba(0,0,0,0.3)', x + width,y, x + width + offsetx, y - offsety, x + width + offsetx, y - offsety + height, x + width,y + height ); } } } } // If the colorsRight option is set then change // the colors option back to what it was. // if (!RGraph.isNullish(properties.colorsRight)) { properties.colors = properties.colorsInitial; } // // Turn off any shadow // RGraph.noShadow(this); // Reset the linewidth this.context.lineWidth = 1; }; // // Draws the labels // this.drawLabels = function () { var labels = properties.yaxisLabels, barAreaHeight = this.canvas.height - this.marginTop - this.marginBottom // Get the text configuration var textConf = RGraph.getTextConf({ object: this, prefix: 'yaxisLabels' }); this.context.fillStyle = textConf.color; for (var i=0,len=labels.length; i<len; ++i) { var ret = RGraph.text({ object: this, color: textConf.color, font: textConf.font, size: textConf.size, bold: textConf.bold, italic: textConf.italic, x: this.marginLeft + this.axisWidth + (properties.marginCenter / 2) + properties.yaxisLabelsOffsetx, y: this.marginTop + ((barAreaHeight / labels.length) * (i)) + ((barAreaHeight / labels.length) / 2) + properties.yaxisLabelsOffsety, text: String(labels[i] ? String(labels[i]) : ''), halign: 'center', valign: 'center', marker: false, tag: 'labels', cssClass: RGraph.getLabelsCSSClassName({ object: this, name: 'yaxisLabelsClass', index: i }) }); } this.context.fillStyle = properties.textColor; if (properties.xaxisLabels) { // Determine a few things var grapharea = (this.canvas.width - properties.marginCenter - this.marginLeft - this.marginRight) / 2; var textConf = RGraph.getTextConf({ object: this, prefix: 'xaxisLabels' }); // Now draw the X labels for the left hand side if (properties.leftVisible || properties.rightVisible) { // Determine how many labels ther are var len = this.scale2.labels.length; if (this.properties.xaxisScaleSpecific && this.properties.xaxisScaleSpecific.length) { len = (this.properties.xaxisScaleSpecific.length); this.properties.xaxisScaleZerostart = false; } for (var i=0; i<len; ++i) { // Draw the scale for the left-hand-side if (properties.leftVisible) { RGraph.text({ object: this, font: textConf.font, size: textConf.size, bold: textConf.bold, italic: textConf.italic, color: textConf.color, x: this.marginLeft + ((grapharea / (!RGraph.isNullish(this.properties.xaxisScaleSpecific) ? len - 1 : len)) * i) - properties.xaxisLabelsOffsetx, y: this.canvas.height - this.marginBottom + 4 + properties.xaxisLabelsOffsety + properties.xaxisTickmarksLength, text: (this.properties.xaxisScaleSpecific && typeof this.properties.xaxisScaleSpecific[i] === 'string') ? this.properties.xaxisScaleSpecific[i] : (typeof properties.xaxisScaleFormatter === 'function' ? (properties.xaxisScaleFormatter)(this, this.scale2.values[len - i - 1]) : this.scale2.labels[len - i - 1]), valign: 'top', halign: 'center', tag: 'scale' }); } if (RGraph.isArray(this.properties.xaxisScaleSpecific)) { var reversed_labels = this.properties.xaxisScaleSpecific.toReversed(); } // Draw the scale for the right-hand-side if (properties.rightVisible) { RGraph.text({ object: this, font: textConf.font, size: textConf.size, bold: textConf.bold, italic: textConf.italic, color: textConf.color, x: this.marginLeft + grapharea + properties.marginCenter + ((grapharea / (!RGraph.isNullish(this.properties.xaxisScaleSpecific) ? len - 1 : len) ) * (i + (RGraph.isArray(this.properties.xaxisScaleSpecific) ? 0 : 1) )) + properties.xaxisLabelsOffsetx, y: this.canvas.height - this.marginBottom + 4 + properties.xaxisLabelsOffsety + properties.xaxisTickmarksLength, text: (this.properties.xaxisScaleSpecific && typeof this.properties.xaxisScaleSpecific[i] === 'string') ? reversed_labels[i] : (typeof properties.xaxisScaleFormatter === 'function' ? (properties.xaxisScaleFormatter)(this, this.scale2.values[i]) : this.scale2.labels[i]), valign: 'top', halign: 'center', tag: 'scale' }); } } } // Draw zero? if (properties.xaxisScaleZerostart) { // Draw zero for the left-hand-side if (properties.leftVisible) { RGraph.text({ object: this, font: textConf.font, size: textConf.size, bold: textConf.bold, italic: textConf.italic, color: textConf.color, x: this.marginLeft + this.axisWidth - properties.xaxisLabelsOffsetx, y: this.canvas.height - this.marginBottom + 4 + properties.xaxisLabelsOffsety + properties.xaxisTickmarksLength, text: typeof properties.xaxisScaleFormatter === 'function' ? (properties.xaxisScaleFormatter)(this, 0) : RGraph.numberFormat({ object: this, number: (0).toFixed(properties.xaxisScaleDecimals), unitspre: properties.xaxisScaleUnitsPre, unitspost: properties.xaxisScaleUnitsPost }), valign: 'top', halign: 'center', tag: 'scale' }); } // Draw zero for the right-hand-side if (properties.rightVisible) { RGraph.text({ object: this, font: textConf.font, size: textConf.size, bold: textConf.bold, italic: textConf.italic, color: textConf.color, x: this.marginLeft + this.axisWidth + this.marginCenter + properties.xaxisLabelsOffsetx, y: this.canvas.height - this.marginBottom + 4 + properties.xaxisLabelsOffsety + properties.xaxisTickmarksLength, text: typeof properties.xaxisScaleFormatter === 'function' ? (properties.xaxisScaleFormatter)(this, 0) : RGraph.numberFormat({ object: this, number: (0).toFixed(properties.xaxisScaleDecimals), unitspre: properties.xaxisScaleUnitsPre, unitspost: properties.xaxisScaleUnitsPost }), valign: 'top', halign: 'center', tag: 'scale' }); } } } // // Draw above labels // if (properties.labelsAbove) { this.drawLabelsAbove(); } }; // This function draws the above labels this.drawLabelsAbove = function () { var coordsLeft = this.coordsLeft, coordsRight = this.coordsRight; // Get the text configuration var textConf = RGraph.getTextConf({ object: this, prefix: 'labelsAbove' }); // Draw the left sides above labels for (var i=0,seq=0; i<coordsLeft.length; ++i, ++seq) { if (typeof this.left[i] == 'number') { var coords = this.coords[seq]; RGraph.text({ object: this, font: textConf.font, size: textConf.size, bold: textConf.bold, italic: textConf.italic, color: textConf.color, x: coords[0] - 5 - properties.labelsAboveOffsetx, y: coords[1] + (coords[3] / 2) + properties.labelsAboveOffsety, text: typeof properties.labelsAboveFormatter === 'function' ? properties.labelsAboveFormatter(this, this.left[i]) : RGraph.numberFormat({ object: this, number: this.left[i].toFixed(typeof properties.labelsAboveDecimals === 'number' ? properties.labelsAboveDecimals : 0), unitspre: properties.labelsAboveUnitsPre, unitspost: properties.labelsAboveUnitsPost }), valign: 'center', halign: 'right', tag: 'labels.above' }); // A grouped or stacked chart } else if (typeof this.left[i] === 'object') { // Loop through the group for (var j=0; j<this.left[i].length; ++j,++seq) { // Stacked charts only show the above label on the last // segment of the bar if (properties.grouping === 'stacked' && j !== (this.left[i].length - 1) ) { continue; } var coords = coordsLeft[seq]; RGraph.text({ object: this, font: textConf.font, size: textConf.size, bold: textConf.bold, italic: textConf.italic, color: textConf.color, x: coords[0] - 5 - properties.labelsAboveOffsetx, y: coords[1] + (coords[3] / 2) + properties.labelsAboveOffsety, text: typeof properties.labelsAboveFormatter === 'function' ? properties.labelsAboveFormatter(this, this.left[i][j]) : RGraph.numberFormat({ object: this, number: RGraph.isNullish(this.left[i][j]) || isNaN(this.left[i][j]) ? '' : (properties.grouping === 'stacked' ? RGraph.arraySum(this.left[i]): Number(this.left[i][j])).toFixed(typeof properties.labelsAboveDecimals === 'number' ? properties.labelsAboveDecimals : 0), unitspre: properties.labelsAboveUnitsPre, unitspost: properties.labelsAboveUnitsPost }), valign: 'center', halign: 'right', tag: 'labels.above' }); } seq--; } } // Draw the right sides above labels for (i=0,seq=0; i<coordsRight.length; ++i,++seq) { if (typeof this.right[i] === 'number') { var coords = coordsRight[seq]; RGraph.text({ object: this, font: textConf.font, size: textConf.size, bold: textConf.bold, italic: textConf.italic, color: textConf.color, x: coords[0] + coords[2] + 5 + (properties.variant === '3d' ? 10 : 0) + properties.labelsAboveOffsetx, y: coords[1] + (coords[3] / 2) + (properties.variant === '3d' ? -5 : 0) + properties.labelsAboveOffsety, text: typeof properties.labelsAboveFormatter === 'function' ? properties.labelsAboveFormatter(this, this.right[i]) : RGraph.numberFormat({ object: this, number: this.right[i].toFixed(typeof properties.labelsAboveDecimals === 'number' ? properties.labelsAboveDecimals : 0), unitspre: properties.labelsAboveUnitsPre, unitspost: properties.labelsAboveUnitsPost }), valign: 'center', halign: 'left', tag: 'labels.above' }); // A grouped/stacked chart } else if (typeof this.right[i] === 'object') { // Loop through the group for (var j=0; j<this.right[i].length; ++j,++seq) { // Stacked charts only show the above label on the last // segment of the bar if (properties.grouping === 'stacked' && j !== (this.right[i].length - 1)) { continue; } var coords = coordsRight[seq]; RGraph.text({ object: this, font: textConf.font, size: textConf.size, bold: textConf.bold, italic: textConf.italic, color: textConf.color, x: coords[0] + coords[2] + 5 + (properties.variant === '3d' ? 10 : 0) + properties.labelsAboveOffsetx, y: coords[1] + (coords[3] / 2) + (properties.variant === '3d' ? -5 : 0) + properties.labelsAboveOffsety, text: typeof properties.labelsAboveFormatter === 'function' ? properties.labelsAboveFormatter(this, this.right[i][j]) : RGraph.numberFormat({ object: this, number: RGraph.isNullish(this.right[i][j]) || isNaN(this.right[i][j]) ? '' : properties.grouping === 'stacked' ? RGraph.arraySum(this.right[i]).toFixed(properties.labelsAboveDecimals) : Number(this.right[i][j]).toFixed(typeof properties.labelsAboveDecimals === 'number' ? properties.labelsAboveDecimals : 0), unitspre: properties.labelsAboveUnitsPre, unitspost: properties.labelsAboveUnitsPost }), valign: 'center', halign: 'left', tag: 'labels.above' }); } --seq; } } }; // // Draws the titles // this.drawTitles = function () { // Make sure that the title subtitle are strings properties.titleLeft = String(properties.titleLeft); properties.titleRight = String(properties.titleRight); properties.title = String(properties.title); // Draw the left title if (properties.titleLeft) { // Get the text configuration var textConf = RGraph.getTextConf({ object: this, prefix: 'titleLeft' }); RGraph.text({ object: this, font: textConf.font, size: textConf.size, bold: textConf.bold, italic: textConf.italic, color: textConf.color, x: this.marginLeft + 5 + properties.titleLeftOffsetx, y: this.marginTop - 5 + properties.titleLeftOffsety, text: properties.titleLeft, halign:'left', valign: 'bottom', tag: 'title.left' }); } // Draw the right title if (properties.titleRight) { // Get the text configuration var textConf = RGraph.getTextConf({ object: this, prefix: 'titleRight' }); RGraph.text({ object: this, font: textConf.font, size: textConf.size, bold: textConf.bold, italic: textConf.italic, color: textConf.color, x: this.canvas.width - this.marginRight - 5 + properties.titleRightOffsetx, y: this.marginTop - 5 + properties.titleRightOffsety, text: properties.titleRight, halign: 'right', valign: 'bottom', tag: 'title.right' }); } // Draw the main title for the whole chart if (properties.title) { RGraph.drawTitle( this, properties.title, this.marginTop, null, typeof properties.titleSize === 'number' ? properties.titleSize : null ); } }; // // Returns the appropriate focussed bar coordinates // // @param e object The event object // this.getShape = function (e) { var canvas = this.canvas, context = this.context, mouseXY = RGraph.getMouseXY(e), side = 0; // Default to the left side // // Loop through the bars determining if the mouse is over a bar // for (var i=0; i<this.coords.length; i++) { if (RGraph.tooltipsHotspotIgnore(this, i)) { continue; } var mouseX = mouseXY[0], mouseY = mouseXY[1], left = this.coords[i][0], top = this.coords[i][1], width = this.coords[i][2], height = this.coords[i][3] //if (properties.variant === '3d') { // Now ( 23/10/2019 use path checking always - not just for 3D charts this.path( 'b r % % % %', left,top,width,height ); var over = this.context.isPointInPath(mouseX, mouseY) && (this.properties.clip ? RGraph.clipTo.test(this, mouseX, mouseY) : true); // Is the mouse cursor over a shape? if (over) { if (RGraph.parseTooltipText) { var tooltip = RGraph.parseTooltipText(properties.tooltips, i); } var indexes = RGraph.sequentialIndexToGrouped(i, this.data2), group = indexes[0], index = indexes[1], group2 = group; // Work out the group2 variable if ( (group +1) > this.left.length) { group2 -= this.left.length; side = 1;// Right-hand-side } return { object: this, x: left, y: top, width: width, height: height, tooltip: typeof tooltip === 'string' ? tooltip : null, side: side, sequentialIndex: i, index: index, dataset: group, dataset2: group2, label: properties.yaxisLabels && typeof properties.yaxisLabels[group2] === 'string' ? properties.yaxisLabels[group2] : null }; } } return null; }; // // Each object type has its own Highlight() function which highlights the appropriate shape // // @param object shape The shape to highlight // this.highlight = function (shape) { if (typeof properties.highlightStyle === 'function') { (properties.highlightStyle)(shape); // Highlight all of the rects except this one - essentially an inverted highlight } else if (typeof properties.highlightStyle === 'string' && properties.highlightStyle === 'invert') { for (var i=0; i<this.coords.length; ++i) { if (i !== shape.sequentialIndex) { this.path( 'b r % % % % s % f %', this.coords[i][0] - 0.5, this.coords[i][1] - 0.5, this.coords[i][2] + 1, this.coords[i][3] + 1, properties.highlightStroke, properties.highlightFill ); } } this.drawAxes(); } else { RGraph.Highlight.rect(this, shape); this.drawAxes(); } }; // // When you click on the canvas, this will return the relevant value (if any) // // REMEMBER This function will need updating if the Bipolar ever gets yaxisScaleMin // // @param object e The event object // this.getValue = function (e) { var obj = e.target.__object__; var mouseXY = RGraph.getMouseXY(e); var mouseX = mouseXY[0]; // // Left hand side // if (mouseX > this.marginLeft && mouseX < ( (this.canvas.width / 2) - (properties.marginCenter / 2) )) { var value = (mouseX - properties.marginLeft) / this.axisWidth; value = this.max - (value * this.max); } // // Right hand side // if (mouseX < (this.canvas.width - this.marginRight) && mouseX > ( (this.canvas.width / 2) + (properties.marginCenter / 2) )) { var value = (mouseX - properties.marginLeft - this.axisWidth - properties.marginCenter) / this.axisWidth; value = (value * this.max); } return value; }; // // 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); if (properties.variant === '3d' && !properties.textAccessible) { var adjustment = properties.variantThreedAngle * mouseXY[0]; mouseXY[1] -= adjustment; } if ( mouseXY[0] > properties.marginLeft && mouseXY[0] < (this.canvas.width - properties.marginRight) && mouseXY[1] > properties.marginTop && mouseXY[1] < (this.canvas.height - properties.marginBottom) ) { return this; } }; // // Returns the X coords for a value. Returns two coords because there are... two scales. // // @param number value The value to get the coord for // this.getXCoord = function (value) { if (value > this.max || value < 0) { return null; } var ret = []; // The offset into the graph area var offset = ((value / this.max) * this.axisWidth); // Get the coords (one fo each side) ret[0] = (this.marginLeft + this.axisWidth) - offset; ret[1] = (this.canvas.width - this.marginRight - this.axisWidth) + offset; return ret; }; // // 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.highlightStroke = RGraph.arrayClone(properties.highlightStroke); this.original_colors.highlightFill = RGraph.arrayClone(properties.highlightFill); this.original_colors.axesColor = RGraph.arrayClone(properties.axesColor); this.original_colors.colorsStroke = RGraph.arrayClone(properties.colorsStroke); this.original_colors.colorsLeft = RGraph.arrayClone(properties.colorsLeft); this.original_colors.colorsRight = RGraph.arrayClone(properties.colorsRight); this.original_colors.keyColors = RGraph.arrayClone(properties.keyColors); } var colors = properties.colors; for (var i=0; i<colors.length; ++i) { colors[i] = this.parseSingleColorForGradient(colors[i]); } if (RGraph.isArray(properties.colorsLeft)) { for (var i=0; i<properties.colorsLeft.length; ++i) { properties.colorsLeft[i] = this.parseSingleColorForGradient(properties.colorsLeft[i]); } } if (RGraph.isArray(properties.colorsRight)) { for (var i=0; i<properties.colorsRight.length; ++i) { properties.colorsRight[i] = this.parseSingleColorForGradient(properties.colorsRight[i]); } } // keyColors var colors = properties.keyColors; if (colors) { for (var i=0; i<colors.length; ++i) { colors[i] = this.parseSingleColorForGradient(colors[i]); } } properties.highlightStroke = this.parseSingleColorForGradient(properties.highlightStroke); properties.highlightFill = this.parseSingleColorForGradient(properties.highlightFill); properties.axesColor = this.parseSingleColorForGradient(properties.axesColor); properties.colorsStroke = this.parseSingleColorForGradient(properties.colorsStroke); }; // // Use this function to reset the object to the post-constructor state. Eg reset colors if // need be etc // this.reset = function () { }; // // This parses a single color value // this.parseSingleColorForGradient = function (color) { if (!color || typeof color != 'string') { return color; } if (color.match(/^gradient\((.*)\)$/i)) { // Allow for JSON gradients if (color.match(/^gradient\(({.*})\)$/i)) { return RGraph.parseJSONGradient({object: this, def: RegExp.$1}); } var parts = RegExp.$1.split(':'); // Create the gradient var grad = this.context.createLinearGradient(properties.marginLeft,0,this.canvas.width - properties.marginRight,0); var diff = 1 / (parts.length - 1); grad.addColorStop(0, RGraph.trim(parts[0])); for (var j=1; j<parts.length; ++j) { grad.addColorStop(j * diff, RGraph.trim(parts[j])); } } return grad ? grad : color; }; // // This function handles highlighting an entire data-series for the interactive // key // // @param int index The index of the data series to be highlighted // this.interactiveKeyHighlight = function (index) { for (var i=0; i<this.coords2.length; ++i) { var coords = this.coords2[i][index]; // Draw some highlight over the top of the bar this.path( 'b r % % % % s % f %', coords[0], coords[1], coords[2], coords[3], properties.keyInteractiveHighlightChartStroke, properties.keyInteractiveHighlightChartFill ); } }; // // Using a function to add events makes it easier to facilitate method chaining // // @param string type The type of even to add // @param function func // this.on = function (type, func) { if (type.substr(0,2) !== 'on') { type = 'on' + type; } if (typeof this[type] !== 'function') { this[type] = func; } else { RGraph.addCustomEventListener(this, type, func); } return this; }; // // Draw the background grid // this.drawBackgroundGrid = function () { if (properties.backgroundGrid) { var variant = properties.variant, color = properties.backgroundGridColor, numvlines = properties.xaxisLabelsCount, // TODO Should this be based on the data - not the labels...? numhlines = this.left.length, vlines = properties.backgroundGridVlines, hlines = properties.backgroundGridHlines, linewidth = properties.backgroundGridLinewidth; // Autofit if (typeof properties.backgroundGridHlinesCount === 'number') { numhlines = properties.backgroundGridHlinesCount; } if (typeof properties.backgroundGridVlinesCount === 'number') { numvlines = properties.backgroundGridVlinesCount; } this.context.lineWidth = linewidth; // If it's a bar and 3D variant, translate if (variant == '3d') { this.context.save(); this.context.translate( properties.variantThreedOffsetx, -1 * properties.variantThreedOffsety ); } // Draw vertical grid lines for the left side if (properties.leftVisible) { if (vlines) { for (var i=0; i<=numvlines; i+=1) { this.path( 'b m % % l % % s %', this.marginLeft + (this.axisWidth / numvlines) * i, this.marginTop, this.marginLeft + (this.axisWidth / numvlines) * i, this.marginTop + this.axisHeight, color ); } } // Draw horizontal grid lines for the left side if (hlines) { for (var i=0; i<=numhlines; i+=1) { this.path( 'b m % % l % % s %', this.marginLeft, this.marginTop + (this.axisHeight / numhlines) * i, this.marginLeft + this.axisWidth, this.marginTop + (this.axisHeight / numhlines) * i, color ); } } } // Draw vertical grid lines for the right side if (properties.rightVisible) { if (vlines) { for (var i=0; i<=numvlines; i+=1) { this.path( 'b m % % l % % s %', this.marginLeft + this.marginCenter + this.axisWidth + (this.axisWidth / numvlines) * i, this.marginTop, this.marginLeft + this.marginCenter + this.axisWidth + (this.axisWidth / numvlines) * i, this.marginTop + this.axisHeight, color ); } } // Draw horizontal grid lines for the right side if (hlines) { for (var i=0; i<=numhlines; i+=1) { this.path( 'b m % % l % % s %', this.marginLeft + this.axisWidth + this.marginCenter, this.marginTop + (this.axisHeight / numhlines) * i, this.marginLeft + this.axisWidth + this.marginCenter + this.axisWidth, this.marginTop + (this.axisHeight / numhlines) * i, color ); } } } // If it's a bar and 3D variant, translate if (variant == '3d') { this.context.restore(); } } }; // // This function runs once only // (put at the end of the file (before any effects)) // this.firstDrawFunc = function () { }; // // Calulate the center margin size // this.getMarginCenter = function () { var bold = typeof properties.yaxisLabelsBold === 'boolean' ? properties.yaxisLabelsBold : properties.textBold, font = typeof properties.yaxisLabelsFont === 'string' ? properties.yaxisLabelsFont : properties.textFont, size = typeof properties.yaxisLabelsSize === 'number' ? properties.yaxisLabelsSize : properties.textSize; // Loop through the labels measuring them for (var i=0,len=0; i<properties.yaxisLabels.length; ++i) { len = Math.max(len, RGraph.measureText( properties.yaxisLabels[i], bold, font, size )[0]); } return len + 25; }; // // Grow // // The Bipolar chart Grow effect gradually increases the values of the bars // // @param object An object of options - eg: {frames: 30} // @param function A function to call when the effect is complete // this.grow = function () { // Cancel any stop request if one is pending this.cancelStopAnimation(); // Callback var opt = arguments[0] || {}, frames = opt.frames || 30, frame = 0, callback = arguments[1] || function () {}, obj = this; // Save the data var originalLeft = RGraph.arrayClone(this.left), originalRight = RGraph.arrayClone(this.right); // Stop the scale from changing by setting xaxisScaleMax (if it's // not already set) if (RGraph.isNullish(properties.xaxisScaleMax)) { var xmax = 0; // Get the max values this.getMax(); this.set('xaxisScaleMax', this.scale2.max); } var iterator = function () { if (obj.stopAnimationRequested_left || obj.stopAnimationRequested_right) { // Reset the flag obj.stopAnimationRequested_left = false; obj.stopAnimationRequested_right = false; // Reset the data obj.left = RGraph.arrayClone(originalLeft); obj.right = RGraph.arrayClone(originalRight); return; } var easingMultiplier = RGraph.Effects.getEasingMultiplier(frames, frame); // Left hand side for (var i=0; i<obj.left.length; i+=1) { if (typeof obj.left[i] === 'number') { obj.left[i] = easingMultiplier * originalLeft[i]; } else { for (var j=0; j<obj.left[i].length; ++j) { obj.left[i][j] = easingMultiplier * originalLeft[i][j]; } } } // Right hand side for (var i=0; i<obj.right.length; i+=1) { if (typeof obj.right[i] === 'number') { obj.right[i] = easingMultiplier * originalRight[i]; } else { for (var j=0; j<obj.right[i].length; ++j) { obj.right[i][j] = easingMultiplier * originalRight[i][j]; } } } RGraph.redrawCanvas(obj.canvas); // Repeat or call the end function if one is defined if (frame < frames) { frame += 1; RGraph.Effects.updateCanvas(iterator); } else { callback(obj); } }; iterator(); return this; }; // // Bipolar chart Wave effect. // // @param object OPTIONAL An object map of options. You specify 'frames' here to give the number of frames in the effect // @param function OPTIONAL A function that will be called when the effect is complete // this.wave = function () { this.cancelStopAnimation(); var obj = this, opt = arguments[0] || {}; opt.frames = opt.frames || 120; opt.startFrames_left = []; opt.startFrames_right = []; opt.counters_left = []; opt.counters_right = []; var framesperbar = opt.frames / 3, frame_left = -1, frame_right = -1, callback = arguments[1] || function () {}, original_left = RGraph.arrayClone(obj.left), original_right = RGraph.arrayClone(obj.right); for (var i=0,len=obj.left.length; i<len; i+=1) { opt.startFrames_left[i] = ((opt.frames / 3) / (obj.left.length - 1)) * i; opt.startFrames_right[i] = ((opt.frames / 3) / (obj.right.length - 1)) * i; opt.counters_left[i] = 0; opt.counters_right[i] = 0; } // This stops the chart from jumping obj.draw(); obj.set('xaxisScaleMax', obj.scale2.max); RGraph.clear(obj.canvas); // Zero all of the data for (var i=0,len=obj.left.length; i<len; i+=1) { if (typeof obj.left[i] === 'number') obj.left[i] = 0; if (typeof obj.right[i] === 'number') obj.right[i] = 0; } // // Iterate over the left side // function iteratorLeft () { if (obj.stopAnimationRequested_left) { // Reset the flag obj.stopAnimationRequested_left = false; // Reset the data obj.left = RGraph.arrayClone(original_left); return; } ++frame_left; for (var i=0,len=obj.left.length; i<len; i+=1) { if (frame_left > opt.startFrames_left[i]) { var isNullish = RGraph.isNullish(obj.left[i]); // Regular bars if (typeof obj.left[i] === 'number') { obj.left[i] = Math.min( Math.abs(original_left[i]), Math.abs(original_left[i] * ( (opt.counters_left[i]++) / framesperbar)) ); // Make the number negative if the original was if (original_left[i] < 0) { obj.left[i] *= -1; } // Stacked or grouped bars } else if (RGraph.isArray(obj.left[i])) { for (var j=0; j<obj.left[i].length; ++j) { obj.left[i][j] = Math.min( Math.abs(original_left[i][j]), Math.abs(original_left[i][j] * ( (opt.counters_left[i]++) / framesperbar)) ); // Make the number negative if the original was if (original_left[i][j] < 0) { obj.left[i][j] *= -1; } } } if (isNullish) { obj.left[i] = null; } } else { obj.left[i] = typeof obj.left[i] === 'object' && obj.left[i] ? RGraph.arrayPad([], obj.left[i].length, 0) : (RGraph.isNullish(obj.left[i]) ? null : 0); } } // No callback here - only called by the right function if (frame_left < opt.frames) { RGraph.redrawCanvas(obj.canvas); RGraph.Effects.updateCanvas(iteratorLeft); } } // // Iterate over the right side // function iteratorRight () { if (obj.stopAnimationRequested_right) { // Reset the flag obj.stopAnimationRequested_right = false; // Reset the data obj.right = RGraph.arrayClone(original_right); return; } ++frame_right; for (var i=0,len=obj.right.length; i<len; i+=1) { if (frame_right > opt.startFrames_right[i]) { var isNull = RGraph.isNullish(obj.right[i]); if (typeof obj.left[i] === 'number') { obj.right[i] = Math.min( Math.abs(original_right[i]), Math.abs(original_right[i] * ( (opt.counters_right[i]++) / framesperbar)) ); // Make the number negative if the original was if (original_right[i] < 0) { obj.right[i] *= -1; } if (isNull) { obj.right[i] = null; } } else if (RGraph.isArray(obj.right[i])) { for (var j=0; j<obj.right[i].length; ++j) { obj.right[i][j] = Math.min( Math.abs(original_right[i][j]), Math.abs(original_right[i][j] * ( (opt.counters_right[i]++) / framesperbar)) ); // Make the number negative if the original was if (original_right[i][j] < 0) { obj.right[i][j] *= -1; } } } } else { obj.right[i] = typeof obj.right[i] === 'object' && obj.right[i] ? RGraph.arrayPad([], obj.right[i].length, 0) : (RGraph.isNullish(obj.right[i]) ? null : 0); } } // No callback here - only called by the right function if (frame_right < opt.frames) { RGraph.redrawCanvas(obj.canvas); RGraph.Effects.updateCanvas(iteratorRight); } else { callback(this); } } iteratorLeft(); iteratorRight(); return this; }; // // Couple of functions that allow you to control the // Bipolar animation effects // this.stopAnimation = function () { this.stopAnimationRequested_left = true; this.stopAnimationRequested_right = true; }; this.cancelStopAnimation = function () { this.stopAnimationRequested_left = false; this.stopAnimationRequested_right = false; }; // // A worker function that handles Bipolar chart specific tooltip substitutions // this.tooltipSubstitutions = function (opt) { var indexes = RGraph.sequentialIndexToGrouped(opt.index, this.data2); // Calculate the dataset that can be used in labels// if (indexes[0] >= this.left.length) { var dataset2 = indexes[0] - this.left.length, side = 'right', values = this.right[dataset2]; } else { var dataset2 = indexes[0], side = 'left' values = this.left[dataset2]; } if (typeof values === 'number') { values = [values]; } return { index: indexes[1], dataset: indexes[0], dataset2: dataset2, sequentialIndex: opt.index, value: typeof this.data2[opt.index] === 'number' ? this.data2[opt.index] : this.data2[indexes[0]][indexes[1]], values: values, side: side }; }; // // 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) { var label; var side = ((specific.dataset + 1) > this.left.length) ? 'right' : 'left'; if (typeof this[side][specific.dataset2] === 'object') { label = (!RGraph.isNullish(properties.tooltipsFormattedKeyLabels) && typeof properties.tooltipsFormattedKeyLabels === 'object' && properties.tooltipsFormattedKeyLabels[index]) ? properties.tooltipsFormattedKeyLabels[index] : ''; } else { label = (!RGraph.isNullish(properties.tooltipsFormattedKeyLabels) && typeof properties.tooltipsFormattedKeyLabels === 'object' && properties.tooltipsFormattedKeyLabels[specific.index]) ? properties.tooltipsFormattedKeyLabels[specific.index] : ''; } return { label: label }; }; // // 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) coords = this.coords[args.index]; // Position the tooltip in the X direction args.tooltip.style.left = ( canvasXY[0] // The X coordinate of the canvas + coords[0] // The X coordinate of the point on the chart + (coords[2] / 2) // Add half of the width of the bar - (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 + coords[1] // The Y coordinate of the bar on the chart - tooltip.offsetHeight // The height of the tooltip - 10 // An arbitrary amount + obj.properties.tooltipsOffsety // Add any user defined offset ) + 'px'; // If the chart is a 3D version the tooltip Y position needs this // adjustment if (properties.variant === '3d') { var left = coords[0]; var top = coords[1]; var angle = properties.variantThreedAngle; var adjustment = Math.tan(angle) * left; args.tooltip.style.top = parseInt(args.tooltip.style.top) + 10 + adjustment + '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]; } else { var total = 0; // LHS data for (let i=0; i<this.data[0].length; ++i) { if (RGraph.isArray(this.data[0][i])) { total += this.data[0][i][index]; } } // RHS data for (let i=0; i<this.data[1].length; ++i) { if (RGraph.isArray(this.data[1][i])) { total += this.data[1][i][index]; } } return total; } }; // // Returns how many data-points there should be when a string // based key property has been specified. For example, this: // // key: '%{property:_labels[%{index}]} %{value_formatted}' // // ...depending on how many bits of data ther is might get // turned into this: // // key: [ // '%{property:_labels[%{index}]} %{value_formatted}', // '%{property:_labels[%{index}]} %{value_formatted}', // '%{property:_labels[%{index}]} %{value_formatted}', // '%{property:_labels[%{index}]} %{value_formatted}', // '%{property:_labels[%{index}]} %{value_formatted}', // ] // // ... ie in that case there would be 4 data-points so the // template is repeated 4 times. // this.getKeyNumDatapoints = function () { var num = 1; for (let side=0; side<this.data.length; ++side) { for (let i=0; i<this.data[side].length; ++i) { if (RGraph.isArray(this.data[side][i])) { num = Math.max( num, this.data[side][i].length ); } } } return num; }; // // This function handles clipping to scale values. Because // each chart handles scales differently, a worker function // is needed instead of it all being done centrally in the // RGraph.clipTo.start() function. // // @param string clip The clip string as supplied by the // user in the chart configuration // this.clipToScaleWorker = function (clip) { var match1 = RegExp.$1; var match2 = RegExp.$2; // The Regular expression is actually done by the // calling RGraph.clipTo.start() function in the core // library if (match1 === 'min') from = 0; else from = Number(match1); if (match2 === 'max') to = this.scale2.max; else to = Number(match2); var [x1, x2] = this.getXCoord(from); var [x3, x4] = this.getXCoord(to); for (var i=0; i<2; ++i) { // LEFT-HAND-SIDE if (i === 0) { // Change the X if the number is "min" if (match1 === 'min') { x1 += (this.properties.marginCenter / 2); } // Change the width if the number is "max" if (match2 === 'max') { x3 = 0; } this.path( 'sa b r % % % %', x3, 0, x1 - x3, this.canvas.height ); // RIGHT-HAND-SIDE } else { // Change the X if the number is "min" if (match1 === 'min') { var rightX = this.properties.marginLeft + this.axisWidth + (this.properties.marginCenter / 2); } else { var rightX = x2; } // Change the width if the number is "max" if (match2 === 'max') { var rightW = (this.canvas.width / 2); } else { var rightW = x4 - rightX; } this.path( 'r % % % % cl', rightX, 0, rightW, this.canvas.height ); }; } } // // This function handles TESTING clipping to scale values. // Because each chart handles scales differently, a worker // function is needed instead of it all being done // centrally in the RGraph.clipTo.start() function. // // @param string clip The clip string as supplied by the // user in the chart configuration // this.clipToScaleTestWorker = function (clip) { var match1 = RegExp.$1; var match2 = RegExp.$2; // The Regular expression is actually done by the // calling RGraph.clipTo.start() function in the core // library if (match1 === 'min') from = 0; else from = Number(match1); if (match2 === 'max') to = this.scale2.max; else to = Number(match2); var [x1, x2] = this.getXCoord(from); var [x3, x4] = this.getXCoord(to); for (var i=0; i<2; ++i) { // LEFT-HAND-SIDE if (i === 0) { // Change the X if the number is "min" if (match1 === 'min') { x1 += (this.properties.marginCenter / 2); } // Change the width if the number is "max" if (match2 === 'max') { x3 = 0; } this.path( 'b r % % % %', x3, 0, x1 - x3, this.canvas.height ); // RIGHT-HAND-SIDE } else { // Change the X if the number is "min" if (match1 === 'min') { var rightX = this.properties.marginLeft + this.axisWidth + (this.properties.marginCenter / 2); } else { var rightX = x2; } // Change the width if the number is "max" if (match2 === 'max') { var rightW = (this.canvas.width / 2); } else { var rightW = x4 - rightX; } this.path( 'r % % % %', rightX, 0, rightW, this.canvas.height ); }; } } // // Objects are now always registered so that when RGraph.redraw() // is called this chart will be redrawn. // RGraph.register(this); // // This is the 'end' of the constructor so if the first argument // contains configuration dsta - handle that. // RGraph.parseObjectStyleConfig(this, conf.options); };