// 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 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 0 && this.coords2Left.length === 0) { for (var i=0; i 0 && this.coords2Right.length === 0) { for (var i=0; i=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.isNull(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; // 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 = 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.yaxisTickmarksCount > 0) { if (properties.leftVisible) { this.context.beginPath(); for (var i=0; i 0) { var xInterval = this.axisWidth / properties.xaxisTickmarksCount; // Is xaxisTickmarksInterval specified ? If so, use that. if (typeof properties.xaxisTickmarksInterval == 'number') { xInterval = properties.xaxisTickmarksInterval; } // Draw the left sides X tick marks if (properties.leftVisible) { for (i=this.marginLeft; i<(this.marginLeft + this.axisWidth); i+=xInterval) { this.context.beginPath(); this.context.moveTo(i, this.canvas.height - this.marginBottom); this.context.lineTo(i, (this.canvas.height - this.marginBottom) + 4); this.context.closePath(); this.context.stroke(); } } if (properties.rightVisible) { // Draw the right sides X tick marks var stoppingPoint = this.canvas.width - this.marginRight + 1; for (i=(this.marginLeft + this.axisWidth + properties.marginCenter + xInterval); i<=stoppingPoint; i+=xInterval) { this.context.beginPath(); this.context.moveTo(i, this.canvas.height - this.marginBottom); this.context.lineTo(i, (this.canvas.height - this.marginBottom) + 4); 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) + 4, 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) + 4, 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) { 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.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 opt.startFrames_left[i]) { var isNull = RGraph.isNull(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 opt.startFrames_right[i]) { var isNull = RGraph.isNull(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= 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.isNull(properties.tooltipsFormattedKeyLabels) && typeof properties.tooltipsFormattedKeyLabels === 'object' && properties.tooltipsFormattedKeyLabels[index]) ? properties.tooltipsFormattedKeyLabels[index] : ''; } else { label = (!RGraph.isNull(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