Sponsoring The Perl Toolchain Summit 2025: Help make this important event another success 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.Scatter = 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.width = Number(this.svg.getAttribute('width'));
this.height = Number(this.svg.getAttribute('height'));
this.data = conf.data;
this.type = 'scatter';
this.coords = [];
this.coords2 = [];
this.colorsParsed = false;
this.originalColors = {};
this.gradientCounter = 1;
this.sequential = 0;
// 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,
backgroundColor: null,
backgroundImage: null,
backgroundImageAspect: 'none',
backgroundImageStretch: true,
backgroundImageOpacity: null,
backgroundImageX: null,
backgroundImageY: null,
backgroundImageW: null,
backgroundImageH: null,
backgroundGrid: true,
backgroundGridColor: '#ddd',
backgroundGridLinewidth: 1,
backgroundGridHlines: true,
backgroundGridHlinesCount: null,
backgroundGridVlines: true,
backgroundGridVlinesCount: null,
backgroundGridBorder: true,
backgroundGridDashed: false,
backgroundGridDotted: false,
backgroundGridDashArray: null,
xmax: 0,
tickmarksStyle: 'cross',
tickmarksSize: 7,
colors: ['black'],
line: false,
lineColors: 1,
lineLinewidth: 1,
errorbarsColor: 'black',
errorbarsLinewidth: 1,
errorbarsCapwidth: 10,
yaxis: true,
yaxisTickmarks: true,
yaxisTickmarksLength: 3,
yaxisColor: 'black',
yaxisScale: true,
yaxisLabels: null,
yaxisLabelsOffsetx: 0,
yaxisLabelsOffsety: 0,
yaxisLabelsCount: 5,
yaxisUnitsPre: '',
yaxisUnitsPost: '',
yaxisStrict: false,
yaxisDecimals: 0,
yaxisPoint: '.',
yaxisThousand: ',',
yaxisRound: false,
yaxisMax: null,
yaxisMin: 0,
yaxisFormatter: null,
xaxis: true,
xaxisTickmarks: true,
xaxisTickmarksLength: 5,
xaxisLabels: null,
xaxisLabelsPosition: 'section',
xaxisLabelsPositionEdgeTickmarksCount: 10,
xaxisColor: 'black',
xaxisLabelsOffsetx: 0,
xaxisLabelsOffsety: 0,
xaxisMin: 0,
xaxisMax: null,
xaxisLabelsCount: 10,
xaxisUnitsPre: '',
xaxisUnitsPost: '',
xaxisMax: null,
xaxisMin: 0,
xaxisPoint: '.',
xaxisRound: false,
xaxisThousand: ',',
xaxisDecimals: 0,
xaxisFormatter: null,
textColor: 'black',
textFont: 'sans-serif',
textSize: 12,
textBold: false,
textItalic: false,
labelsAboveFont: null,
labelsAboveSize: null,
labelsAboveBold: null,
labelsAboveItalic: null,
labelsAboveColor: null,
labelsAboveBackground: 'rgba(255,255,255,0.7)',
labelsAboveBackgroundPadding: 2,
labelsAboveXUnitsPre: null,
labelsAboveXUnitsPost: null,
labelsAboveXPoint: null,
labelsAboveXThousand: null,
labelsAboveXFormatter: null,
labelsAboveXDecimals: null,
labelsAboveXDecimalsTrim: null,
labelsAboveYUnitsPre: null,
labelsAboveYUnitsPost: null,
labelsAboveYPoint: null,
labelsAboveYThousand: null,
labelsAboveYFormatter: null,
labelsAboveYDecimals: null,
labelsAboveOffsetx: 0,
labelsAboveOffsety: -10,
labelsAboveHalign: 'center',
labelsAboveValign: 'bottom',
labelsAboveSeperator: ',',
tooltipsOverride: null,
tooltipsEffect: 'fade',
tooltipsCssClass: 'RGraph_tooltip',
tooltipsEvent: 'mousemove',
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,
key: null,
keyColors: null,
keyOffsetx: 0,
keyOffsety: 0,
keyTextOffsetx: 0,
keyTextOffsety: -1,
keyTextSize: null,
keyTextBold: null,
keyTextItalic: null,
bubble: false,
bubbleMaxValue: null,
bubbleMaxRadius: null,
bubbleColorsSolid: false,
errorbars: null,
errorbarsColor: 'black',
errorbarsLinewidth: 1,
errorbarsCapwidth: 10,
};
//
// Copy the global object properties to this instance
//
RG.SVG.getGlobals(this);
//
// Set the options that the user has provided
//
for (i in conf.options) {
if (typeof i === 'string') {
this.set(i, conf.options[i]);
}
}
// Handles the data that was supplied to the object. If only one dataset
// was given, convert it into into a multiple dataset style array
if (this.data[0] && !RG.SVG.isArray(this.data[0])) {
this.data = [];
this.data[0] = conf.data;
}
/**
* "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;
//
// Convert string X values to timestamps
//
if (typeof prop.xaxisMin === 'string') {
prop.xaxisMin = RG.SVG.parseDate(prop.xaxisMin);
}
if (typeof prop.xaxisMax === 'string') {
prop.xaxisMax = RG.SVG.parseDate(prop.xaxisMax);
}
for (var i=0; i<this.data.length; ++i) {
for (var j=0; j<this.data[i].length; ++j) {
if (typeof this.data[i][j].x === 'string') {
this.data[i][j].x = RG.SVG.parseDate(this.data[i][j].x);
}
}
}
//
// 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);
this.graphWidth = this.width - prop.gutterLeft - prop.gutterRight;
this.graphHeight = this.height - prop.gutterTop - prop.gutterBottom;
// Prevents these from growing
this.coords = [];
this.coords2 = [];
// Parse the colors for gradients
RG.SVG.resetColorsToOriginalValues({object:this});
this.parseColors();
// Work out the maximum value
for (var ds=0,max=0; ds<this.data.length; ++ds) { // Datasets
for (var dp=0; dp<this.data[ds].length; ++dp) { // Datapoints
max = ma.max(
max,
this.data[ds][dp].y + (this.data[ds][dp].errorbar ? (typeof this.data[ds][dp].errorbar === 'number' ? this.data[ds][dp].errorbar : this.data[ds][dp].errorbar.max) : 0)
);
}
}
// A custom, user-specified maximum value
if (typeof prop.yaxisMax === 'number') {
max = prop.yaxisMax;
}
// Set the ymin to zero if it's set mirror
if (prop.yaxisMin === 'mirror' || prop.yaxisMin === 'middle' || prop.yaxisMin === 'center') {
var mirrorScale = true;
prop.yaxisMin = 0;
}
//
// Generate an appropiate scale
//
this.scale = RG.SVG.getScale({
object: this,
numlabels: prop.yaxisLabelsCount,
unitsPre: prop.yaxisUnitsPre,
unitsPost: prop.yaxisUnitsPost,
max: max,
min: prop.yaxisMin,
point: prop.yaxisPoint,
round: prop.yaxisRound,
thousand: prop.yaxisThousand,
decimals: prop.yaxisDecimals,
strict: typeof prop.yaxisMax === 'number',
formatter: prop.yaxisFormatter
});
//
// Get the scale a second time if the ymin should be mirored
//
// Set the ymin to zero if it's set mirror
if (mirrorScale) {
this.scale = RG.SVG.getScale({
object: this,
numlabels: prop.yaxisLabelsCount,
unitsPre: prop.yaxisUnitsPre,
unitsPost: prop.yaxisUnitsPost,
max: this.scale.max,
min: this.scale.max * -1,
point: prop.yaxisPoint,
round: false,
thousand: prop.yaxisThousand,
decimals: prop.yaxisDecimals,
strict: typeof prop.yaxisMax === 'number',
formatter: prop.yaxisFormatter
});
}
// 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
RG.SVG.drawBackground(this);
// Draw the axes under the points
RG.SVG.drawXAxis(this);
RG.SVG.drawYAxis(this);
// Create a group for all of the datasets
var dataset_group = RGraph.SVG.create({
svg: this.svg,
type: 'g',
parent: this.svg.all,
attr: {
className: 'scatter_datasets_' + this.uid
}
});
// Draw the points for all of the datasets
for (var i=0; i<this.data.length; ++i) {
this.drawPoints({
index: i,
data: this.data[i],
group: dataset_group
});
// Draw a line for this dataset
if (prop.line === true || (typeof prop.line === 'object' && prop.line[i] === true)) {
this.drawLine({
index: i,
coords: this.coords2[i]
});
}
}
// Draw the key
if (typeof prop.key !== null && RG.SVG.drawKey) {
RG.SVG.drawKey(this);
} else if (!RGraph.SVG.isNull(prop.key)) {
alert('The drawKey() function does not exist - have you forgotten to include the key library?');
}
// Add the event listener that clears the highlight rect if
// there is any. Must be MOUSEDOWN (ie before the click event)
//var obj = this;
//doc.body.addEventListener('mousedown', function (e)
//{
//RG.SVG.removeHighlight(obj);
//}, false);
// Fire the draw event
RG.SVG.fireCustomEvent(this, 'ondraw');
return this;
};
//
// Draws the Points
//
// @param opt object Options to the function which can consist of:
// o index: The numerical index of the DATASET
// o dataset: The dataset.
//
this.drawPoints = function (opt)
{
var index = opt.index,
data = opt.data,
group = opt.group;
// Initialise the array for coordinates
if (!this.coords2[index]) {
this.coords2[index] = [];
}
//
// Create the <g> tag that the datapoints are added to
//
var group = RG.SVG.create({
svg: this.svg,
type: 'g',
parent: group,
attr: {
className: 'scatter_dataset_' + index + '_' + this.uid
}
});
// Loop through the data
for (var i=0; i<data.length; ++i) {
var point = data[i];
if (typeof point.x === 'number'&& typeof point.y === 'number') {
var ret = this.drawSinglePoint({
dataset: data,
datasetIdx: index,
point: point,
index: i,
group: group, // The SVG <g> tag the the points are added to
sequential: this.sequential
});
// Add the coordinates to the coords arrays
this.coords.push({
x: ret.x,
y: ret.y,
z: ret.size,
type: ret.type,
element: ret.mark,
object: this
});
this.coords2[index][i] = {
x: ret.x,
y: ret.y,
z: ret.size,
type: ret.type,
element: ret.mark,
object: this
};
this.sequential++
}
//
// Add tooltip highlight to the point
//
if ( (typeof data[i].tooltip === 'string' && data[i].tooltip) || (typeof data[i].tooltip === 'number') ) {
// Convert the tooltip to a string
data[i].tooltip = String(data[i].tooltip);
// Make the tooltipsEvent default to click
if (prop.tooltipsEvent !== 'mousemove') {
prop.tooltipsEvent = 'click';
}
if (!group_tooltip_hotspots) {
var group_tooltip_hotspots = RG.SVG.create({
svg: this.svg,
parent: this.svg.all,
type: 'g',
attr: {
className: 'rgraph-scatter-tooltip-hotspots'
}
});
}
var rect = RG.SVG.create({
svg: this.svg,
parent: this.svg.all,
type: 'rect',
parent: group_tooltip_hotspots,
attr: {
x: ret.x - (ret.size / 2),
y: ret.y - (ret.size / 2),
width: ret.size,
height: ret.size,
fill: 'transparent',
stroke: 'transparent',
'stroke-width': 0
},
style: {
cursor: 'pointer'
}
});
// Add the hotspot to the original tickmark
ret.mark.hotspot = rect;
(function (dataset, index, seq, obj)
{
rect.addEventListener(prop.tooltipsEvent, function (e)
{
var tooltip = RG.SVG.REG.get('tooltip');
if (tooltip && tooltip.__dataset__ === dataset && tooltip.__index__ === index) {
return;
}
obj.removeHighlight();
// Show the tooltip
RG.SVG.tooltip({
object: obj,
dataset: dataset,
index: index,
sequentialIndex: seq,
text: obj.data[dataset][index].tooltip,
event: e
});
// Highlight the shape that has been clicked on
if (RG.SVG.REG.get('tooltip')) {
obj.highlight(this);
}
}, false);
// Install the event listener that changes the
// cursor if necessary
if (prop.tooltipsEvent === 'click') {
rect.addEventListener('mousemove', function (e)
{
e.target.style.cursor = 'pointer';
}, false);
}
}(index, i, this.sequential - 1, this));
}
}
};
//
// Draws a single point on the chart
//
this.drawSinglePoint = function (opt)
{
var dataset = opt.dataset,
datasetIdx = opt.datasetIdx,
seq = opt.sequential,
point = opt.point,
index = opt.index,
valueX = opt.point.x,
valueY = opt.point.y,
conf = opt.point || {},
group = opt.group,
coordX = opt.coordx = this.getXCoord(valueX),
coordY = opt.coordy = this.getYCoord(valueY);
// Get the above label
if (conf.labelsAbove) {
var above = true;
} else if (conf.labelAbove) {
var above = true;
} else if (conf.above) {
var above = true;
}
// Allow shape to be synonym for type
if (typeof conf.type === 'undefined' && typeof conf.shape !== 'undefined') {
conf.type = conf.shape;
}
// set the type to the default if its not set
if (typeof conf.type === 'string') {
// nada
} else if (typeof prop.tickmarksStyle === 'string') {
conf.type = prop.tickmarksStyle;
} else if (typeof prop.tickmarksStyle === 'object' && typeof prop.tickmarksStyle[datasetIdx] === 'string') {
conf.type = prop.tickmarksStyle[datasetIdx];
}
// set the size to the default if its not set
if (typeof conf.size !== 'number' && typeof prop.tickmarksSize === 'number') {
conf.size = prop.tickmarksSize;
} else if (typeof conf.size !== 'number' && typeof prop.tickmarksSize === 'object' && typeof prop.tickmarksSize[datasetIdx] === 'number') {
conf.size = prop.tickmarksSize[datasetIdx];
}
// Set the color to the default if its not set and then blacck if thats not set either
if (typeof conf.color === 'string') {
// nada
} else if (typeof prop.colors[datasetIdx] === 'string') {
conf.color = prop.colors[datasetIdx];
} else {
conf.color = 'black';
}
// Set the opacity of this point
if (typeof conf.opacity === 'undefined') {
conf.opacity = 1;
} else if (typeof conf.opacity === 'number') {
// nada
}
// Draw the errorbar here
//
// First convert the errorbar information in the data into an array in the properties
//
prop.errorbars = [];
for (var ds=0,max=0; ds<this.data.length; ++ds) {
for (var idx=0; idx<this.data[ds].length; ++idx) {
prop.errorbars.push(this.data[ds][idx].errorbar);
}
}
this.drawErrorbar({
object: this,
dataset: datasetIdx,
index: index,
group: group,
sequential: seq,
x: coordX,
y: coordY,
valueX: valueX,
valueY: valueY,
parent: group
});
// Bubble charts are drawn by their own function
if (prop.bubble) {
return this.drawBubble(opt, conf);
}
// Handle the various shapes for tickmarks here
switch (conf.type) {
case 'image:' + conf.type.substr(6):
var src = conf.type.substr(6);
var img = new Image();
img.src = src;
var mark = RG.SVG.create({
svg: this.svg,
type: 'image',
parent: group,
attr: {
preserveAspectRatio: 'xMidYMid meet',
'xlink:href': src
}
});
// Once the image has loaded the x/y/width/height can be set
// (both the image and it's hotspot)
img.onload = function ()
{
var x = coordX - (img.width / 2),
y = coordY - (img.height / 2),
w = img.width,
h = img.height;
mark.setAttribute('x', x);
mark.setAttribute('y', y);
mark.setAttribute('width', w);
mark.setAttribute('height', h);
if (mark && mark.hotspot) {
mark.hotspot.setAttribute('x', x);
mark.hotspot.setAttribute('y', y);
mark.hotspot.setAttribute('width', w);
mark.hotspot.setAttribute('height', h);
}
};
break;
case 'triangle':
var mark = RG.SVG.create({
svg: this.svg,
type: 'path',
parent: group,
attr: {
d: 'M {1} {2} L {3} {4} L {5} {6}'.format(
coordX - (conf.size / 2),
coordY + (conf.size / 2),
coordX,
coordY - (conf.size / 2),
coordX + (conf.size / 2),
coordY + (conf.size / 2)
),
fill: conf.color,
'fill-opacity': conf.opacity
}
});
break;
case 'plus':
var mark = RG.SVG.create({
svg: this.svg,
type: 'path',
parent: group,
attr: {
d: 'M {1} {2} L {3} {4} M {5} {6} L {7} {8}'.format(
coordX - (conf.size / 2),
coordY,
coordX + (conf.size / 2),
coordY,
coordX,
coordY - (conf.size / 2),
coordX,
coordY + (conf.size / 2)
),
stroke: conf.color,
'stroke-opacity': conf.opacity
}
});
break;
case 'square':
case 'rect':
var mark = RG.SVG.create({
svg: this.svg,
type: 'rect',
parent: group,
attr: {
x: coordX - (conf.size / 2),
y: coordY - (conf.size / 2),
width: conf.size,
height: conf.size,
fill: conf.color,
'fill-opacity': conf.opacity
}
});
break;
case 'dot':
case 'circle':
var mark = RG.SVG.create({
svg: this.svg,
type: 'circle',
parent: group,
attr: {
cx: coordX,
cy: coordY,
r: conf.size / 2,
fill: conf.color,
'fill-opacity': conf.opacity
}
});
break;
case 'cross':
default:
var mark = RG.SVG.create({
svg: this.svg,
type: 'path',
parent: group,
attr: {
d: 'M {1} {2} L {3} {4} M {5} {6} L {7} {8}'.format(
coordX - (conf.size / 2), coordY - (conf.size / 2),
coordX + (conf.size / 2), coordY + (conf.size / 2),
coordX - (conf.size / 2), coordY + (conf.size / 2),
coordX + (conf.size / 2), coordY - (conf.size / 2)
),
stroke: conf.color,
'stroke-opacity': conf.opacity
}
});
break;
}
//
// Draw the above label if it's present
//
if (typeof conf.above === 'string' || (typeof conf.above !== 'string' && conf.above) ) {
this.drawLabelsAbove({
point: conf,
coordX: coordX,
coordY: coordY
});
}
// Add some data attributes that save various values
mark.setAttribute('data-index', index);
mark.setAttribute('data-dataset', datasetIdx);
mark.setAttribute('data-original-opacity', conf.opacity);
mark.setAttribute('data-original-color', conf.color);
mark.setAttribute('data-original-coordx', coordX);
mark.setAttribute('data-original-coordy', coordY);
mark.setAttribute('data-size', conf.size);
mark.setAttribute('data-sequential', seq);
mark.setAttribute('data-type', conf.type);
return {
x: coordX,
y: coordY,
size: conf.type.substr(0,6) === 'image:' ? img.width : conf.size,
mark: mark,
type: conf.type
};
};
// Draw a bubble on a bubble chart
this.drawBubble = function (opt, conf)
{
var size = (conf.z / prop.bubbleMaxValue) * prop.bubbleMaxRadius;
var color = RG.SVG.parseColorRadial({
object: this,
color: prop.bubbleColorsSolid ? conf.color : 'Gradient(white:' + conf.color + ')',
cx: opt.coordx + (size / 4),
cy: opt.coordy - (size / 4),
fx: opt.coordx + (size / 4),
fy: opt.coordy - (size / 4),
r: size * 1.5
});
var circle = RG.SVG.create({
svg: this.svg,
type: 'circle',
attr: {
cx: opt.coordx,
cy: opt.coordy,
r: size,
fill: color,
'fill-opacity': conf.opacity
}
});
// Add some data attributes that save various values
circle.setAttribute('data-index', opt.index);
circle.setAttribute('data-dataset', opt.datasetIdx);
circle.setAttribute('data-original-opacity', conf.opacity);
circle.setAttribute('data-original-color', conf.color);
circle.setAttribute('data-original-coordx', opt.coordx);
circle.setAttribute('data-original-coordy', opt.coordy);
circle.setAttribute('data-size', size);
circle.setAttribute('data-sequential', opt.sequential);
circle.setAttribute('data-type', 'bubble');
return {
x: opt.coordx,
y: opt.coordy,
z: opt.coordz
};
};
//
// This functions draws a line if required
//
this.drawLine = function (opt)
{
var linewidth = 1,
color = 'black';
// Calculate the linewidth
if (typeof prop.lineLinewidth === 'object' && typeof prop.lineLinewidth[opt.index] === 'number') {
linewidth = prop.lineLinewidth[opt.index];
} else if (typeof prop.lineLinewidth === 'number') {
linewidth = prop.lineLinewidth;
} else {
linewidth = 1;
}
// Determine the color
if (typeof prop.lineColors === 'object' && prop.lineColors[opt.index]) {
color = prop.lineColors[opt.index];
} else if (prop.colors[opt.index] === 'string') {
color = prop.colors[opt.index];
} else {
color = 'black';
}
for (var i=0,path=''; i<this.coords2[opt.index].length; ++i) {
path += '{1} {2} {3} '.format(
i === 0 ? 'M' : 'L',
this.coords2[opt.index][i].x,
this.coords2[opt.index][i].y
);
}
RG.SVG.create({
svg: this.svg,
type: 'path',
parent: this.svg.all,
attr: {
d: path,
fill: 'transparent',
stroke: color,
'stroke-width': linewidth,
'stroke-linecap': 'round',
'stroke-linejoin': 'round'
}
});
};
/**
* This function can be used to retrieve the relevant X coordinate for a
* particular value.
*
* @param int value The value to get the X coordinate for
*/
this.getXCoord = function (value)
{
var x;
if (value > prop.xaxisMax) {
return null;
}
if (value < prop.xaxisMin) {
return null;
}
x = ((value - prop.xaxisMin) / (prop.xaxisMax - prop.xaxisMin));
x *= (this.width - prop.gutterLeft - prop.gutterRight);
x = prop.gutterLeft + x;
return x;
};
/**
* This function can be used to retrieve the relevant Y coordinate for a
* particular value.
*
* @param int value The value to get the Y coordinate for
*/
this.getYCoord = function (value)
{
var prop = this.properties;
if (value > this.scale.max) {
return null;
}
var y, xaxispos = prop.xaxispos;
if (value < this.scale.min) {
return null;
}
y = ((value - this.scale.min) / (this.scale.max - this.scale.min));
y *= (this.height - prop.gutterTop - prop.gutterBottom);
y = this.height - prop.gutterBottom - y;
return y;
};
/**
* This function can be used to highlight a bar on the chart
*
* @param object rect The rectangle to highlight
*/
this.highlight = function (rect)
{
rect.setAttribute('fill', prop.highlightFill);
// Store the highlight rect in the registry so
// it can be reset later
RG.SVG.REG.set('highlight', rect);
};
//
// Draws the labelsAbove
//
// @param opt An object that consists of various arguments to the function
//
this.drawLabelsAbove = function (opt)
{
var conf = opt.point,
coordX = opt.coordX,
coordY = opt.coordY;
// Facilitate labelsAboveSpecific
if (typeof conf.above === 'string') {
var str = conf.above;
} else {
conf.x = RG.SVG.numberFormat({
object: this,
num: conf.x.toFixed(prop.labelsAboveXDecimals ),
prepend: typeof prop.labelsAboveXUnitsPre === 'string' ? prop.labelsAboveXUnitsPre : null,
append: typeof prop.labelsAboveXUnitsPost === 'string' ? prop.labelsAboveXUnitsPost : null,
point: typeof prop.labelsAboveXPoint === 'string' ? prop.labelsAboveXPoint : null,
thousand: typeof prop.labelsAboveXThousand === 'string' ? prop.labelsAboveXThousand : null,
formatter: typeof prop.labelsAboveXFormatter === 'function' ? prop.labelsAboveXFormatter : null,
decimals_trim: prop.labelsAboveXDecimalsTrim
});
conf.y = RG.SVG.numberFormat({
object: this,
num: conf.y.toFixed(prop.labelsAboveYDecimals ),
prepend: typeof prop.labelsAboveYUnitsPre === 'string' ? prop.labelsAboveYUnitsPre : null,
append: typeof prop.labelsAboveYUnitsPost === 'string' ? prop.labelsAboveYUnitsPost : null,
point: typeof prop.labelsAboveYPoint === 'string' ? prop.labelsAboveYPoint : null,
thousand: typeof prop.labelsAboveYThousand === 'string' ? prop.labelsAboveYThousand : null,
formatter: typeof prop.labelsAboveYFormatter === 'function' ? prop.labelsAboveYFormatter : null,
decimals_trim: prop.labelsAboveYDecimalsTrim
});
var str = '{1}{2}{3}'.format(
conf.x,
prop.labelsAboveSeperator,
conf.y
);
}
// Add the text to the scene
RG.SVG.text({
object: this,
parent: this.svg.all,
tag: 'labels.above',
text: str,
x: parseFloat(coordX) + prop.labelsAboveOffsetx,
y: parseFloat(coordY) + prop.labelsAboveOffsety,
halign: prop.labelsAboveHalign,
valign: prop.labelsAboveValign,
font: prop.labelsAboveFont || prop.textFont,
size: prop.labelsAboveSize || prop.textSize,
bold: prop.labelsAboveBold || prop.textBold,
italic: prop.labelsAboveItalic || prop.textItalic,
color: prop.labelsAboveColor || prop.textColor,
background: prop.labelsAboveBackground || null,
padding: prop.labelsAboveBackgroundPadding || 0
});
};
/**
* This allows for easy specification of gradients
*/
this.parseColors = function ()
{
// TODO Loop thru the data parsing the color for gradients too
// 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;
// IMPORTANT: Bubble chart gradients are parse in the drawBubble()
// function below
if (colors && !prop.bubble) {
for (var i=0; i<colors.length; ++i) {
colors[i] = RG.SVG.parseColorLinear({
object: this,
color: colors[i]
});
}
}
prop.backgroundGridColor = RG.SVG.parseColorLinear({object: this, color: prop.backgroundGridColor});
prop.highlightFill = RG.SVG.parseColorLinear({object: this, color: prop.highlightFill});
prop.backgroundColor = RG.SVG.parseColorLinear({object: this, color: prop.backgroundColor});
};
/**
* 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.setAttribute('fill', 'transparent');
RG.SVG.REG.set('highlight', null);
}
};
//
// Draws a single errorbar
//
this.drawErrorbar = function (opt)
{
// Get the error bar value
var max = RG.SVG.getErrorbarsMaxValue({
object: this,
index: opt.sequential
});
// Get the error bar value
var min = RG.SVG.getErrorbarsMinValue({
object: this,
index: opt.sequential
});
if (!max && !min) {
return;
}
var linewidth = RG.SVG.getErrorbarsLinewidth({object: this, index: opt.sequential}),
color = RG.SVG.getErrorbarsColor({object: this, index: opt.sequential}),
capwidth = RG.SVG.getErrorbarsCapWidth({object: this, index: opt.sequential}),
halfCapWidth = capwidth / 2;
if (max > 0) {
var y1 = this.getYCoord(opt.valueY + max)
y2 = this.getYCoord(opt.valueY - min);
// Draw the UPPER vertical line
var errorbarLine = RG.SVG.create({
svg: this.svg,
type: 'line',
parent: opt.parent,
attr: {
x1: opt.x,
y1: opt.y,
x2: opt.x,
y2: y1,
stroke: color,
'stroke-width': linewidth
}
});
// Draw the cap to the UPPER line
var errorbarCap = RG.SVG.create({
svg: this.svg,
type: 'line',
parent: opt.parent,
attr: {
x1: opt.x - halfCapWidth,
y1: y1,
x2: opt.x + halfCapWidth,
y2: y1,
stroke: color,
'stroke-width': linewidth
}
});
}
// Draw the minimum errorbar if necessary
if (typeof min === 'number') {
var errorbarLine = RG.SVG.create({
svg: this.svg,
type: 'line',
parent: opt.parent,
attr: {
x1: opt.x,
y1: opt.y,
x2: opt.x,
y2: y2,
stroke: color,
'stroke-width': linewidth
}
});
// Draw the cap to the UPPER line
var errorbarCap = RG.SVG.create({
svg: this.svg,
type: 'line',
parent: opt.parent,
attr: {
x1: opt.x - halfCapWidth,
y1: y2,
x2: opt.x + halfCapWidth,
y2: y2,
stroke: color,
'stroke-width': linewidth
}
});
}
};
};
return this;
// End module pattern
})(window, document);