// 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 { // Note that we're in an arrow function so the // 'this' variable is OK to be used and refers // to the RGraph Line chart object. RGraph.scale(this); }); // // Fire the onbeforedraw event // RGraph.fireCustomEvent(this, 'onbeforedraw'); // Translate half a pixel for antialiasing purposes - but only if it hasn't been // done already // if ( !this.properties.scale && this.properties.antialiasTranslate && !this.canvas.__rgraph_aa_translated__) { this.context.translate(0.5,0.5); this.canvas.__rgraph_aa_translated__ = true; } // // 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 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.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 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 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 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= 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], scaleFactor = RGraph.getScaleFactor(this); // Position the tooltip in the X direction args.tooltip.style.left = ( canvasXY[0] // The X coordinate of the canvas + (coords[0] / scaleFactor) // The X coordinate of the point on the chart + (coords[2] / 2 / scaleFactor)// 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] / scaleFactor) // 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] / 2; var angle = properties.variantThreedAngle; var adjustment = Math.tan(angle) * left; args.tooltip.style.top = parseInt(args.tooltip.style.top) + 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