Dave Cross: Still Munging Data With Perl: Online event - Mar 27 Learn more

// version: 2017-11-25
/**
* o--------------------------------------------------------------------------------o
* | This file is part of the RGraph package - you can learn more at: |
* | |
* | |
* | RGraph is licensed under the Open Source MIT license. That means that it's |
* | totally free to use and there are no restrictions on what you can do with it! |
* o--------------------------------------------------------------------------------o
*/
RGraph = window.RGraph || {isRGraph: true};
RGraph.SVG = RGraph.SVG || {};
// Module pattern
(function (win, doc, undefined)
{
var RG = RGraph,
ua = navigator.userAgent,
ma = Math,
win = window,
doc = document;
RG.SVG.Bipolar = function (conf)
{
//
// A setter that the constructor uses (at the end)
// to set all of the properties
//
// @param string name The name of the property to set
// @param string value The value to set the property to
//
this.set = function (name, value)
{
if (arguments.length === 1 && typeof name === 'object') {
for (i in arguments[0]) {
if (typeof i === 'string') {
var ret = RG.SVG.commonSetter({
object: this,
name: i,
value: arguments[0][i]
});
name = ret.name;
value = ret.value;
this.set(name, value);
}
}
} else {
var ret = RG.SVG.commonSetter({
object: this,
name: name,
value: value
});
name = ret.name;
value = ret.value;
this.properties[name] = value;
// If setting the colors, update the originalColors
// property too
if (name === 'colors') {
this.originalColors = RG.SVG.arrayClone(value);
this.colorsParsed = false;
}
}
return this;
};
this.id = conf.id;
this.uid = RG.SVG.createUID();
this.container = document.getElementById(this.id);
this.layers = {}; // MUST be before the SVG tag is created!
this.svg = RG.SVG.createSVG({object: this,container: this.container});
this.isRGraph = true;
this.data = [conf.left, conf.right];
this.left = conf.left;
this.right = conf.right;
this.type = 'bipolar';
this.coords = [];
this.coordsLeft = [];
this.coordsRight = [];
this.coords2 = [];
this.coords2Left = [];
this.coords2Right = [];
this.stackedBackfacesLeft = [];
this.stackedBackfacesRight = [];
this.originalColors = {};
this.gradientCounter = 1;
this.sequentialIndex = 0; // Used for tooltips
// Add this object to the ObjectRegistry
RG.SVG.OR.add(this);
this.container.style.display = 'inline-block';
this.properties =
{
gutterLeft: 35,
gutterRight: 35,
gutterTop: 35,
gutterBottom: 35,
gutterCenter: null,
backgroundColor: null,
backgroundGrid: true,
backgroundGridColor: '#ddd',
backgroundGridLinewidth: 1,
backgroundGridHlines: true,
backgroundGridHlinesCount: null,
backgroundGridVlines: true,
backgroundGridVlinesCount: null,
backgroundGridBorder: true,
backgroundGridDashed: false,
backgroundGridDotted: false,
backgroundGridDashArray: null,
xaxis: true,
xaxisLinewidth: 1,
xaxisTickmarks: true,
xaxisTickmarksLength: 5,
xaxisLabelsCount: 5,
xaxisLabelsPositionEdgeTickmarksCount: 5,
xaxisColor: 'black',
xaxisLabelsOffsetx: 0,
xaxisLabelsOffsety: 0,
xaxisUnitsPre: '',
xaxisUnitsPost: '',
xaxisStrict: false,
xaxisDecimals: 0,
xaxisPoint: '.',
xaxisThousand: ',',
xaxisRound: false,
xaxisMax: null,
xaxisMin: 0,
xaxisFormatter: null,
xaxisTextFont: null,
xaxisTextSize: null,
xaxisTextBold: null,
xaxisTextItalic: null,
xaxisTextColor: null,
yaxis: true,
yaxisTickmarks: true,
yaxisTickmarksLength: 5,
yaxisColor: 'black',
yaxisScale: false,
yaxisLabels: null,
yaxisLabelsOffsetx: 0,
yaxisLabelsOffsety: 0,
yaxisTextFont: null,
yaxisTextSize: null,
yaxisTextBold: null,
yaxisTextItalic: null,
yaxisTextColor: null,
// 20 colors. If you need more you need to set the colors property
colors: [
'red', '#0f0', '#00f', '#ff0', '#0ff', '#0f0','pink','orange','gray','black',
'red', '#0f0', '#00f', '#ff0', '#0ff', '#0f0','pink','orange','gray','black'
],
colorsSequential: false,
strokestyle: 'rgba(0,0,0,0)',
vmargin: 3,
vmarginGrouped: 2,
labelsAbove: false,
labelsAboveFont: null,
labelsAboveSize: null,
labelsAboveBold: null,
labelsAboveItalic: null,
labelsAboveColor: null,
labelsAboveBackground: null,
labelsAboveBackgroundPadding: 0,
labelsAboveUnitsPre: null,
labelsAboveUnitsPost: null,
labelsAbovePoint: null,
labelsAboveThousand: null,
labelsAboveFormatter: null,
labelsAboveDecimals: null,
labelsAboveOffsetx: 0,
labelsAboveOffsety: 0,
labelsAboveSpecific: null,
textColor: 'black',
textFont: 'sans-serif',
textSize: 12,
textBold: false,
textItalic: false,
linewidth: 1,
grouping: 'grouped',
tooltips: null,
tooltipsOverride: null,
tooltipsEffect: 'fade',
tooltipsCssClass: 'RGraph_tooltip',
tooltipsEvent: 'click',
highlightStroke: 'rgba(0,0,0,0)',
highlightFill: 'rgba(255,255,255,0.7)',
highlightLinewidth: 1,
title: '',
titleSize: 16,
titleX: null,
titleY: null,
titleHalign: 'center',
titleValign: null,
titleColor: 'black',
titleFont: null,
titleBold: false,
titleItalic: false,
titleSubtitle: null,
titleSubtitleSize: 10,
titleSubtitleX: null,
titleSubtitleY: null,
titleSubtitleHalign: 'center',
titleSubtitleValign: null,
titleSubtitleColor: '#aaa',
titleSubtitleFont: null,
titleSubtitleBold: false,
titleSubtitleItalic: false,
shadow: false,
shadowOffsetx: 2,
shadowOffsety: 2,
shadowBlur: 2,
shadowOpacity: 0.25,
key: null,
keyColors: null,
keyOffsetx: 0,
keyOffsety: 0,
keyTextOffsetx: 0,
keyTextOffsety: -1,
keyTextSize: null,
keyTextBold: null,
keyTextItalic: null,
keyTextFont: null
};
//
// Copy the global object properties to this instance
//
RG.SVG.getGlobals(this);
/**
* "Decorate" the object with the generic effects if the effects library has been included
*/
if (RG.SVG.FX && typeof RG.SVG.FX.decorate === 'function') {
RG.SVG.FX.decorate(this);
}
var prop = this.properties;
//
// The draw method draws the Bar chart
//
this.draw = function ()
{
// Fire the beforedraw event
RG.SVG.fireCustomEvent(this, 'onbeforedraw');
// Should the first thing that's done inthe.draw() function
// except for the onbeforedraw event
this.width = Number(this.svg.getAttribute('width'));
this.height = Number(this.svg.getAttribute('height'));
// Create the defs tag if necessary
RG.SVG.createDefs(this);
//
// Autosize the center gutter to allow for big labels
//
if (typeof prop.gutterCenter !== 'number') {
prop.gutterCenter = this.getGutterCenter();
}
// Reset the coords arrays
this.coords = [];
this.coordsLeft = [];
this.coordsRight = [];
this.coords2 = [];
this.coords2Left = [];
this.coords2Right = [];
this.graphWidth = (this.width - prop.gutterLeft - prop.gutterRight - prop.gutterCenter) / 2;
this.graphHeight = this.height - prop.gutterTop - prop.gutterBottom;
/**
* Parse the colors. This allows for simple gradient syntax
*/
// Parse the colors for gradients
RG.SVG.resetColorsToOriginalValues({object:this});
this.parseColors();
// Go through the data and work out the maximum value
var values = [];
for (var i=0; i<2; ++i) {
for (var j=0,max=0; j<this.data[i].length; ++j) {
if (typeof this.data[i][j] === 'number') {
values.push(this.data[i][j]);
} else if (RG.SVG.isArray(this.data[i][j]) && prop.grouping === 'grouped') {
values.push(RG.SVG.arrayMax(this.data[i][j]));
} else if (RG.SVG.isArray(this.data[i][j]) && prop.grouping === 'stacked') {
values.push(RG.SVG.arraySum(this.data[i][j]));
}
}
}
var max = RG.SVG.arrayMax(values);
// A custom, user-specified maximum value
if (typeof prop.xaxisMax === 'number') {
max = prop.xaxisMax;
}
//
// Generate an appropiate scale
//
this.scale = RG.SVG.getScale({
object: this,
numlabels: prop.xaxisLabelsCount,
unitsPre: prop.xaxisUnitsPre,
unitsPost: prop.xaxisUnitsPost,
max: max,
min: prop.xaxisMin,
point: prop.xaxisPoint,
round: prop.xaxisRound,
thousand: prop.xaxisThousand,
decimals: prop.xaxisDecimals,
strict: typeof prop.xaxisMax === 'number',
formatter: prop.xaxisFormatter
});
// Now the scale has been generated adopt its max value
this.max = this.scale.max;
this.min = this.scale.min;
prop.yaxisMax = this.scale.max;
prop.yaxisMin = this.scale.min;
// Draw the background first
this.drawBackground(this);
// Draw the title
this.drawTitle();
// Draw the bars
this.drawBars();
// Draw the axes over the bars
this.drawAxes();
// Draw the labels for both of the the axes
this.drawLabels()
// Draw the labelsAbove labels
this.drawLabelsAbove();
// Draw the key
if (typeof prop.key !== null && RG.SVG.drawKey) {
RG.SVG.drawKey(this);
} else if (!RG.SVG.isNull(prop.key)) {
alert('The drawKey() function does not exist - have you forgotten to include the key library?');
}
// Fire the draw event
RG.SVG.fireCustomEvent(this, 'ondraw');
return this;
};
//
// Draws the background
//
this.drawBackground = function ()
{
// Save the original gutter properties
var originalGutterRight = prop.gutterRight,
originalGutterLeft = prop.gutterLeft;
// Draw the LEFT background
prop.gutterRight = this.width - (prop.gutterLeft + this.graphWidth);
if (RG.SVG.isNull(prop.backgroundGridHlinesCount)) {
var resetToNull = true;
prop.backgroundGridHlinesCount = this.left.length;
}
// Set the LEFT background image properties
var properties = ['','Aspect','Opacity','Stretch','X','Y','W','H',];
for (i in properties ) {
if (typeof properties[i] === 'string') {
prop['backgroundImage' + properties[i]] = prop['backgroundImageLeft' + properties[i]];
}
}
RG.SVG.drawBackground(this);
if (resetToNull) {
prop.backgroundGridHlinesCount = null;
}
// Draw the RIGHT background
prop.gutterRight = originalGutterRight;
prop.gutterLeft = this.width - (prop.gutterRight + this.graphWidth);
if (RG.SVG.isNull(prop.backgroundGridHlinesCount)) {
prop.backgroundGridHlinesCount = this.right.length;
}
// Set the RIGHT background image properties
var properties = ['','Aspect','Opacity','Stretch','X','Y','W','H',];
for (i in properties ) {
if (typeof properties[i] === 'string') {
prop['backgroundImage' + properties[i]] = prop['backgroundImageRight' + properties[i]];
}
}
// Draw the background
RG.SVG.drawBackground(this);
// Reset the gutter properties to the original values
prop.gutterLeft = originalGutterLeft;
prop.gutterRight = originalGutterRight;
};
//
// Draws the axes
//
this.drawAxes = function ()
{
// Draw the LEFT X axes
if (prop.xaxis) {
RG.SVG.create({
svg: this.svg,
type: 'path',
parent: this.svg.all,
attr: {
d: 'M {1} {2} L {3} {4}'.format(
prop.gutterLeft,
this.height - prop.gutterBottom,
prop.gutterLeft + this.graphWidth,
this.height - prop.gutterBottom
),
'stroke-width': prop.xaxisLinewidth,
stroke: prop.xaxisColor,
fill: 'rgba(0,0,0,0)',
'shape-rendering': 'crispEdges'
}
});
// Draw the right X axis
RG.SVG.create({
svg: this.svg,
type: 'path',
parent: this.svg.all,
attr: {
d: 'M {1} {2} L {3} {4}'.format(
this.width - prop.gutterRight,
this.height - prop.gutterBottom,
this.width - prop.gutterRight - this.graphWidth,
this.height - prop.gutterBottom
),
'stroke-width': prop.xaxisLinewidth,
stroke: prop.xaxisColor,
fill: 'rgba(0,0,0,0)',
'shape-rendering': 'crispEdges'
}
});
//
// Draw tickmarks if necessary
//
if (prop.xaxisTickmarks) {
var startY = this.height - prop.gutterBottom,
endY = this.height - prop.gutterBottom + prop.xaxisTickmarksLength;
// Draw the LEFT sides tickmarks
for (var i=0; i<prop.xaxisLabelsPositionEdgeTickmarksCount; ++i) {
var x = prop.gutterLeft + (i * (this.graphWidth / prop.xaxisLabelsPositionEdgeTickmarksCount));
RG.SVG.create({
svg: this.svg,
parent: this.svg.all,
type: 'path',
attr: {
d: 'M{1} {2} L{3} {4}'.format(
x + 0.001,
startY,
x,
endY
),
stroke: prop.xaxisColor,
'stroke-width': prop.xaxisLinewidth,
'shape-rendering': "crispEdges"
}
});
}
// Draw an extra LEFT tick if no Y axis is being shown
if (!prop.yaxis) {
var x = prop.gutterLeft + this.graphWidth;
RG.SVG.create({
svg: this.svg,
parent: this.svg.all,
type: 'path',
attr: {
d: 'M{1} {2} L{3} {4}'.format(
x + 0.001,
startY,
x,
endY
),
stroke: prop.xaxisColor,
'stroke-width': prop.xaxisLinewidth,
'shape-rendering': "crispEdges"
}
});
}
// Draw the RIGHT sides tickmarks
for (var i=0; i<prop.xaxisLabelsPositionEdgeTickmarksCount; ++i) {
var x = prop.gutterLeft + prop.gutterCenter + this.graphWidth + ((i+1) * (this.graphWidth / prop.xaxisLabelsPositionEdgeTickmarksCount));
RG.SVG.create({
svg: this.svg,
parent: this.svg.all,
type: 'path',
attr: {
d: 'M{1} {2} L{3} {4}'.format(
x + 0.001,
startY,
x,
endY
),
stroke: prop.xaxisColor,
'stroke-width': prop.xaxisLinewidth,
'shape-rendering': "crispEdges"
}
});
}
// Draw an extra RIGHT tick if no Y axis is being shown
if (!prop.yaxis) {
var x = prop.gutterLeft + this.graphWidth + prop.gutterCenter;
RG.SVG.create({
svg: this.svg,
parent: this.svg.all,
type: 'path',
attr: {
d: 'M{1} {2} L{3} {4}'.format(
x + 0.001,
startY,
x,
endY
),
stroke: prop.xaxisColor,
'stroke-width': prop.xaxisLinewidth,
'shape-rendering': "crispEdges"
}
});
}
}
}
// Draw the LEFT vertical axes
if (prop.yaxis) {
RG.SVG.create({
svg: this.svg,
type: 'path',
parent: this.svg.all,
attr: {
d: 'M {1} {2} L {3} {4}'.format(
prop.gutterLeft + this.graphWidth,
this.height - prop.gutterBottom,
prop.gutterLeft + this.graphWidth,
prop.gutterTop
),
'stroke-width': prop.yaxisLinewidth,
stroke: prop.yaxisColor,
fill: 'rgba(0,0,0,0)',
'shape-rendering': 'crispEdges',
'stroke-linecap': 'square'
}
});
// Draw the RIGHT vertical axis
RG.SVG.create({
svg: this.svg,
type: 'path',
parent: this.svg.all,
attr: {
d: 'M {1} {2} L {3} {4}'.format(
prop.gutterLeft + this.graphWidth + prop.gutterCenter,
this.height - prop.gutterBottom,
prop.gutterLeft + this.graphWidth + prop.gutterCenter,
prop.gutterTop
),
'stroke-width': prop.yaxisLinewidth,
stroke: prop.yaxisColor,
fill: 'rgba(0,0,0,0)',
'shape-rendering': 'crispEdges',
'stroke-linecap': 'square'
}
});
//
// Draw Y axis tickmarks if necessary
//
if (prop.yaxisTickmarks) {
var startX = prop.gutterLeft + this.graphWidth,
endX = prop.gutterLeft + this.graphWidth + prop.yaxisTickmarksLength,
numticks = this.left.length;
// Draw the left sides tickmarks
for (var i=0; i<numticks; ++i) {
var y = prop.gutterTop + (i * (this.graphHeight / numticks));
RG.SVG.create({
svg: this.svg,
parent: this.svg.all,
type: 'path',
attr: {
d: 'M{1} {2} L{3} {4}'.format(
startX + 0.001,
y,
endX,
y
),
stroke: prop.yaxisColor,
'stroke-width': prop.yaxisLinewidth,
'shape-rendering': "crispEdges"
}
});
}
// Draw an extra LEFT tickmark if the X axis is not being shown
if (!prop.xaxis) {
var y = prop.gutterTop + this.graphHeight;
RG.SVG.create({
svg: this.svg,
parent: this.svg.all,
type: 'path',
attr: {
d: 'M{1} {2} L{3} {4}'.format(
startX + 0.001,
y,
endX,
y
),
stroke: prop.yaxisColor,
'stroke-width': prop.yaxisLinewidth,
'shape-rendering': "crispEdges"
}
});
}
var startX = prop.gutterLeft + this.graphWidth + prop.gutterCenter,
endX = prop.gutterLeft + this.graphWidth + prop.gutterCenter - prop.yaxisTickmarksLength,
numticks = this.right.length;
for (var i=0; i<numticks; ++i) {
var y = prop.gutterTop + (i * (this.graphHeight / numticks));
RG.SVG.create({
svg: this.svg,
parent: this.svg.all,
type: 'path',
attr: {
d: 'M{1} {2} L{3} {4}'.format(
startX + 0.001,
y,
endX,
y
),
stroke: prop.yaxisColor,
'stroke-width': prop.yaxisLinewidth,
'shape-rendering': "crispEdges"
}
});
}
// Draw an extra RIGHT tickmark if the X axis is not being shown
if (!prop.xaxis) {
var y = prop.gutterTop + this.graphHeight;
RG.SVG.create({
svg: this.svg,
parent: this.svg.all,
type: 'path',
attr: {
d: 'M{1} {2} L{3} {4}'.format(
startX + 0.001,
y,
endX,
y
),
stroke: prop.yaxisColor,
'stroke-width': prop.yaxisLinewidth,
'shape-rendering': "crispEdges"
}
});
}
}
}
};
//
// Draws the labels
//
this.drawLabels = function ()
{
//
// Draw the Y axis labels
//
var numlabels = prop.yaxisLabels ? prop.yaxisLabels.length : 5
for (var i=0; i<numlabels; ++i) {
var segment = this.graphHeight / numlabels,
y = prop.gutterTop + (segment * i) + (segment / 2) + prop.yaxisLabelsOffsety,
x = prop.gutterLeft + this.graphWidth + (prop.gutterCenter / 2) + prop.yaxisLabelsOffsetx;
var text = RG.SVG.text({
object: this,
parent: this.svg.all,
text: prop.yaxisLabels && prop.yaxisLabels[i] ? prop.yaxisLabels[i] : '',
x: x,
y: y,
halign: 'center',
valign: 'center',
tag: 'labels.yaxis',
font: prop.yaxisTextFont || prop.textFont,
size: prop.yaxisTextSize || (typeof prop.textSize === 'number' ? prop.textSize + 'pt' : prop.textSize),
bold: !RG.SVG.isNull(prop.yaxisTextBold) ? prop.yaxisTextBold : prop.textBold,
italic: !RG.SVG.isNull(prop.yaxisTextItalic) ? prop.yaxisTextItalic : prop.textItalic,
color: prop.yaxisTextColor || prop.textColor
});
}
//
// Draw the X axis scale for the LEFT side
//
var segment = this.graphWidth / prop.xaxisLabelsCount;
for (var i=0; i<this.scale.labels.length; ++i) {
RG.SVG.text({
object: this,
parent: this.svg.all,
text: this.scale.labels[i],
x: prop.gutterLeft + this.graphWidth - (segment * (i+1)) + prop.xaxisLabelsOffsetx,
y: this.height - prop.gutterBottom + 10 + prop.xaxisLabelsOffsety,
halign: 'center',
valign: 'top',
tag: 'labels.xaxis',
font: prop.xaxisTextFont || prop.textFont,
size: prop.xaxisTextSize || (typeof prop.textSize === 'number' ? prop.textSize + 'pt' : prop.textSize),
bold: typeof prop.xaxisTextBold === 'boolean' ? prop.xaxisTextBold : prop.textBold,
italic: typeof prop.xaxisTextItalic === 'boolean' ? prop.xaxisTextItalic : prop.textItalic,
color: prop.xaxisTextColor || prop.textColor
});
}
//
// Add the minimum label for the LEST side
//
var y = this.height - prop.gutterBottom + 10,
str = (prop.xaxisUnitsPre + prop.xaxisMin.toFixed(prop.xaxisDecimals).replace(/\./, prop.xaxisPoint) + prop.xaxisUnitsPost);
var text = RG.SVG.text({
object: this,
parent: this.svg.all,
text: str,
x: prop.gutterLeft + this.graphWidth + prop.xaxisLabelsOffsetx,
y: y + prop.xaxisLabelsOffsety,
halign: 'center',
valign: 'top',
tag: 'labels.xaxis',
font: prop.xaxisTextFont || prop.textFont,
size: prop.xaxisTextSize || (typeof prop.textSize === 'number' ? prop.textSize + 'pt' : prop.textSize),
bold: typeof prop.xaxisTextBold === 'boolean' ? prop.xaxisTextBold : prop.textBold,
italic: typeof prop.xaxisTextItalic === 'boolean' ? prop.xaxisTextItalic : prop.textItalic,
color: prop.xaxisTextColor || prop.textColor
});
//
// Draw the X axis scale for the RIGHT side
//
for (var i=0; i<this.scale.labels.length; ++i) {
RG.SVG.text({
object: this,
parent: this.svg.all,
text: this.scale.labels[i],
x: prop.gutterLeft + this.graphWidth + prop.gutterCenter + (segment * (i + 1)) + prop.xaxisLabelsOffsetx,
y: this.height - prop.gutterBottom + 10 + prop.xaxisLabelsOffsety,
halign: 'center',
valign: 'top',
tag: 'labels.xaxis',
font: prop.xaxisTextFont || prop.textFont,
size: prop.xaxisTextSize || (typeof prop.textSize === 'number' ? prop.textSize + 'pt' : prop.textSize),
bold: typeof prop.xaxisTextBold === 'boolean' ? prop.xaxisTextBold : prop.textBold,
italic: typeof prop.xaxisTextItalic === 'boolean' ? prop.xaxisTextItalic : prop.textItalic,
color: prop.xaxisTextColor || prop.textColor
});
}
//
// Add the minimum label for the RIGHT side
//
var text = RG.SVG.text({
object: this,
parent: this.svg.all,
text: prop.xaxisUnitsPre + prop.xaxisMin.toFixed(prop.xaxisDecimals).replace(/\./, prop.xaxisPoint) + prop.xaxisUnitsPost,
x: prop.gutterLeft + this.graphWidth + prop.gutterCenter + prop.xaxisLabelsOffsetx,
y: this.height - prop.gutterBottom + 10 + prop.xaxisLabelsOffsety,
halign: 'center',
valign: 'top',
tag: 'labels.xaxis',
font: prop.xaxisTextFont || prop.textFont,
size: prop.xaxisTextSize || (typeof prop.textSize === 'number' ? prop.textSize + 'pt' : prop.textSize),
bold: typeof prop.xaxisTextBold === 'boolean' ? prop.xaxisTextBold : prop.textBold,
italic: typeof prop.xaxisTextItalic === 'boolean' ? prop.xaxisTextItalic : prop.textItalic,
color: prop.xaxisTextColor || prop.textColor
});
};
//
// Draws the bars
//
this.drawBars = function ()
{
if (prop.shadow) {
RG.SVG.setShadow({
object: this,
offsetx: prop.shadowOffsetx,
offsety: prop.shadowOffsety,
blur: prop.shadowBlur,
opacity: prop.shadowOpacity,
id: 'dropShadow'
});
}
// Go thru the LEFT data and draw the bars
for (var i=0; i<this.left.length; ++i) {
// LEFT REGULAR NUMBER
if (typeof this.left[i] === 'number') {
var color = prop.colors[this.sequentialIndex],
tooltip = RG.SVG.isNull(prop.tooltips) ? null : prop.tooltips[this.sequentialIndex],
y = prop.gutterTop + ((this.graphHeight / this.left.length) * i) + prop.vmargin,
width = this.getWidth(this.left[i]),
x = prop.gutterLeft + this.graphWidth - width,
height = (this.graphHeight / this.left.length) - prop.vmargin - prop.vmargin;
var rect = RG.SVG.create({
svg: this.svg,
parent: this.svg.all,
type: 'rect',
attr: {
x: x,
y: y,
width: width,
height: height,
fill: prop.colorsSequential ? prop.colors[this.sequentialIndex] : prop.colors[0],
stroke: prop.strokestyle,
'stroke-width': prop.linewidth,
'shape-rendering': 'crispEdges',
'data-original-x': x,
'data-original-y': y,
'data-original-width': width,
'data-original-height': height,
'data-tooltop': (tooltip || ''),
'data-index': i,
'data-sequential-index': this.sequentialIndex,
'data-value': this.left[i],
filter: prop.shadow ? 'url(#dropShadow)' : ''
}
});
this.coords.push({
object: this,
element: rect,
x: parseFloat(rect.getAttribute('x')),
y: parseFloat(rect.getAttribute('y')),
width: parseFloat(rect.getAttribute('width')),
height: parseFloat(rect.getAttribute('height'))
});
this.coordsLeft.push({
object: this,
element: rect,
x: parseFloat(rect.getAttribute('x')),
y: parseFloat(rect.getAttribute('y')),
width: parseFloat(rect.getAttribute('width')),
height: parseFloat(rect.getAttribute('height'))
});
this.installTooltipsEventListeners({
rect: rect,
index: i,
sequentialIndex: this.sequentialIndex
});
this.sequentialIndex++;
// LEFT STACKED
} else if (RG.SVG.isArray(this.left[i]) && prop.grouping === 'stacked') {
var accWidth = 0;
for (var j=0; j<this.left[i].length; ++j) {
var color = prop.colors[this.sequentialIndex],
tooltip = RG.SVG.isNull(prop.tooltips) ? null : prop.tooltips[this.sequentialIndex],
y = prop.gutterTop + ((this.graphHeight / this.left.length) * i) + prop.vmargin,
width = this.getWidth(this.left[i][j]),
accWidth = accWidth + width,
x = prop.gutterLeft + this.graphWidth - accWidth,
height = (this.graphHeight / this.left.length) - prop.vmargin - prop.vmargin;
// If this is the first iteration of the loop and a shadow
// is requested draw a rect here to create it.
if (j === 0 && prop.shadow) {
var shadowBackfaceX = prop.gutterLeft + this.graphWidth - this.getWidth(RG.SVG.arraySum(this.left[i])),
shadowBackfaceWidth = this.getWidth(RG.SVG.arraySum(this.left[i]));
var rect = RG.SVG.create({
svg: this.svg,
parent: this.svg.all,
type: 'rect',
attr: {
fill: '#eee',
x: shadowBackfaceX,
y: y,
width: shadowBackfaceWidth,
height: height,
'stroke-width': 0,
'data-index': i,
filter: 'url(#dropShadow)'
}
});
this.stackedBackfacesLeft[i] = rect;
}
var rect = RG.SVG.create({
svg: this.svg,
parent: this.svg.all,
type: 'rect',
attr: {
x: x,
y: y,
width: width,
height: height,
fill: prop.colorsSequential ? prop.colors[this.sequentialIndex] : prop.colors[j],
stroke: prop.strokestyle,
'stroke-width': prop.linewidth,
'shape-rendering': 'crispEdges',
'data-original-x': x,
'data-original-y': y,
'data-original-width': width,
'data-original-height': height,
'data-tooltop': (tooltip || ''),
'data-index': i,
'data-subindex': j,
'data-sequential-index': this.sequentialIndex,
'data-value': this.left[i][j]
}
});
this.coords.push({
object: this,
element: rect,
x: parseFloat(rect.getAttribute('x')),
y: parseFloat(rect.getAttribute('y')),
width: parseFloat(rect.getAttribute('width')),
height: parseFloat(rect.getAttribute('height'))
});
this.coordsLeft.push({
object: this,
element: rect,
x: parseFloat(rect.getAttribute('x')),
y: parseFloat(rect.getAttribute('y')),
width: parseFloat(rect.getAttribute('width')),
height: parseFloat(rect.getAttribute('height'))
});
if (!this.coords2[i]) {
this.coords2[i] = [];
}
if (!this.coords2Left[i]) {
this.coords2Left[i] = [];
}
this.coords2[i].push({
object: this,
element: rect,
x: parseFloat(rect.getAttribute('x')),
y: parseFloat(rect.getAttribute('y')),
width: parseFloat(rect.getAttribute('width')),
height: parseFloat(rect.getAttribute('height'))
});
this.coords2Left[i].push({
object: this,
element: rect,
x: parseFloat(rect.getAttribute('x')),
y: parseFloat(rect.getAttribute('y')),
width: parseFloat(rect.getAttribute('width')),
height: parseFloat(rect.getAttribute('height'))
});
this.installTooltipsEventListeners({
rect: rect,
index: i,
sequentialIndex: this.sequentialIndex
});
this.sequentialIndex++;
}
// LEFT GROUPED
} else if (RG.SVG.isArray(this.left[i]) && prop.grouping === 'grouped') {
for (var j=0; j<this.left[i].length; ++j) {
var color = prop.colors[this.sequentialIndex],
tooltip = RG.SVG.isNull(prop.tooltips) ? null : prop.tooltips[this.sequentialIndex],
height = ((this.graphHeight / this.left.length) - prop.vmargin - prop.vmargin - (prop.vmarginGrouped * (this.left[i].length - 1))) / this.left[i].length,
y = prop.gutterTop + ((this.graphHeight / this.left.length) * i) + prop.vmargin + (height * j) + (j * prop.vmarginGrouped),
width = this.getWidth(this.left[i][j]),
x = prop.gutterLeft + this.graphWidth - width;
var rect = RG.SVG.create({
svg: this.svg,
parent: this.svg.all,
type: 'rect',
attr: {
x: x,
y: y,
width: width,
height: height,
fill: prop.colorsSequential ? prop.colors[this.sequentialIndex] : prop.colors[j],
stroke: prop.strokestyle,
'stroke-width': prop.linewidth,
'shape-rendering': 'crispEdges',
'data-original-x': x,
'data-original-y': y,
'data-original-width': width,
'data-original-height': height,
'data-tooltop': (tooltip || ''),
'data-index': i,
'data-subindex': j,
'data-sequential-index': this.sequentialIndex,
'data-value': this.left[i][j],
filter: prop.shadow ? 'url(#dropShadow)' : ''
}
});
this.coords.push({
object: this,
element: rect,
x: parseFloat(rect.getAttribute('x')),
y: parseFloat(rect.getAttribute('y')),
width: parseFloat(rect.getAttribute('width')),
height: parseFloat(rect.getAttribute('height'))
});
this.coordsLeft.push({
object: this,
element: rect,
x: parseFloat(rect.getAttribute('x')),
y: parseFloat(rect.getAttribute('y')),
width: parseFloat(rect.getAttribute('width')),
height: parseFloat(rect.getAttribute('height'))
});
if (!this.coords2[i]) {
this.coords2[i] = [];
}
if (!this.coords2Left[i]) {
this.coords2Left[i] = [];
}
this.coords2[i].push({
object: this,
element: rect,
x: parseFloat(rect.getAttribute('x')),
y: parseFloat(rect.getAttribute('y')),
width: parseFloat(rect.getAttribute('width')),
height: parseFloat(rect.getAttribute('height'))
});
this.coords2Left[i].push({
object: this,
element: rect,
x: parseFloat(rect.getAttribute('x')),
y: parseFloat(rect.getAttribute('y')),
width: parseFloat(rect.getAttribute('width')),
height: parseFloat(rect.getAttribute('height'))
});
this.installTooltipsEventListeners({
rect: rect,
index: i,
sequentialIndex: this.sequentialIndex
});
this.sequentialIndex++;
}
}
}
// Go thru the RIGHT data and draw the bars
for (var i=0; i<this.right.length; ++i) {
// RIGHT REGULAR
if (typeof this.right[i] === 'number') {
var color = prop.colors[this.sequentialIndex],
tooltip = RG.SVG.isNull(prop.tooltips) ? null : prop.tooltips[this.sequentialIndex],
y = prop.gutterTop + ((this.graphHeight / this.right.length) * i) + prop.vmargin,
width = this.getWidth(this.right[i]),
x = prop.gutterLeft + this.graphWidth + prop.gutterCenter,
height = (this.graphHeight / this.right.length) - prop.vmargin - prop.vmargin;
var rect = RG.SVG.create({
svg: this.svg,
parent: this.svg.all,
type: 'rect',
attr: {
x: x,
y: y,
width: width,
height: height,
fill: prop.colorsSequential ? prop.colors[this.sequentialIndex] : prop.colors[0],
stroke: prop.strokestyle,
'stroke-width': prop.linewidth,
'shape-rendering': 'crispEdges',
'data-original-x': x,
'data-original-y': y,
'data-original-width': width,
'data-original-height': height,
'data-tooltop': (tooltip || ''),
'data-index': i,
'data-sequential-index': this.sequentialIndex,
'data-value': this.right[i],
filter: prop.shadow ? 'url(#dropShadow)' : ''
}
});
this.coords.push({
object: this,
element: rect,
x: parseFloat(rect.getAttribute('x')),
y: parseFloat(rect.getAttribute('y')),
width: parseFloat(rect.getAttribute('width')),
height: parseFloat(rect.getAttribute('height'))
});
this.coordsRight.push({
object: this,
element: rect,
x: parseFloat(rect.getAttribute('x')),
y: parseFloat(rect.getAttribute('y')),
width: parseFloat(rect.getAttribute('width')),
height: parseFloat(rect.getAttribute('height'))
});
this.installTooltipsEventListeners({
rect: rect,
index: i,
sequentialIndex: this.sequentialIndex
});
this.sequentialIndex++;
// RIGHT STACKED
} else if (RG.SVG.isArray(this.right[i]) && prop.grouping === 'stacked') {
var accWidth = 0;
for (var j=0; j<this.right[i].length; ++j) {
var color = prop.colors[this.sequentialIndex],
tooltip = RG.SVG.isNull(prop.tooltips) ? null : prop.tooltips[this.sequentialIndex],
y = prop.gutterTop + ((this.graphHeight / this.right.length) * i) + prop.vmargin,
width = this.getWidth(this.right[i][j]),
x = prop.gutterLeft + this.graphWidth + prop.gutterCenter + accWidth,
accWidth = accWidth + width,
height = (this.graphHeight / this.left.length) - prop.vmargin - prop.vmargin;
// If this is the first iteration of the loop and a shadow
// is requested draw a rect here to create it.
if (j === 0 && prop.shadow) {
var shadowBackfaceX = prop.gutterLeft + this.graphWidth + prop.gutterCenter,
shadowBackfaceWidth = this.getWidth(RG.SVG.arraySum(this.right[i]));
var rect = RG.SVG.create({
svg: this.svg,
parent: this.svg.all,
type: 'rect',
attr: {
fill: '#eee',
x: shadowBackfaceX,
y: y,
width: shadowBackfaceWidth,
height: height,
'stroke-width': 0,
'data-index': i,
filter: 'url(#dropShadow)'
}
});
this.stackedBackfacesRight[i] = rect;
}
var rect = RG.SVG.create({
svg: this.svg,
parent: this.svg.all,
type: 'rect',
attr: {
x: x,
y: y,
width: width,
height: height,
fill: prop.colorsSequential ? prop.colors[this.sequentialIndex] : prop.colors[j],
stroke: prop.strokestyle,
'stroke-width': prop.linewidth,
'shape-rendering': 'crispEdges',
'data-original-x': x,
'data-original-y': y,
'data-original-width': width,
'data-original-height': height,
'data-tooltop': (tooltip || ''),
'data-index': i,
'data-subindex': j,
'data-sequential-index': this.sequentialIndex,
'data-value': this.right[i][j]
}
});
this.coords.push({
object: this,
element: rect,
x: parseFloat(rect.getAttribute('x')),
y: parseFloat(rect.getAttribute('y')),
width: parseFloat(rect.getAttribute('width')),
height: parseFloat(rect.getAttribute('height'))
});
this.coordsRight.push({
object: this,
element: rect,
x: parseFloat(rect.getAttribute('x')),
y: parseFloat(rect.getAttribute('y')),
width: parseFloat(rect.getAttribute('width')),
height: parseFloat(rect.getAttribute('height'))
});
if (!this.coords2[i + this.left.length]) {
this.coords2[i + this.left.length] = [];
}
if (!this.coords2Right[i]) {
this.coords2Right[i] = [];
}
this.coords2[i + this.left.length].push({
object: this,
element: rect,
x: parseFloat(rect.getAttribute('x')),
y: parseFloat(rect.getAttribute('y')),
width: parseFloat(rect.getAttribute('width')),
height: parseFloat(rect.getAttribute('height'))
});
this.coords2Right[i].push({
object: this,
element: rect,
x: parseFloat(rect.getAttribute('x')),
y: parseFloat(rect.getAttribute('y')),
width: parseFloat(rect.getAttribute('width')),
height: parseFloat(rect.getAttribute('height'))
});
this.installTooltipsEventListeners({
rect: rect,
index: i,
sequentialIndex: this.sequentialIndex
});
this.sequentialIndex++;
}
// RIGHT GROUPED
} else if (RG.SVG.isArray(this.right[i]) && prop.grouping === 'grouped') {
for (var j=0; j<this.right[i].length; ++j) {
var color = prop.colors[this.sequentialIndex],
tooltip = RG.SVG.isNull(prop.tooltips) ? null : prop.tooltips[this.sequentialIndex],
height = ((this.graphHeight / this.right.length) - prop.vmargin - prop.vmargin - (prop.vmarginGrouped * (this.right[i].length - 1))) / this.right[i].length,
y = prop.gutterTop + ((this.graphHeight / this.right.length) * i) + prop.vmargin + (height * j) + (j * prop.vmarginGrouped),
width = this.getWidth(this.right[i][j]),
x = prop.gutterLeft + this.graphWidth + prop.gutterCenter;
var rect = RG.SVG.create({
svg: this.svg,
parent: this.svg.all,
type: 'rect',
attr: {
x: x,
y: y,
width: width,
height: height,
fill: prop.colorsSequential ? prop.colors[this.sequentialIndex] : prop.colors[j],
stroke: prop.strokestyle,
'stroke-width': prop.linewidth,
'shape-rendering': 'crispEdges',
'data-original-x': x,
'data-original-y': y,
'data-original-width': width,
'data-original-height': height,
'data-tooltop': (tooltip || ''),
'data-index': i,
'data-subindex': j,
'data-sequential-index': this.sequentialIndex,
'data-value': this.right[i][j],
filter: prop.shadow ? 'url(#dropShadow)' : ''
}
});
this.coords.push({
object: this,
element: rect,
x: parseFloat(rect.getAttribute('x')),
y: parseFloat(rect.getAttribute('y')),
width: parseFloat(rect.getAttribute('width')),
height: parseFloat(rect.getAttribute('height'))
});
this.coordsRight.push({
object: this,
element: rect,
x: parseFloat(rect.getAttribute('x')),
y: parseFloat(rect.getAttribute('y')),
width: parseFloat(rect.getAttribute('width')),
height: parseFloat(rect.getAttribute('height'))
});
if (!this.coords2[i + this.left.length]) {
this.coords2[i + this.left.length] = [];
}
if (!this.coords2Right[i]) {
this.coords2Right[i] = [];
}
this.coords2[i + this.left.length].push({
object: this,
element: rect,
x: parseFloat(rect.getAttribute('x')),
y: parseFloat(rect.getAttribute('y')),
width: parseFloat(rect.getAttribute('width')),
height: parseFloat(rect.getAttribute('height'))
});
this.coords2Right[i].push({
object: this,
element: rect,
x: parseFloat(rect.getAttribute('x')),
y: parseFloat(rect.getAttribute('y')),
width: parseFloat(rect.getAttribute('width')),
height: parseFloat(rect.getAttribute('height'))
});
this.installTooltipsEventListeners({
rect: rect,
index: i,
sequentialIndex: this.sequentialIndex
});
this.sequentialIndex++;
}
}
}
};
//
// Installs the tooltips event lissteners. This is called from the
// above function.
//
// @param object opt The various arguments to the function
//
this.installTooltipsEventListeners = function (opt)
{
var obj = this;
// Add the tooltip events
if (!RG.SVG.isNull(prop.tooltips) && prop.tooltips[this.sequentialIndex]) {
//
// Add tooltip event listeners
//
(function (idx, seq)
{
opt.rect.addEventListener(prop.tooltipsEvent.replace(/^on/, ''), function (e)
{
obj.removeHighlight();
// Show the tooltip
RG.SVG.tooltip({
object: obj,
index: idx,
group: null,
sequentialIndex: seq,
text: prop.tooltips[seq],
event: e
});
// Highlight the rect that has been clicked on
obj.highlight(e.target);
}, false);
opt.rect.addEventListener('mousemove', function (e)
{
e.target.style.cursor = 'pointer'
}, false);
})(opt.index, opt.sequentialIndex);
}
};
/**
*
*
* @param int value The value to get the width for.
*/
this.getWidth = function (value)
{
var x1 = this.getLeftXCoord(0),
x2 = this.getLeftXCoord(value);
if (RG.SVG.isNull(x1) || RG.SVG.isNull(x2)) {
return null;
}
return x1 - x2;
};
/**
* This function is similar to the above but instead
* of a width it gets a relevant coord for a value
* on the LEFT side
*
* @param int value The value to get the coordinate for.
*/
this.getLeftXCoord = function (value)
{
var width;
if (value > this.scale.max) {
return null;
}
if (value < this.scale.min) {
return null;
}
width = ((value - this.scale.min) / (this.scale.max - this.scale.min));
width *= this.graphWidth;
// Calculate the X coord
var x = prop.gutterLeft + this.graphWidth - width;
return x;
};
/**
* This function gets an X coordinate for the RIGHT
* side.
*
* @param int value The value to get the coordinate for.
*/
this.getRightXCoord = function (value)
{
var width;
if (value > this.scale.max) {
return null;
}
if (value < this.scale.min) {
return null;
}
width = ((value - this.scale.min) / (this.scale.max - this.scale.min));
width *= this.graphWidth;
// Calculate the X coord
var x = prop.gutterLeft + this.graphWidth + prop.gutterCenter + width;
return x;
};
/**
* This function can be used to highlight a bar on the chart
*
* @param object rect The rectangle to highlight
*/
this.highlight = function (rect)
{
var x = parseInt(rect.getAttribute('x')),
y = parseInt(rect.getAttribute('y')),
width = parseInt(rect.getAttribute('width')),
height = parseInt(rect.getAttribute('height'));
var highlight = RG.SVG.create({
svg: this.svg,
parent: this.svg.all,
type: 'rect',
attr: {
stroke: prop.highlightStroke,
fill: prop.highlightFill,
x: x - 1,
y: y - 1,
width: width + 2,
height: height + 2
},
style: {
pointerEvents: 'none'
}
});
// Store the highlight rect in the rebistry so
// it can be cleared later
RG.SVG.REG.set('highlight', highlight);
};
/**
* This allows for easy specification of gradients
*/
this.parseColors = function ()
{
// Save the original colors so that they can be restored when
// the canvas is cleared
if (!Object.keys(this.originalColors).length) {
this.originalColors = {
colors: RG.SVG.arrayClone(prop.colors),
backgroundGridColor: RG.SVG.arrayClone(prop.backgroundGridColor),
highlightFill: RG.SVG.arrayClone(prop.highlightFill),
backgroundColor: RG.SVG.arrayClone(prop.backgroundColor)
}
}
// colors
var colors = prop.colors;
if (colors) {
for (var i=0; i<colors.length; ++i) {
colors[i] = RG.SVG.parseColorLinear({
object: this,
color: colors[i],
direction: 'horizontal'
});
}
}
prop.backgroundGridColor = RG.SVG.parseColorLinear({object: this, color: prop.backgroundGridColor,direction:'horizontal'});
prop.highlightFill = RG.SVG.parseColorLinear({object: this, color: prop.highlightFill,direction:'horizontal'});
prop.backgroundColor = RG.SVG.parseColorLinear({object: this, color: prop.backgroundColor,direction:'horizontal'});
};
//
// Draws the labelsAbove
//
this.drawLabelsAbove = function ()
{
// Go through the above labels
if (prop.labelsAbove) {
//var data_seq = RG.SVG.arrayLinearize(this.data),
// seq = 0,
// stacked_total = 0;
for (var dataset=0,seq=0; dataset<this.data.length; ++dataset,++seq) {
for (var i=0; i<this.data[dataset].length; ++i,++seq) {
var value = this.data[dataset][i],
halign = dataset === 0 ? 'right' : 'left',
valign = 'center',
hoffset = dataset === 0 ? -10 : 10;
// REGULAR CHART
if (typeof value === 'number') {
var x = parseInt(this.coords[seq].element.getAttribute('x')) + hoffset + prop.labelsAboveOffsetx,
height = parseInt(this.coords[seq].element.getAttribute('height')),
y = parseInt(this.coords[seq].element.getAttribute('y')) + (height / 2) + prop.labelsAboveOffsety,
width = parseInt(this.coords[seq].element.getAttribute('width'));
// If the dataset is the RHS (which would equal )
// then set the alignment appropriately
if (dataset === 1) {
x += width;
}
var str = RG.SVG.numberFormat({
object: this,
num: value.toFixed(prop.labelsAboveDecimals),
prepend: typeof prop.labelsAboveUnitsPre === 'string' ? prop.labelsAboveUnitsPre : null,
append: typeof prop.labelsAboveUnitsPost === 'string' ? prop.labelsAboveUnitsPost : null,
point: typeof prop.labelsAbovePoint === 'string' ? prop.labelsAbovePoint : null,
thousand: typeof prop.labelsAboveThousand === 'string' ? prop.labelsAboveThousand : null,
formatter: typeof prop.labelsAboveFormatter === 'function' ? prop.labelsAboveFormatter : null
});
// Facilitate labelsAboveSpecific
if (prop.labelsAboveSpecific && prop.labelsAboveSpecific.length && (typeof prop.labelsAboveSpecific[seq] === 'string' || typeof prop.labelsAboveSpecific[seq] === 'number') ) {
str = parseStr(prop.labelsAboveSpecific[seq]);
} else if ( prop.labelsAboveSpecific && prop.labelsAboveSpecific.length && typeof prop.labelsAboveSpecific[seq] !== 'string' && typeof prop.labelsAboveSpecific[seq] !== 'number') {
continue;
}
// Add the text to the SVG
RG.SVG.text({
object: this,
parent: this.svg.all,
text: str,
x: x,
y: y,
halign: halign,
valign: valign,
tag: 'labels.above',
font: prop.labelsAboveFont || prop.textFont,
size: prop.labelsAboveSize || prop.textSize,
bold: typeof prop.labelsAboveBold === 'boolean' ? prop.labelsAboveBold : prop.textBold,
italic: typeof prop.labelsAboveItalic === 'boolean' ? prop.labelsAboveItalic : prop.textItalic,
color: prop.labelsAboveColor || prop.textColor,
background: prop.labelsAboveBackground,
padding: prop.labelsAboveBackgroundPadding
});
// STACKED CHART
} else if (typeof value === 'object' && prop.grouping === 'stacked') {
for (var k=0,sum=0,width=0; k<this.coords2[i].length; ++k) {
sum += parseFloat(this.coords2[i][k].element.getAttribute('data-value'));
}
var len =this.coords2[i].length;
if (dataset === 0) {
var x = parseFloat(this.coords2[i][len - 1].x) + hoffset,
height = parseFloat(this.coords2[i][len - 1].height),
y = parseFloat(this.coords2[i][0].y) + (height / 2);
} else {
var x = parseFloat(this.coords2[this.data[0].length + i][0].x) + hoffset + prop.labelsAboveOffsetx,
height = parseFloat(this.coords2[i][len - 1].height),
y = parseFloat(this.coords2[i][0].y) + (height / 2) + prop.labelsAboveOffsety;
// Work out the total width by summing all the individual widths
for (var j=0; j<this.coords2Right[i].length; ++j) {
x += this.coords2Right[i][j].width;
}
}
var str = RG.SVG.numberFormat({
object: this,
num: sum.toFixed(prop.labelsAboveDecimals),
prepend: typeof prop.labelsAboveUnitsPre === 'string' ? prop.labelsAboveUnitsPre : null,
append: typeof prop.labelsAboveUnitsPost === 'string' ? prop.labelsAboveUnitsPost : null,
point: typeof prop.labelsAbovePoint === 'string' ? prop.labelsAbovePoint : null,
thousand: typeof prop.labelsAboveThousand === 'string' ? prop.labelsAboveThousand : null,
formatter: typeof prop.labelsAboveFormatter === 'function' ? prop.labelsAboveFormatter : null
});
// Facilitate labelsAboveSpecific
if (prop.labelsAboveSpecific && prop.labelsAboveSpecific.length && (typeof prop.labelsAboveSpecific[seq] === 'string' || typeof prop.labelsAboveSpecific[seq] === 'number') ) {
str = parseStr(prop.labelsAboveSpecific[seq]);
} else if ( prop.labelsAboveSpecific && prop.labelsAboveSpecific.length && typeof prop.labelsAboveSpecific[seq] !== 'string' && typeof prop.labelsAboveSpecific[seq] !== 'number') {
continue;
}
// Add the text to the SVG
RG.SVG.text({
object: this,
parent: this.svg.all,
text: str,
x: x,
y: y,
halign: halign,
valign: valign,
tag: 'labels.above',
font: prop.labelsAboveFont || prop.textFont,
size: prop.labelsAboveSize || prop.textSize,
bold: typeof prop.labelsAboveBold === 'boolean' ? prop.labelsAboveBold : prop.textBold,
italic: typeof prop.labelsAboveItalic === 'boolean' ? prop.labelsAboveItalic : prop.textItalic,
color: prop.labelsAboveColor || prop.textColor,
background: prop.labelsAboveBackground,
padding: prop.labelsAboveBackgroundPadding
});
// GROUPED CHART
} else if (typeof value === 'object' && prop.grouping === 'grouped') {
for (var k=0; k<value.length; ++k) {
val = value[k];
var x = parseInt(this.coords[seq].element.getAttribute('x')) + hoffset + prop.labelsAboveOffsetx,
height = parseInt(this.coords[seq].element.getAttribute('height')),
y = parseInt(this.coords[seq].element.getAttribute('y')) + (height / 2) + prop.labelsAboveOffsety,
width = parseInt(this.coords[seq].element.getAttribute('width'));
// If the dataset is the RHS (which would equal )
// then set the alignment appropriately
if (dataset === 1) {
x += width;
}
var str = RG.SVG.numberFormat({
object: this,
num: parseFloat(val).toFixed(prop.labelsAboveDecimals),
prepend: typeof prop.labelsAboveUnitsPre === 'string' ? prop.labelsAboveUnitsPre : null,
append: typeof prop.labelsAboveUnitsPost === 'string' ? prop.labelsAboveUnitsPost : null,
point: typeof prop.labelsAbovePoint === 'string' ? prop.labelsAbovePoint : null,
thousand: typeof prop.labelsAboveThousand === 'string' ? prop.labelsAboveThousand : null,
formatter: typeof prop.labelsAboveFormatter === 'function' ? prop.labelsAboveFormatter : null
});
// Facilitate labelsAboveSpecific
if (prop.labelsAboveSpecific && prop.labelsAboveSpecific.length && (typeof prop.labelsAboveSpecific[seq] === 'string' || typeof prop.labelsAboveSpecific[seq] === 'number') ) {
str = parseStr(prop.labelsAboveSpecific[seq]);
} else if ( prop.labelsAboveSpecific && prop.labelsAboveSpecific.length && typeof prop.labelsAboveSpecific[seq] !== 'string' && typeof prop.labelsAboveSpecific[seq] !== 'number') {
continue;
}
// Add the text to the SVG
RG.SVG.text({
object: this,
parent: this.svg.all,
text: str,
x: x,
y: y,
halign: halign,
valign: valign,
tag: 'labels.above',
font: prop.labelsAboveFont || prop.textFont,
size: prop.labelsAboveSize || prop.textSize,
bold: typeof prop.labelsAboveBold === 'boolean' ? prop.labelsAboveBold : prop.textBold,
italic: typeof prop.labelsAboveItalic === 'boolean' ? prop.labelsAboveItalic : prop.textItalic,
color: prop.labelsAboveColor || prop.textColor,
background: prop.labelsAboveBackground,
padding: prop.labelsAboveBackgroundPadding
});
seq++;
}
seq--;
}
}
--seq;
}
}
};
/**
* 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;
}
RG.SVG.addCustomEventListener(this, type, func);
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;
};
//
// Remove highlight from the chart (tooltips)
//
this.removeHighlight = function ()
{
var highlight = RG.SVG.REG.get('highlight');
if (highlight && highlight.parentNode) {
highlight.parentNode.removeChild(highlight);
}
RG.SVG.REG.set('highlight', null);
};
//
// Calulate the center gutter size
//
this.getGutterCenter = function ()
{
var bold = typeof prop.yaxisTextBold === 'boolean' ? prop.yaxisTextBold : prop.textBold,
font = typeof prop.yaxisTextFont === 'string' ? prop.yaxisTextFont : prop.textFont,
size = typeof prop.yaxisTextSize === 'number' ? prop.yaxisTextSize : prop.textSize,
width = 0;
// Loop through the labels measuring them
if (prop.yaxisLabels) {
for (var i=0,len=prop.yaxisLabels.length; i<len; ++i) {
width = ma.max(width, RG.SVG.measureText({
text: prop.yaxisLabels[i],
bold: bold,
font: font,
size: size
})[0]);
}
} else {
var width = 50;
}
return width + 15;
};
//
// Draw the title
//
this.drawTitle = function ()
{
if (RG.SVG.isNull(prop.titleX)) {
prop.titleX = ((this.width - prop.gutterLeft - prop.gutterRight) / 2) + prop.gutterLeft;
}
RG.SVG.drawTitle(this);
};
//
// The Bar chart grow effect
//
this.grow = function ()
{
var opt = arguments[0] || {},
frames = opt.frames || 30,
frame = 0,
obj = this,
left = RG.SVG.arrayClone(this.left),
right = RG.SVG.arrayClone(this.right),
seq = 0;
this.draw();
var iterate = function ()
{
// LOOP THROUGH THE LEFT DATA
for (var i=0,seq=0,len=obj.coordsLeft.length; i<len; ++i, ++seq) {
var multiplier = (frame / frames)
* RG.SVG.FX.getEasingMultiplier(frames, frame)
* RG.SVG.FX.getEasingMultiplier(frames, frame);
// The main loop through the data
// LEFT REGULAR
if (typeof left[i] === 'number') {
width = ma.abs(obj.getLeftXCoord(left[i]) - obj.getLeftXCoord(0));
left[i] = obj.left[i] * multiplier;
// Set the new height on the rect
obj.coordsLeft[i].element.setAttribute(
'width',
width
);
// Set the correct Y coord on the object
obj.coords[seq].element.setAttribute(
'x',
obj.getLeftXCoord(0) - width
);
// LEFT STACKED
} else if (typeof left[i] === 'object' && prop.grouping === 'stacked') {
var accumulativeWidth = 0;
for (var j=0,len2=left[i].length; j<len2; ++j, ++seq) {
width = ma.abs(obj.getLeftXCoord(left[i][j]) - obj.getLeftXCoord(0));
left[i][j] = obj.left[i][j] * multiplier;
obj.coords[seq].element.setAttribute(
'width',
width
);
obj.coords[seq].element.setAttribute(
'x',
obj.getLeftXCoord(0) - width - accumulativeWidth
);
accumulativeWidth += (prop.grouping === 'stacked' ? width : 0);
}
//
// Set the width and X coord of the backfaces
//
if (obj.stackedBackfacesLeft[i]) {
obj.stackedBackfacesLeft[i].setAttribute(
'width',
accumulativeWidth
);
obj.stackedBackfacesLeft[i].setAttribute(
'x',
obj.getLeftXCoord(0) - accumulativeWidth
);
}
// Decrease seq by one so that it's not incremented twice
--seq;
// LEFT GROUPED
} else if (typeof left[i] === 'object' && prop.grouping === 'grouped') {
// Loop thru the group
for (var j=0,len2=left[i].length; j<len2; ++j, ++seq) {
width = ma.abs(obj.getLeftXCoord(left[i][j]) - obj.getLeftXCoord(0));
left[i][j] = obj.left[i][j] * multiplier;
obj.coords[seq].element.setAttribute(
'width',
width
);
obj.coords[seq].element.setAttribute(
'x',
obj.getLeftXCoord(0) - width
);
}
// Decrease seq by one so that it's not incremented twice
--seq;
}
}
// LOOP THROUGH THE RIGHT DATA
for (var i=0,seq=0,len=obj.coordsRight.length; i<len; ++i, ++seq) {
var multiplier = (frame / frames)
* RG.SVG.FX.getEasingMultiplier(frames, frame)
* RG.SVG.FX.getEasingMultiplier(frames, frame);
// The main loop through the data
// RIGHT REGULAR
if (typeof right[i] === 'number') {
width = ma.abs(obj.getRightXCoord(right[i]) - obj.getRightXCoord(0));
right[i] = obj.right[i] * multiplier;
// Set the new height on the rect
obj.coordsRight[i].element.setAttribute(
'width',
width
);
// Set the correct Y coord on the object
obj.coordsRight[seq].element.setAttribute(
'x',
obj.getRightXCoord(0)
);
// RIGHT STACKED
} else if (typeof right[i] === 'object' && prop.grouping === 'stacked') {
var accumulativeWidth = 0;
for (var j=0,len2=right[i].length; j<len2; ++j, ++seq) {
width = ma.abs(obj.getRightXCoord(right[i][j]) - obj.getRightXCoord(0));
right[i][j] = obj.right[i][j] * multiplier;
obj.coordsRight[seq].element.setAttribute(
'width',
width
);
obj.coordsRight[seq].element.setAttribute(
'x',
obj.getRightXCoord(0) + accumulativeWidth
);
accumulativeWidth += width;
}
//
// Set the width and X coord of the backfaces
//
if (obj.stackedBackfacesRight[i]) {
obj.stackedBackfacesRight[i].setAttribute(
'width',
accumulativeWidth
);
obj.stackedBackfacesRight[i].setAttribute(
'x',
obj.getRightXCoord(0)
);
}
// Decrease seq by one so that it's not incremented twice
--seq;
// RIGHT GROUPED
} else if (typeof right[i] === 'object' && prop.grouping === 'grouped') {
// Loop thru the group
for (var j=0,len2=right[i].length; j<len2; ++j, ++seq) {
width = ma.abs(obj.getRightXCoord(right[i][j]) - obj.getRightXCoord(0));
right[i][j] = obj.right[i][j] * multiplier;
obj.coordsRight[seq].element.setAttribute(
'width',
width
);
obj.coordsRight[seq].element.setAttribute(
'x',
obj.getRightXCoord(0)
);
}
// Decrease seq by one so that it's not incremented twice
--seq;
}
}
if (frame++ <= frames) {
RG.SVG.FX.update(iterate);
} else if (opt.callback) {
(opt.callback)(obj);
}
};
iterate();
return this;
};
/**
* HBar chart Wave effect.
*
* @param object OPTIONAL An object map of options. You specify 'frames'
* here to give the number of frames in the effect
* and also callback to specify a callback function
* thats called at the end of the effect
*/
this.wave = function ()
{
var obj = this,
opt = arguments[0] || {},
frames = opt.frames || 120,
startFrames_left = [],
startFrames_right = [],
counters_left = [],
counters_right = [];
var framesperbar = frames / 3,
frame_left = -1,
frame_right = -1,
callback = arguments[1] || function () {},
original_left = RG.SVG.arrayClone(this.left),
original_right = RG.SVG.arrayClone(this.right);
for (var i=0,len=this.left.length,seq=0; i<len; i+=1,++seq) {
startFrames_left[seq] = ((frames / 3) / (RG.SVG.arrayLinearize(this.left).length - 1)) * i;
startFrames_right[seq] = ((frames / 3) / (RG.SVG.arrayLinearize(this.right).length - 1)) * i;
counters_left[seq] = 0;
counters_right[seq] = 0;
if (RG.SVG.isArray(this.left[i])) {
for (var j=0; j<this.left[i].length; ++j,seq++) {
startFrames_left[seq] = ((frames / 3) / (RG.SVG.arrayLinearize(this.left).length - 1)) * seq;
startFrames_right[seq] = ((frames / 3) / (RG.SVG.arrayLinearize(this.right).length - 1)) * seq;
counters_left[seq] = 0;
counters_right[seq] = 0;
}
--seq;
}
}
// This stops the chart from jumping
this.draw();
// Zero all of the data and set all of the rect widths to zero
for (var i=0,len=this.left.length; i<len; i+=1) {
if (typeof this.left[i] === 'number') {
this.left[i] = 0;
this.right[i] = 0;
this.coordsLeft[i].element.setAttribute('width', 0);
this.coordsRight[i].element.setAttribute('width', 0);
} else if (typeof this.left[i] === 'object' && !RG.SVG.isNull(this.left[i])) {
for (var j=0; j<this.left[i].length; ++j) {
this.left[i][j] = 0;
this.right[i][j] = 0;
this.coords2Left[i][j].element.setAttribute('width', 0);
this.coords2Right[i][j].element.setAttribute('width', 0);
}
}
}
//
// Iterate over the left side
//
function iteratorLeft ()
{
++frame_left;
for (var i=0,len=obj.left.length,seq=0; i<len; i+=1,seq+=1) {
if (frame_left >= startFrames_left[seq]) {
var isNull = RG.SVG.isNull(obj.left[i]);
// Regular bars
if (typeof obj.left[i] === 'number') {
obj.left[i] = ma.min(
ma.abs(original_left[i]),
ma.abs(original_left[i] * ( (counters_left[i]++) / framesperbar))
);
var rect_left = obj.coords[i].element;
rect_left.setAttribute(
'width',
parseFloat(rect_left.getAttribute('data-original-width')) * (obj.left[i] / rect_left.getAttribute('data-value'))
);
rect_left.setAttribute(
'x',
obj.properties.gutterLeft + obj.graphWidth - (parseFloat(rect_left.getAttribute('data-original-width')) * (obj.left[i] / rect_left.getAttribute('data-value')))
);
// Stacked or grouped bars
} else if (RG.SVG.isArray(obj.left[i])) {
for (var j=0,accWidth=0; j<obj.left[i].length; ++j,++seq) {
obj.left[i][j] = ma.min(
ma.abs(original_left[i][j]),
ma.abs(original_left[i][j] * ( (counters_left[seq]++) / framesperbar))
);
var rect_left = obj.coords[seq].element;
rect_left.setAttribute(
'width',
parseFloat(rect_left.getAttribute('data-original-width')) * (obj.left[i][j] / rect_left.getAttribute('data-value'))
);
rect_left.setAttribute(
'x',
obj.properties.gutterLeft + obj.graphWidth - (parseFloat(rect_left.getAttribute('data-original-width')) * (obj.left[i][j] / rect_left.getAttribute('data-value'))) - accWidth
);
// Only update this for stacked charts
if (obj.properties.grouping === 'stacked') {
accWidth += parseFloat(rect_left.getAttribute('width'));
}
}
seq--;
}
if (isNull) {
obj.left[i] = null;
}
} else {
obj.left[i] = typeof obj.left[i] === 'object' && obj.left[i] ? RG.SVG.arrayPad([], obj.left[i].length, 0) : (RG.SVG.isNull(obj.left[i]) ? null : 0);
}
}
// No callback here - only called by the right function
if (frame_left <= frames) {
RG.SVG.FX.update(iteratorLeft);
}
}
//
// Iterate over the left side
//
function iteratorRight ()
{
++frame_right;
for (var i=0,len=obj.right.length,seq=0; i<len; i+=1,seq+=1) {
if (frame_right >= startFrames_right[seq]) {
var isNull = RG.SVG.isNull(obj.right[i]);
// Regular bars
if (typeof obj.right[i] === 'number') {
obj.right[i] = ma.min(
ma.abs(original_right[i]),
ma.abs(original_right[i] * ( (counters_right[i]++) / framesperbar))
);
var rect_right = obj.coords[i + obj.left.length].element;
rect_right.setAttribute(
'width',
parseFloat(rect_right.getAttribute('data-original-width')) * (obj.right[i] / rect_right.getAttribute('data-value'))
);
rect_right.setAttribute(
'x',
obj.properties.gutterLeft + obj.graphWidth + prop.gutterCenter
);
// Stacked or grouped bars
} else if (RG.SVG.isArray(obj.right[i])) {
for (var j=0,accWidth=0; j<obj.right[i].length; ++j,++seq) {
obj.right[i][j] = ma.min(
ma.abs(original_right[i][j]),
ma.abs(original_right[i][j] * ( (counters_right[seq]++) / framesperbar))
);
var rect_right = obj.coordsRight[seq].element;
rect_right.setAttribute(
'width',
parseFloat(rect_right.getAttribute('data-original-width')) * (obj.right[i][j] / rect_right.getAttribute('data-value'))
);
rect_right.setAttribute(
'x',
obj.properties.gutterLeft + obj.graphWidth + prop.gutterCenter + accWidth
);
// Only update this for stacked charts
if (obj.properties.grouping === 'stacked') {
accWidth += parseFloat(rect_right.getAttribute('width'));
}
}
seq--;
}
if (isNull) {
obj.right[i] = null;
}
} else {
obj.right[i] = typeof obj.right[i] === 'object' && obj.right[i] ? RG.SVG.arrayPad([], obj.right[i].length, 0) : (RG.SVG.isNull(obj.right[i]) ? null : 0);
}
}
// Call the callback if necessary
if (frame_right <= frames) {
RG.SVG.FX.update(iteratorRight);
} else {
// Fini - call the callback
}
}
iteratorLeft();
iteratorRight();
return this;
};
//
// Set the options that the user has provided
//
for (i in conf.options) {
if (typeof i === 'string') {
this.set(i, conf.options[i]);
}
}
};
return this;
// End module pattern
})(window, document);