diff options
Diffstat (limited to 'usr/local/www/protochart/ProtoChart.js')
-rw-r--r-- | usr/local/www/protochart/ProtoChart.js | 2653 |
1 files changed, 0 insertions, 2653 deletions
diff --git a/usr/local/www/protochart/ProtoChart.js b/usr/local/www/protochart/ProtoChart.js deleted file mode 100644 index 4e60f18..0000000 --- a/usr/local/www/protochart/ProtoChart.js +++ /dev/null @@ -1,2653 +0,0 @@ -/** - * Class: ProtoChart - * Version: v0.5 beta - * - * ProtoChart is a charting lib on top of Prototype. - * This library is heavily motivated by excellent work done by: - * * Flot <http://code.google.com/p/flot/> - * * Flotr <http://solutoire.com/flotr/> - * - * Complete examples can be found at: <http://www.deensoft.com/lab/protochart> - */ - -/** - * Events: - * ProtoChart:mousemove - Fired when mouse is moved over the chart - * ProtoChart:plotclick - Fired when graph is clicked - * ProtoChart:dataclick - Fired when graph is clicked AND the click is on a data point - * ProtoChart:selected - Fired when certain region on the graph is selected - * ProtoChart:hit - Fired when mouse is moved near or over certain data point on the graph - */ - - -if(!Proto) var Proto = {}; - -Proto.Chart = Class.create({ - /** - * Function: - * {Object} elem - * {Object} data - * {Object} options - */ - initialize: function(elem, data, options) - { - options = options || {}; - this.graphData = []; - /** - * Property: options - * - * Description: Various options can be set. More details in description. - * - * colors: - * {Array} - pass in a array which contains strings of colors you want to use. Default has 6 color set. - * - * legend: - * {BOOL} - show - if you want to show the legend. Default is false - * {integer} - noColumns - Number of columns for the legend. Default is 1 - * {function} - labelFormatter - A function that returns a string. The function is called with a string and is expected to return a string. Default = null - * {string} - labelBoxBorderColor - border color for the little label boxes. Default #CCC - * {HTMLElem} - container - an HTML id or HTML element where the legend should be rendered. If left null means to put the legend on top of the Chart - * {string} - position - position for the legend on the Chart. Default value 'ne' - * {integer} - margin - default valud of 5 - * {string} - backgroundColor - default to null (which means auto-detect) - * {float} - backgroundOpacity - leave it 0 to avoid background - * - * xaxis (yaxis) options: - * {string} - mode - default is null but you can pass a string "time" to indicate time series - * {integer} - min - * {integer} - max - * {float} - autoscaleMargin - in % to add if auto-setting min/max - * {mixed} - ticks - either [1, 3] or [[1, "a"], 3] or a function which gets axis info and returns ticks - * {function} - tickFormatter - A function that returns a string as a tick label. Default is null - * {float} - tickDecimals - * {integer} - tickSize - * {integer} - minTickSize - * {array} - monthNames - * {string} - timeformat - * - * Points / Lines / Bars options: - * {bool} - show, default is false - * {integer} - radius: default is 3 - * {integer} - lineWidth : default is 2 - * {bool} - fill : default is true - * {string} - fillColor: default is #ffffff - * - * Grid options: - * {string} - color - * {string} - backgroundColor - defualt is *null* - * {string} - tickColor - default is *#dddddd* - * {integer} - labelMargin - should be in pixels default is 3 - * {integer} - borderWidth - default *1* - * {bool} - clickable - default *null* - pass in TRUE if you wish to monitor click events - * {mixed} - coloredAreas - default *null* - pass in mixed object eg. {x1, x2} - * {string} - coloredAreasColor - default *#f4f4f4* - * {bool} - drawXAxis - default *true* - * {bool} - drawYAxis - default *true* - * - * selection options: - * {string} - mode : either "x", "y" or "xy" - * {string} - color : string - */ - this.options = this.merge(options,{ - colors: ["#edc240", "#00A8F0", "#C0D800", "#cb4b4b", "#4da74d", "#9440ed"], - legend: { - show: false, - noColumns: 1, - labelFormatter: null, - labelBoxBorderColor: "#ccc", - container: null, - position: "ne", - margin: 5, - backgroundColor: null, - backgroundOpacity: 0.85 - }, - xaxis: { - mode: null, - min: null, - max: null, - autoscaleMargin: null, - ticks: null, - tickFormatter: null, - tickDecimals: null, - tickSize: null, - minTickSize: null, - monthNames: null, - timeformat: null - }, - yaxis: { - mode: null, - min: null, - max: null, - ticks: null, - tickFormatter: null, - tickDecimals: null, - tickSize: null, - minTickSize: null, - monthNames: null, - timeformat: null, - autoscaleMargin: 0.02 - }, - - points: { - show: false, - radius: 3, - lineWidth: 2, - fill: true, - fillColor: "#ffffff" - }, - lines: { - show: false, - lineWidth: 2, - fill: false, - fillColor: null - }, - bars: { - show: false, - lineWidth: 2, - barWidth: 1, - fill: true, - fillColor: null, - showShadow: false, - fillOpacity: 0.4, - autoScale: true - }, - pies: { - show: false, - radius: 50, - borderWidth: 1, - fill: true, - fillColor: null, - fillOpacity: 0.90, - labelWidth: 30, - fontSize: 11, - autoScale: true - }, - grid: { - color: "#545454", - backgroundColor: null, - tickColor: "#dddddd", - labelMargin: 3, - borderWidth: 1, - clickable: null, - coloredAreas: null, - coloredAreasColor: "#f4f4f4", - drawXAxis: true, - drawYAxis: true - }, - mouse: { - track: false, - position: 'se', - fixedPosition: true, - clsName: 'mouseValHolder', - trackFormatter: this.defaultTrackFormatter, - margin: 3, - color: '#ff3f19', - trackDecimals: 1, - sensibility: 2, - radius: 5, - lineColor: '#cb4b4b' - }, - selection: { - mode: null, - color: "#97CBFF" - }, - allowDataClick: true, - makeRandomColor: false, - shadowSize: 4 - }); - - /* - * Local variables. - */ - this.canvas = null; - this.overlay = null; - this.eventHolder = null; - this.context = null; - this.overlayContext = null; - - this.domObj = $(elem); - - this.xaxis = {}; - this.yaxis = {}; - this.chartOffset = {left: 0, right: 0, top: 0, bottom: 0}; - this.yLabelMaxWidth = 0; - this.yLabelMaxHeight = 0; - this.xLabelBoxWidth = 0; - this.canvasWidth = 0; - this.canvasHeight = 0; - this.chartWidth = 0; - this.chartHeight = 0; - this.hozScale = 0; - this.vertScale = 0; - this.workarounds = {}; - - this.domObj = $(elem); - - this.barDataRange = []; - - this.lastMousePos = { pageX: null, pageY: null }; - this.selection = { first: { x: -1, y: -1}, second: { x: -1, y: -1} }; - this.prevSelection = null; - this.selectionInterval = null; - this.ignoreClick = false; - this.prevHit = null; - - if(this.options.makeRandomColor) - this.options.color = this.makeRandomColor(this.options.colors); - - this.setData(data); - this.constructCanvas(); - this.setupGrid(); - this.draw(); - }, - /** - * Private function internally used. - */ - merge: function(src, dest) - { - var result = dest || {}; - for(var i in src){ - result[i] = (typeof(src[i]) == 'object' && !(src[i].constructor == Array || src[i].constructor == RegExp)) ? this.merge(src[i], dest[i]) : result[i] = src[i]; - } - return result; - }, - /** - * Function: setData - * {Object} data - * - * Description: - * Sets datasoruces properly then sets the Bar Width accordingly, then copies the default data options and then processes the graph data - * - * Returns: none - * - */ - setData: function(data) - { - this.graphData = this.parseData(data); - this.setBarWidth(); - this.copyGraphDataOptions(); - this.processGraphData(); - }, - /** - * Function: parseData - * {Object} data - * - * Return: - * {Object} result - * - * Description: - * Takes the provided data object and converts it into generic data that we can understand. User can pass in data in 3 different ways: - * - [d1, d2] - * - [{data: d1, label: "data1"}, {data: d2, label: "data2"}] - * - [d1, {data: d1, label: "data1"}] - * - * This function parses these senarios and makes it readable - */ - parseData: function(data) - { - var res = []; - data.each(function(d){ - var s; - if(d.data) { - s = {}; - for(var v in d) { - s[v] = d[v]; - } - } - else { - s = {data: d}; - } - res.push(s); - }.bind(this)); - return res; - }, - /** - * function: makeRandomColor - * {Object} colorSet - * - * Return: - * {Array} result - array containing random colors - */ - makeRandomColor: function(colorSet) - { - var randNum = Math.floor(Math.random() * colorSet.length); - var randArr = []; - var newArr = []; - randArr.push(randNum); - - while(randArr.length < colorSet.length) - { - var tempNum = Math.floor(Math.random() * colorSet.length); - - while(checkExisted(tempNum, randArr)) - tempNum = Math.floor(Math.random() * colorSet.length); - - randArr.push(tempNum); - } - - randArr.each(function(ra){ - newArr.push(colorSet[ra]); - - }.bind(this)); - return newArr; - }, - /** - * function: checkExisted - * {Object} needle - * {Object} haystack - * - * return: - * {bool} existed - true if it finds needle in the haystack - */ - checkExisted: function(needle, haystack) - { - var existed = false; - haystack.each(function(aNeedle){ - if(aNeedle == needle) { - existed = true; - throw $break; - } - }.bind(this)); - return existed; - }, - /** - * function: setBarWidth - * - * Description: sets the bar width for Bar Graph, you should enable *autoScale* property for bar graph - */ - setBarWidth: function() - { - if(this.options.bars.show && this.options.bars.autoScale) - { - this.options.bars.barWidth = 1 / this.graphData.length / 1.2; - } - }, - /** - * Function: copyGraphDataOptions - * - * Description: Private function that goes through each graph data (series) and assigned the graph - * properties to it. - */ - copyGraphDataOptions: function() - { - var i, neededColors = this.graphData.length, usedColors = [], assignedColors = []; - - this.graphData.each(function(gd){ - var sc = gd.color; - if(sc) { - --neededColors; - if(Object.isNumber(sc)) { - assignedColors.push(sc); - } - else { - usedColors.push(this.parseColor(sc)); - } - } - }.bind(this)); - - - assignedColors.each(function(ac){ - neededColors = Math.max(neededColors, ac + 1); - }); - - var colors = []; - var variation = 0; - i = 0; - while (colors.length < neededColors) { - var c; - if (this.options.colors.length == i) { - c = new Proto.Color(100, 100, 100); - } - else { - c = this.parseColor(this.options.colors[i]); - } - - var sign = variation % 2 == 1 ? -1 : 1; - var factor = 1 + sign * Math.ceil(variation / 2) * 0.2; - c.scale(factor, factor, factor); - - colors.push(c); - - ++i; - if (i >= this.options.colors.length) { - i = 0; - ++variation; - } - } - - var colorIndex = 0, s; - - this.graphData.each(function(gd){ - if(gd.color == null) - { - gd.color = colors[colorIndex].toString(); - ++colorIndex; - } - else if(Object.isNumber(gd.color)) { - gd.color = colors[gd.color].toString(); - } - - gd.lines = Object.extend(Object.clone(this.options.lines), gd.lines); - gd.points = Object.extend(Object.clone(this.options.points), gd.points); - gd.bars = Object.extend(Object.clone(this.options.bars), gd.bars); - gd.mouse = Object.extend(Object.clone(this.options.mouse), gd.mouse); - if (gd.shadowSize == null) { - gd.shadowSize = this.options.shadowSize; - } - }.bind(this)); - - }, - /** - * Function: processGraphData - * - * Description: processes graph data, setup xaxis and yaxis min and max points. - */ - processGraphData: function() { - - this.xaxis.datamin = this.yaxis.datamin = Number.MAX_VALUE; - this.xaxis.datamax = this.yaxis.datamax = Number.MIN_VALUE; - - this.graphData.each(function(gd) { - var data = gd.data; - data.each(function(d){ - if(d == null) { - return; - } - - var x = d[0], y = d[1]; - if(!x || !y || isNaN(x = +x) || isNaN(y = +y)) { - d = null; - return; - } - - if (x < this.xaxis.datamin) - this.xaxis.datamin = x; - if (x > this.xaxis.datamax) - this.xaxis.datamax = x; - if (y < this.yaxis.datamin) - this.yaxis.datamin = y; - if (y > this.yaxis.datamax) - this.yaxis.datamax = y; - }.bind(this)); - }.bind(this)); - - - if (this.xaxis.datamin == Number.MAX_VALUE) - this.xaxis.datamin = 0; - if (this.yaxis.datamin == Number.MAX_VALUE) - this.yaxis.datamin = 0; - if (this.xaxis.datamax == Number.MIN_VALUE) - this.xaxis.datamax = 1; - if (this.yaxis.datamax == Number.MIN_VALUE) - this.yaxis.datamax = 1; - }, - /** - * Function: constructCanvas - * - * Description: constructs the main canvas for drawing. It replicates the HTML elem (usually DIV) passed - * in via constructor. If there is no height/width assigned to the HTML elem then we take a default size - * of 400px (width) and 300px (height) - */ - constructCanvas: function() { - - this.canvasWidth = this.domObj.getWidth(); - this.canvasHeight = this.domObj.getHeight(); - this.domObj.update(""); // clear target - this.domObj.setStyle({ - "position": "relative" - }); - - if (this.canvasWidth <= 0) { - this.canvasWdith = 400; - } - if(this.canvasHeight <= 0) { - this.canvasHeight = 300; - } - - this.canvas = (Prototype.Browser.IE) ? document.createElement("canvas") : new Element("CANVAS", {'width': this.canvasWidth, 'height': this.canvasHeight}); - Element.extend(this.canvas); - this.canvas.style.width = this.canvasWidth + "px"; - this.canvas.style.height = this.canvasHeight + "px"; - - this.domObj.appendChild(this.canvas); - - if (Prototype.Browser.IE) // excanvas hack - { - this.canvas = $(window.G_vmlCanvasManager.initElement(this.canvas)); - } - this.canvas = $(this.canvas); - - this.context = this.canvas.getContext("2d"); - - this.overlay = (Prototype.Browser.IE) ? document.createElement("canvas") : new Element("CANVAS", {'width': this.canvasWidth, 'height': this.canvasHeight}); - Element.extend(this.overlay); - this.overlay.style.width = this.canvasWidth + "px"; - this.overlay.style.height = this.canvasHeight + "px"; - this.overlay.style.position = "absolute"; - this.overlay.style.left = "0px"; - this.overlay.style.right = "0px"; - - this.overlay.setStyle({ - 'position': 'absolute', - 'left': '0px', - 'right': '0px' - }); - this.domObj.appendChild(this.overlay); - - if (Prototype.Browser.IE) { - this.overlay = $(window.G_vmlCanvasManager.initElement(this.overlay)); - } - - this.overlay = $(this.overlay); - this.overlayContext = this.overlay.getContext("2d"); - - if(this.options.selection.mode) - { - this.overlay.observe('mousedown', this.onMouseDown.bind(this)); - this.overlay.observe('mousemove', this.onMouseMove.bind(this)); - } - if(this.options.grid.clickable) { - this.overlay.observe('click', this.onClick.bind(this)); - } - if(this.options.mouse.track) - { - this.overlay.observe('mousemove', this.onMouseMove.bind(this)); - } - }, - /** - * function: setupGrid - * - * Description: a container function that does a few interesting things. - * - * 1. calls <extendXRangeIfNeededByBar> function which makes sure that our axis are expanded if needed - * - * 2. calls <setRange> function providing xaxis options which fixes the ranges according to data points - * - * 3. calls <prepareTickGeneration> function for xaxis which generates ticks according to options provided by user - * - * 4. calls <setTicks> function for xaxis that sets the ticks - * - * similar sequence is called for y-axis. - * - * At the end if this is a pie chart than we insert Labels (around the pie chart) via <insertLabels> and we also call <insertLegend> - */ - setupGrid: function() - { - if(this.options.bars.show) - { - this.xaxis.max += 0.5; - this.xaxis.min -= 0.5; - } - //x-axis - this.extendXRangeIfNeededByBar(); - this.setRange(this.xaxis, this.options.xaxis); - this.prepareTickGeneration(this.xaxis, this.options.xaxis); - this.setTicks(this.xaxis, this.options.xaxis); - - - //y-axis - this.setRange(this.yaxis, this.options.yaxis); - this.prepareTickGeneration(this.yaxis, this.options.yaxis); - this.setTicks(this.yaxis, this.options.yaxis); - this.setSpacing(); - - if(!this.options.pies.show) - { - this.insertLabels(); - } - this.insertLegend(); - }, - /** - * function: setRange - * - * parameters: - * {Object} axis - * {Object} axisOptions - */ - setRange: function(axis, axisOptions) { - var min = axisOptions.min != null ? axisOptions.min : axis.datamin; - var max = axisOptions.max != null ? axisOptions.max : axis.datamax; - - if (max - min == 0.0) { - // degenerate case - var widen; - if (max == 0.0) - widen = 1.0; - else - widen = 0.01; - - min -= widen; - max += widen; - } - else { - // consider autoscaling - var margin = axisOptions.autoscaleMargin; - if (margin != null) { - if (axisOptions.min == null) { - min -= (max - min) * margin; - // make sure we don't go below zero if all values - // are positive - if (min < 0 && axis.datamin >= 0) - min = 0; - } - if (axisOptions.max == null) { - max += (max - min) * margin; - if (max > 0 && axis.datamax <= 0) - max = 0; - } - } - } - axis.min = min; - axis.max = max; - }, - /** - * function: prepareTickGeneration - * - * Parameters: - * {Object} axis - * {Object} axisOptions - */ - prepareTickGeneration: function(axis, axisOptions) { - // estimate number of ticks - var noTicks; - if (Object.isNumber(axisOptions.ticks) && axisOptions.ticks > 0) - noTicks = axisOptions.ticks; - else if (axis == this.xaxis) - noTicks = this.canvasWidth / 100; - else - noTicks = this.canvasHeight / 60; - - var delta = (axis.max - axis.min) / noTicks; - var size, generator, unit, formatter, i, magn, norm; - - if (axisOptions.mode == "time") { - function formatDate(d, fmt, monthNames) { - var leftPad = function(n) { - n = "" + n; - return n.length == 1 ? "0" + n : n; - }; - - var r = []; - var escape = false; - if (monthNames == null) - monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; - for (var i = 0; i < fmt.length; ++i) { - var c = fmt.charAt(i); - - if (escape) { - switch (c) { - case 'h': c = "" + d.getHours(); break; - case 'H': c = leftPad(d.getHours()); break; - case 'M': c = leftPad(d.getMinutes()); break; - case 'S': c = leftPad(d.getSeconds()); break; - case 'd': c = "" + d.getDate(); break; - case 'm': c = "" + (d.getMonth() + 1); break; - case 'y': c = "" + d.getFullYear(); break; - case 'b': c = "" + monthNames[d.getMonth()]; break; - } - r.push(c); - escape = false; - } - else { - if (c == "%") - escape = true; - else - r.push(c); - } - } - return r.join(""); - } - - - // map of app. size of time units in milliseconds - var timeUnitSize = { - "second": 1000, - "minute": 60 * 1000, - "hour": 60 * 60 * 1000, - "day": 24 * 60 * 60 * 1000, - "month": 30 * 24 * 60 * 60 * 1000, - "year": 365.2425 * 24 * 60 * 60 * 1000 - }; - - - // the allowed tick sizes, after 1 year we use - // an integer algorithm - var spec = [ - [1, "second"], [2, "second"], [5, "second"], [10, "second"], - [30, "second"], - [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], - [30, "minute"], - [1, "hour"], [2, "hour"], [4, "hour"], - [8, "hour"], [12, "hour"], - [1, "day"], [2, "day"], [3, "day"], - [0.25, "month"], [0.5, "month"], [1, "month"], - [2, "month"], [3, "month"], [6, "month"], - [1, "year"] - ]; - - var minSize = 0; - if (axisOptions.minTickSize != null) { - if (typeof axisOptions.tickSize == "number") - minSize = axisOptions.tickSize; - else - minSize = axisOptions.minTickSize[0] * timeUnitSize[axisOptions.minTickSize[1]]; - } - - for (i = 0; i < spec.length - 1; ++i) { - if (delta < (spec[i][0] * timeUnitSize[spec[i][1]] + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2 && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) { - break; - } - } - - size = spec[i][0]; - unit = spec[i][1]; - - // special-case the possibility of several years - if (unit == "year") { - magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10)); - norm = (delta / timeUnitSize.year) / magn; - if (norm < 1.5) - size = 1; - else if (norm < 3) - size = 2; - else if (norm < 7.5) - size = 5; - else - size = 10; - - size *= magn; - } - - if (axisOptions.tickSize) { - size = axisOptions.tickSize[0]; - unit = axisOptions.tickSize[1]; - } - - var floorInBase = this.floorInBase; //gives us a reference to a global function.. - - generator = function(axis) { - var ticks = [], - tickSize = axis.tickSize[0], unit = axis.tickSize[1], - d = new Date(axis.min); - - var step = tickSize * timeUnitSize[unit]; - - - - if (unit == "second") - d.setSeconds(floorInBase(d.getSeconds(), tickSize)); - if (unit == "minute") - d.setMinutes(floorInBase(d.getMinutes(), tickSize)); - if (unit == "hour") - d.setHours(floorInBase(d.getHours(), tickSize)); - if (unit == "month") - d.setMonth(floorInBase(d.getMonth(), tickSize)); - if (unit == "year") - d.setFullYear(floorInBase(d.getFullYear(), tickSize)); - - // reset smaller components - d.setMilliseconds(0); - if (step >= timeUnitSize.minute) - d.setSeconds(0); - if (step >= timeUnitSize.hour) - d.setMinutes(0); - if (step >= timeUnitSize.day) - d.setHours(0); - if (step >= timeUnitSize.day * 4) - d.setDate(1); - if (step >= timeUnitSize.year) - d.setMonth(0); - - - var carry = 0, v; - do { - v = d.getTime(); - ticks.push({ v: v, label: axis.tickFormatter(v, axis) }); - if (unit == "month") { - if (tickSize < 1) { - d.setDate(1); - var start = d.getTime(); - d.setMonth(d.getMonth() + 1); - var end = d.getTime(); - d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); - carry = d.getHours(); - d.setHours(0); - } - else - d.setMonth(d.getMonth() + tickSize); - } - else if (unit == "year") { - d.setFullYear(d.getFullYear() + tickSize); - } - else - d.setTime(v + step); - } while (v < axis.max); - - return ticks; - }; - - formatter = function (v, axis) { - var d = new Date(v); - - // first check global format - if (axisOptions.timeformat != null) - return formatDate(d, axisOptions.timeformat, axisOptions.monthNames); - - var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]]; - var span = axis.max - axis.min; - - if (t < timeUnitSize.minute) - fmt = "%h:%M:%S"; - else if (t < timeUnitSize.day) { - if (span < 2 * timeUnitSize.day) - fmt = "%h:%M"; - else - fmt = "%b %d %h:%M"; - } - else if (t < timeUnitSize.month) - fmt = "%b %d"; - else if (t < timeUnitSize.year) { - if (span < timeUnitSize.year) - fmt = "%b"; - else - fmt = "%b %y"; - } - else - fmt = "%y"; - - return formatDate(d, fmt, axisOptions.monthNames); - }; - } - else { - // pretty rounding of base-10 numbers - var maxDec = axisOptions.tickDecimals; - var dec = -Math.floor(Math.log(delta) / Math.LN10); - if (maxDec != null && dec > maxDec) - dec = maxDec; - - magn = Math.pow(10, -dec); - norm = delta / magn; // norm is between 1.0 and 10.0 - - if (norm < 1.5) - size = 1; - else if (norm < 3) { - size = 2; - // special case for 2.5, requires an extra decimal - if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { - size = 2.5; - ++dec; - } - } - else if (norm < 7.5) - size = 5; - else - size = 10; - - size *= magn; - - if (axisOptions.minTickSize != null && size < axisOptions.minTickSize) - size = axisOptions.minTickSize; - - if (axisOptions.tickSize != null) - size = axisOptions.tickSize; - - axis.tickDecimals = Math.max(0, (maxDec != null) ? maxDec : dec); - - var floorInBase = this.floorInBase; - - generator = function (axis) { - var ticks = []; - var start = floorInBase(axis.min, axis.tickSize); - // then spew out all possible ticks - var i = 0, v; - do { - v = start + i * axis.tickSize; - ticks.push({ v: v, label: axis.tickFormatter(v, axis) }); - ++i; - } while (v < axis.max); - return ticks; - }; - - formatter = function (v, axis) { - if(v) { - return v.toFixed(axis.tickDecimals); - } - return 0; - }; - } - - axis.tickSize = unit ? [size, unit] : size; - axis.tickGenerator = generator; - if (Object.isFunction(axisOptions.tickFormatter)) - axis.tickFormatter = function (v, axis) { return "" + axisOptions.tickFormatter(v, axis); }; - else - axis.tickFormatter = formatter; - }, - /** - * function: extendXRangeIfNeededByBar - */ - extendXRangeIfNeededByBar: function() { - - if (this.options.xaxis.max == null) { - // great, we're autoscaling, check if we might need a bump - var newmax = this.xaxis.max; - this.graphData.each(function(gd){ - if(gd.bars.show && gd.bars.barWidth + this.xaxis.datamax > newmax) - { - newmax = this.xaxis.datamax + gd.bars.barWidth; - } - }.bind(this)); - this.xaxis.nax = newmax; - - } - }, - /** - * function: setTicks - * - * parameters: - * {Object} axis - * {Object} axisOptions - */ - setTicks: function(axis, axisOptions) { - axis.ticks = []; - - if (axisOptions.ticks == null) - axis.ticks = axis.tickGenerator(axis); - else if (typeof axisOptions.ticks == "number") { - if (axisOptions.ticks > 0) - axis.ticks = axis.tickGenerator(axis); - } - else if (axisOptions.ticks) { - var ticks = axisOptions.ticks; - - if (Object.isFunction(ticks)) - // generate the ticks - ticks = ticks({ min: axis.min, max: axis.max }); - - // clean up the user-supplied ticks, copy them over - //var i, v; - ticks.each(function(t, i){ - var v = null; - var label = null; - if(typeof t == 'object') { - v = t[0]; - if(t.length > 1) { label = t[1]; } - } - else { - v = t; - } - if(!label) { - label = axis.tickFormatter(v, axis); - } - axis.ticks[i] = {v: v, label: label} - }.bind(this)); - - } - - if (axisOptions.autoscaleMargin != null && axis.ticks.length > 0) { - if (axisOptions.min == null) - axis.min = Math.min(axis.min, axis.ticks[0].v); - if (axisOptions.max == null && axis.ticks.length > 1) - axis.max = Math.min(axis.max, axis.ticks[axis.ticks.length - 1].v); - } - }, - /** - * Function: setSpacing - * - * Parameters: none - */ - setSpacing: function() { - // calculate y label dimensions - var i, labels = [], l; - for (i = 0; i < this.yaxis.ticks.length; ++i) { - l = this.yaxis.ticks[i].label; - - if (l) - labels.push('<div class="tickLabel">' + l + '</div>'); - } - - if (labels.length > 0) { - var dummyDiv = new Element('div', {'style': 'position:absolute;top:-10000px;font-size:smaller'}); - dummyDiv.update(labels.join("")); - this.domObj.insert(dummyDiv); - this.yLabelMaxWidth = dummyDiv.getWidth(); - this.yLabelMaxHeight = dummyDiv.select('div')[0].getHeight(); - dummyDiv.remove(); - } - - var maxOutset = this.options.grid.borderWidth; - if (this.options.points.show) - maxOutset = Math.max(maxOutset, this.options.points.radius + this.options.points.lineWidth/2); - for (i = 0; i < this.graphData.length; ++i) { - if (this.graphData[i].points.show) - maxOutset = Math.max(maxOutset, this.graphData[i].points.radius + this.graphData[i].points.lineWidth/2); - } - - this.chartOffset.left = this.chartOffset.right = this.chartOffset.top = this.chartOffset.bottom = maxOutset; - - this.chartOffset.left += this.yLabelMaxWidth + this.options.grid.labelMargin; - this.chartWidth = this.canvasWidth - this.chartOffset.left - this.chartOffset.right; - - this.xLabelBoxWidth = this.chartWidth / 6; - labels = []; - - for (i = 0; i < this.xaxis.ticks.length; ++i) { - l = this.xaxis.ticks[i].label; - if (l) { - labels.push('<span class="tickLabel" width="' + this.xLabelBoxWidth + '">' + l + '</span>'); - } - } - - var xLabelMaxHeight = 0; - if (labels.length > 0) { - var dummyDiv = new Element('div', {'style': 'position:absolute;top:-10000px;font-size:smaller'}); - dummyDiv.update(labels.join("")); - this.domObj.appendChild(dummyDiv); - xLabelMaxHeight = dummyDiv.getHeight(); - dummyDiv.remove(); - } - - this.chartOffset.bottom += xLabelMaxHeight + this.options.grid.labelMargin; - this.chartHeight = this.canvasHeight - this.chartOffset.bottom - this.chartOffset.top; - this.hozScale = this.chartWidth / (this.xaxis.max - this.xaxis.min); - this.vertScale = this.chartHeight / (this.yaxis.max - this.yaxis.min); - }, - /** - * function: draw - */ - draw: function() { - if(this.options.bars.show) - { - this.extendXRangeIfNeededByBar(); - this.setSpacing(); - this.drawGrid(); - this.drawBarGraph(this.graphData, this.barDataRange); - } - else if(this.options.pies.show) - { - this.preparePieData(this.graphData); - this.drawPieGraph(this.graphData); - } - else - { - this.drawGrid(); - for (var i = 0; i < this.graphData.length; i++) { - this.drawGraph(this.graphData[i]); - } - } - }, - /** - * function: translateHoz - * - * Paramters: - * {Object} x - * - * Description: Given a value this function translate it to relative x coord on canvas - */ - translateHoz: function(x) { - return (x - this.xaxis.min) * this.hozScale; - }, - /** - * function: translateVert - * - * parameters: - * {Object} y - * - * Description: Given a value this function translate it to relative y coord on canvas - */ - translateVert: function(y) { - return this.chartHeight - (y - this.yaxis.min) * this.vertScale; - }, - /** - * function: drawGrid - * - * parameters: none - * - * description: draws the actual grid on the canvas - */ - drawGrid: function() { - var i; - - this.context.save(); - this.context.clearRect(0, 0, this.canvasWidth, this.canvasHeight); - this.context.translate(this.chartOffset.left, this.chartOffset.top); - - // draw background, if any - if (this.options.grid.backgroundColor != null) { - this.context.fillStyle = this.options.grid.backgroundColor; - this.context.fillRect(0, 0, this.chartWidth, this.chartHeight); - } - - // draw colored areas - if (this.options.grid.coloredAreas) { - var areas = this.options.grid.coloredAreas; - if (Object.isFunction(areas)) { - areas = areas({ xmin: this.xaxis.min, xmax: this.xaxis.max, ymin: this.yaxis.min, ymax: this.yaxis.max }); - } - - areas.each(function(a){ - // clip - if (a.x1 == null || a.x1 < this.xaxis.min) - a.x1 = this.xaxis.min; - if (a.x2 == null || a.x2 > this.xaxis.max) - a.x2 = this.xaxis.max; - if (a.y1 == null || a.y1 < this.yaxis.min) - a.y1 = this.yaxis.min; - if (a.y2 == null || a.y2 > this.yaxis.max) - a.y2 = this.yaxis.max; - - var tmp; - if (a.x1 > a.x2) { - tmp = a.x1; - a.x1 = a.x2; - a.x2 = tmp; - } - if (a.y1 > a.y2) { - tmp = a.y1; - a.y1 = a.y2; - a.y2 = tmp; - } - - if (a.x1 >= this.xaxis.max || a.x2 <= this.xaxis.min || a.x1 == a.x2 - || a.y1 >= this.yaxis.max || a.y2 <= this.yaxis.min || a.y1 == a.y2) - return; - - this.context.fillStyle = a.color || this.options.grid.coloredAreasColor; - this.context.fillRect(Math.floor(this.translateHoz(a.x1)), Math.floor(this.translateVert(a.y2)), - Math.floor(this.translateHoz(a.x2) - this.translateHoz(a.x1)), Math.floor(this.translateVert(a.y1) - this.translateVert(a.y2))); - }.bind(this)); - - - } - - // draw the inner grid - this.context.lineWidth = 1; - this.context.strokeStyle = this.options.grid.tickColor; - this.context.beginPath(); - var v; - if (this.options.grid.drawXAxis) { - this.xaxis.ticks.each(function(aTick){ - v = aTick.v; - if(v <= this.xaxis.min || v >= this.xaxis.max) { - return; - } - this.context.moveTo(Math.floor(this.translateHoz(v)) + this.context.lineWidth / 2, 0); - this.context.lineTo(Math.floor(this.translateHoz(v)) + this.context.lineWidth / 2, this.chartHeight); - }.bind(this)); - - } - - if (this.options.grid.drawYAxis) { - this.yaxis.ticks.each(function(aTick){ - v = aTick.v; - if(v <= this.yaxis.min || v >= this.yaxis.max) { - return; - } - this.context.moveTo(0, Math.floor(this.translateVert(v)) + this.context.lineWidth / 2); - this.context.lineTo(this.chartWidth, Math.floor(this.translateVert(v)) + this.context.lineWidth / 2); - }.bind(this)); - - } - this.context.stroke(); - - if (this.options.grid.borderWidth) { - // draw border - this.context.lineWidth = this.options.grid.borderWidth; - this.context.strokeStyle = this.options.grid.color; - this.context.lineJoin = "round"; - this.context.strokeRect(0, 0, this.chartWidth, this.chartHeight); - this.context.restore(); - } - }, - /** - * function: insertLabels - * - * parameters: none - * - * description: inserts the label with proper spacing. Both on X and Y axis - */ - insertLabels: function() { - this.domObj.select(".tickLabels").invoke('remove'); - - var i, tick; - var html = '<div class="tickLabels" style="font-size:smaller;color:' + this.options.grid.color + '">'; - - // do the x-axis - this.xaxis.ticks.each(function(tick){ - if (!tick.label || tick.v < this.xaxis.min || tick.v > this.xaxis.max) - return; - html += '<div style="position:absolute;top:' + (this.chartOffset.top + this.chartHeight + this.options.grid.labelMargin) + 'px;left:' + (this.chartOffset.left + this.translateHoz(tick.v) - this.xLabelBoxWidth/2) + 'px;width:' + this.xLabelBoxWidth + 'px;text-align:center" class="tickLabel">' + tick.label + "</div>"; - - }.bind(this)); - - // do the y-axis - this.yaxis.ticks.each(function(tick){ - if (!tick.label || tick.v < this.yaxis.min || tick.v > this.yaxis.max) - return; - html += '<div id="ylabels" style="position:absolute;top:' + (this.chartOffset.top + this.translateVert(tick.v) - this.yLabelMaxHeight/2) + 'px;left:0;width:' + this.yLabelMaxWidth + 'px;text-align:right" class="tickLabel">' + tick.label + "</div>"; - }.bind(this)); - - html += '</div>'; - - this.domObj.insert(html); - }, - /** - * function: drawGraph - * - * Paramters: - * {Object} graphData - * - * Description: given a graphData (series) this function calls a proper lower level method to draw it. - */ - drawGraph: function(graphData) { - if (graphData.lines.show || (!graphData.bars.show && !graphData.points.show)) - this.drawGraphLines(graphData); - if (graphData.bars.show) - this.drawGraphBar(graphData); - if (graphData.points.show) - this.drawGraphPoints(graphData); - }, - /** - * function: plotLine - * - * parameters: - * {Object} data - * {Object} offset - * - * description: - * Helper function that plots a line based on the data provided - */ - plotLine: function(data, offset) { - var prev, cur = null, drawx = null, drawy = null; - - this.context.beginPath(); - for (var i = 0; i < data.length; ++i) { - prev = cur; - cur = data[i]; - - if (prev == null || cur == null) - continue; - - var x1 = prev[0], y1 = prev[1], - x2 = cur[0], y2 = cur[1]; - - // clip with ymin - if (y1 <= y2 && y1 < this.yaxis.min) { - if (y2 < this.yaxis.min) - continue; // line segment is outside - // compute new intersection point - x1 = (this.yaxis.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = this.yaxis.min; - } - else if (y2 <= y1 && y2 < this.yaxis.min) { - if (y1 < this.yaxis.min) - continue; - x2 = (this.yaxis.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = this.yaxis.min; - } - - // clip with ymax - if (y1 >= y2 && y1 > this.yaxis.max) { - if (y2 > this.yaxis.max) - continue; - x1 = (this.yaxis.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = this.yaxis.max; - } - else if (y2 >= y1 && y2 > this.yaxis.max) { - if (y1 > this.yaxis.max) - continue; - x2 = (this.yaxis.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = this.yaxis.max; - } - - // clip with xmin - if (x1 <= x2 && x1 < this.xaxis.min) { - if (x2 < this.xaxis.min) - continue; - y1 = (this.xaxis.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = this.xaxis.min; - } - else if (x2 <= x1 && x2 < this.xaxis.min) { - if (x1 < this.xaxis.min) - continue; - y2 = (this.xaxis.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = this.xaxis.min; - } - - // clip with xmax - if (x1 >= x2 && x1 > this.xaxis.max) { - if (x2 > this.xaxis.max) - continue; - y1 = (this.xaxis.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = this.xaxis.max; - } - else if (x2 >= x1 && x2 > this.xaxis.max) { - if (x1 > this.xaxis.max) - continue; - y2 = (this.xaxis.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = this.xaxis.max; - } - - if (drawx != this.translateHoz(x1) || drawy != this.translateVert(y1) + offset) - this.context.moveTo(this.translateHoz(x1), this.translateVert(y1) + offset); - - drawx = this.translateHoz(x2); - drawy = this.translateVert(y2) + offset; - this.context.lineTo(drawx, drawy); - } - this.context.stroke(); - }, - /** - * function: plotLineArea - * - * parameters: - * {Object} data - * - * description: - * Helper functoin that plots a colored line graph. This function - * takes the data nad then fill in the area on the graph properly - */ - plotLineArea: function(data) { - var prev, cur = null; - - var bottom = Math.min(Math.max(0, this.yaxis.min), this.yaxis.max); - var top, lastX = 0; - - var areaOpen = false; - - for (var i = 0; i < data.length; ++i) { - prev = cur; - cur = data[i]; - - if (areaOpen && prev != null && cur == null) { - // close area - this.context.lineTo(this.translateHoz(lastX), this.translateVert(bottom)); - this.context.fill(); - areaOpen = false; - continue; - } - - if (prev == null || cur == null) - continue; - - var x1 = prev[0], y1 = prev[1], - x2 = cur[0], y2 = cur[1]; - - // clip x values - - // clip with xmin - if (x1 <= x2 && x1 < this.xaxis.min) { - if (x2 < this.xaxis.min) - continue; - y1 = (this.xaxis.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = this.xaxis.min; - } - else if (x2 <= x1 && x2 < this.xaxis.min) { - if (x1 < this.xaxis.min) - continue; - y2 = (this.xaxis.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = this.xaxis.min; - } - - // clip with xmax - if (x1 >= x2 && x1 > this.xaxis.max) { - if (x2 > this.xaxis.max) - continue; - y1 = (this.xaxis.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = this.xaxis.max; - } - else if (x2 >= x1 && x2 > this.xaxis.max) { - if (x1 > this.xaxis.max) - continue; - y2 = (this.xaxis.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = this.xaxis.max; - } - - if (!areaOpen) { - // open area - this.context.beginPath(); - this.context.moveTo(this.translateHoz(x1), this.translateVert(bottom)); - areaOpen = true; - } - - // now first check the case where both is outside - if (y1 >= this.yaxis.max && y2 >= this.yaxis.max) { - this.context.lineTo(this.translateHoz(x1), this.translateVert(this.yaxis.max)); - this.context.lineTo(this.translateHoz(x2), this.translateVert(this.yaxis.max)); - continue; - } - else if (y1 <= this.yaxis.min && y2 <= this.yaxis.min) { - this.context.lineTo(this.translateHoz(x1), this.translateVert(this.yaxis.min)); - this.context.lineTo(this.translateHoz(x2), this.translateVert(this.yaxis.min)); - continue; - } - - var x1old = x1, x2old = x2; - - // clip with ymin - if (y1 <= y2 && y1 < this.yaxis.min && y2 >= this.yaxis.min) { - x1 = (this.yaxis.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = this.yaxis.min; - } - else if (y2 <= y1 && y2 < this.yaxis.min && y1 >= this.yaxis.min) { - x2 = (this.yaxis.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = this.yaxis.min; - } - - // clip with ymax - if (y1 >= y2 && y1 > this.yaxis.max && y2 <= this.yaxis.max) { - x1 = (this.yaxis.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = this.yaxis.max; - } - else if (y2 >= y1 && y2 > this.yaxis.max && y1 <= this.yaxis.max) { - x2 = (this.yaxis.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = this.yaxis.max; - } - - - // if the x value was changed we got a rectangle - // to fill - if (x1 != x1old) { - if (y1 <= this.yaxis.min) - top = this.yaxis.min; - else - top = this.yaxis.max; - - this.context.lineTo(this.translateHoz(x1old), this.translateVert(top)); - this.context.lineTo(this.translateHoz(x1), this.translateVert(top)); - } - - // fill the triangles - this.context.lineTo(this.translateHoz(x1), this.translateVert(y1)); - this.context.lineTo(this.translateHoz(x2), this.translateVert(y2)); - - // fill the other rectangle if it's there - if (x2 != x2old) { - if (y2 <= this.yaxis.min) - top = this.yaxis.min; - else - top = this.yaxis.max; - - this.context.lineTo(this.translateHoz(x2old), this.translateVert(top)); - this.context.lineTo(this.translateHoz(x2), this.translateVert(top)); - } - - lastX = Math.max(x2, x2old); - } - - if (areaOpen) { - this.context.lineTo(this.translateHoz(lastX), this.translateVert(bottom)); - this.context.fill(); - } - }, - /** - * function: drawGraphLines - * - * parameters: - * {Object} graphData - * - * description: - * Main function that daws the line graph. This function is called - * if <options> lines property is set to show or no other type of - * graph is specified. This function depends on <plotLineArea> and - * <plotLine> functions. - */ - drawGraphLines: function(graphData) { - this.context.save(); - this.context.translate(this.chartOffset.left, this.chartOffset.top); - this.context.lineJoin = "round"; - - var lw = graphData.lines.lineWidth; - var sw = graphData.shadowSize; - // FIXME: consider another form of shadow when filling is turned on - if (sw > 0) { - // draw shadow in two steps - this.context.lineWidth = sw / 2; - this.context.strokeStyle = "rgba(0,0,0,0.1)"; - this.plotLine(graphData.data, lw/2 + sw/2 + this.context.lineWidth/2); - - this.context.lineWidth = sw / 2; - this.context.strokeStyle = "rgba(0,0,0,0.2)"; - this.plotLine(graphData.data, lw/2 + this.context.lineWidth/2); - } - - this.context.lineWidth = lw; - this.context.strokeStyle = graphData.color; - if (graphData.lines.fill) { - this.context.fillStyle = graphData.lines.fillColor != null ? graphData.lines.fillColor : this.parseColor(graphData.color).scale(null, null, null, 0.4).toString(); - this.plotLineArea(graphData.data, 0); - } - - this.plotLine(graphData.data, 0); - this.context.restore(); - }, - /** - * function: plotPoints - * - * parameters: - * {Object} data - * {Object} radius - * {Object} fill - * - * description: - * Helper function that draws the point graph according to the data provided. Size of each - * point is provided by radius variable and fill specifies if points - * are filled - */ - plotPoints: function(data, radius, fill) { - for (var i = 0; i < data.length; ++i) { - if (data[i] == null) - continue; - - var x = data[i][0], y = data[i][1]; - if (x < this.xaxis.min || x > this.xaxis.max || y < this.yaxis.min || y > this.yaxis.max) - continue; - - this.context.beginPath(); - this.context.arc(this.translateHoz(x), this.translateVert(y), radius, 0, 2 * Math.PI, true); - if (fill) - this.context.fill(); - this.context.stroke(); - } - }, - /** - * function: plotPointShadows - * - * parameters: - * {Object} data - * {Object} offset - * {Object} radius - * - * description: - * Helper function that draws the shadows for the points. - */ - plotPointShadows: function(data, offset, radius) { - for (var i = 0; i < data.length; ++i) { - if (data[i] == null) - continue; - - var x = data[i][0], y = data[i][1]; - if (x < this.xaxis.min || x > this.xaxis.max || y < this.yaxis.min || y > this.yaxis.max) - continue; - this.context.beginPath(); - this.context.arc(this.translateHoz(x), this.translateVert(y) + offset, radius, 0, Math.PI, false); - this.context.stroke(); - } - }, - /** - * function: drawGraphPoints - * - * paramters: - * {Object} graphData - * - * description: - * Draws the point graph onto the canvas. This function depends on helper - * functions <plotPointShadows> and <plotPoints> - */ - drawGraphPoints: function(graphData) { - this.context.save(); - this.context.translate(this.chartOffset.left, this.chartOffset.top); - - var lw = graphData.lines.lineWidth; - var sw = graphData.shadowSize; - if (sw > 0) { - // draw shadow in two steps - this.context.lineWidth = sw / 2; - this.context.strokeStyle = "rgba(0,0,0,0.1)"; - this.plotPointShadows(graphData.data, sw/2 + this.context.lineWidth/2, graphData.points.radius); - - this.context.lineWidth = sw / 2; - this.context.strokeStyle = "rgba(0,0,0,0.2)"; - this.plotPointShadows(graphData.data, this.context.lineWidth/2, graphData.points.radius); - } - - this.context.lineWidth = graphData.points.lineWidth; - this.context.strokeStyle = graphData.color; - this.context.fillStyle = graphData.points.fillColor != null ? graphData.points.fillColor : graphData.color; - this.plotPoints(graphData.data, graphData.points.radius, graphData.points.fill); - this.context.restore(); - }, - /** - * function: preparePieData - * - * parameters: - * {Object} graphData - * - * Description: - * Helper function that manipulates the given data stream so that it can - * be plotted as a Pie Chart - */ - preparePieData: function(graphData) - { - for(i = 0; i < graphData.length; i++) - { - var data = 0; - for(j = 0; j < graphData[i].data.length; j++){ - data += parseInt(graphData[i].data[j][1]); - } - graphData[i].data = data; - } - }, - /** - * function: drawPieShadow - * - * {Object} anchorX - * {Object} anchorY - * {Object} radius - * - * description: - * Helper function that draws a shadow for the Pie Chart. This just draws - * a circle with offset that simulates shadow. We do not give each piece - * of the pie an individual shadow. - */ - drawPieShadow: function(anchorX, anchorY, radius) - { - this.context.beginPath(); - this.context.moveTo(anchorX, anchorY); - this.context.fillStyle = 'rgba(0,0,0,' + 0.1 + ')'; - startAngle = 0; - endAngle = (Math.PI/180)*360; - this.context.arc(anchorX + 2, anchorY +2, radius + (this.options.shadowSize/2), startAngle, endAngle, false); - this.context.fill(); - this.context.closePath(); - }, - /** - * function: drawPieGraph - * - * parameters: - * {Object} graphData - * - * description: - * Draws the actual pie chart. This function depends on helper function - * <drawPieShadow> to draw the actual shadow - */ - drawPieGraph: function(graphData) - { - var sumData = 0; - var radius = 0; - var centerX = this.chartWidth/2; - var centerY = this.chartHeight/2; - var startAngle = 0; - var endAngle = 0; - var fontSize = this.options.pies.fontSize; - var labelWidth = this.options.pies.labelWidth; - - //determine Pie Radius - if(!this.options.pies.autoScale) - radius = this.options.pies.radius; - else - radius = (this.chartHeight * 0.85)/2; - - var labelRadius = radius * 1.05; - - for(i = 0; i < graphData.length; i++) - sumData += graphData[i].data; - - // used to adjust labels so that everything adds up to 100% - totalPct = 0; - - //lets draw the shadow first.. we don't need an individual shadow to every pie rather we just - //draw a circle underneath to simulate the shadow... - this.drawPieShadow(centerX, centerY, radius, 0, 0); - - //lets draw the actual pie chart now. - graphData.each(function(gd, j){ - var pct = gd.data / sumData; - startAngle = endAngle; - endAngle += pct * (2 * Math.PI); - var sliceMiddle = (endAngle - startAngle) / 2 + startAngle; - var labelX = centerX + Math.cos(sliceMiddle) * labelRadius; - var labelY = centerY + Math.sin(sliceMiddle) * labelRadius; - var anchorX = centerX; - var anchorY = centerY; - var textAlign = null; - var verticalAlign = null; - var left = 0; - var top = 0; - - //draw pie: - //drawing pie - this.context.beginPath(); - this.context.moveTo(anchorX, anchorY); - this.context.arc(anchorX, anchorY, radius, startAngle, endAngle, false); - this.context.closePath(); - this.context.fillStyle = this.parseColor(gd.color).scale(null, null, null, this.options.pies.fillOpacity).toString(); - - if(this.options.pies.fill) { this.context.fill(); } - - // drawing labels - if (sliceMiddle <= 0.25 * (2 * Math.PI)) - { - // text on top and align left - textAlign = "left"; - verticalAlign = "top"; - left = labelX; - top = labelY + fontSize; - } - else if (sliceMiddle > 0.25 * (2 * Math.PI) && sliceMiddle <= 0.5 * (2 * Math.PI)) - { - // text on bottom and align left - textAlign = "left"; - verticalAlign = "bottom"; - left = labelX - labelWidth; - top = labelY; - } - else if (sliceMiddle > 0.5 * (2 * Math.PI) && sliceMiddle <= 0.75 * (2 * Math.PI)) - { - // text on bottom and align right - textAlign = "right"; - verticalAlign = "bottom"; - left = labelX - labelWidth; - top = labelY - fontSize; - } - else - { - // text on top and align right - textAlign = "right"; - verticalAlign = "bottom"; - left = labelX; - top = labelY - fontSize; - } - - left = left + "px"; - top = top + "px"; - var textVal = Math.round(pct * 100); - - if (j == graphData.length - 1) { - if (textVal + totalPct < 100) { - textVal = textVal + 1; - } else if (textVal + totalPct > 100) { - textVal = textVal - 1; - }; - } - - var html = "<div style=\"position: absolute;zindex:11; width:" + labelWidth + "px;fontSize:" + fontSize + "px;overflow:hidden;top:"+ top + ";left:"+ left + ";textAlign:" + textAlign + ";verticalAlign:" + verticalAlign +"\">" + textVal + "%</div>"; - //$(html).appendTo(target); - this.domObj.insert(html); - - totalPct = totalPct + textVal; - }.bind(this)); - - }, - /** - * function: drawBarGraph - * - * parameters: - * {Object} graphData - * {Object} barDataRange - * - * description: - * Goes through each series in graphdata and passes it onto <drawBarGraphs> function - */ - drawBarGraph: function(graphData, barDataRange) - { - graphData.each(function(gd, i){ - this.drawGraphBars(gd, i, graphData.size(), barDataRange); - }.bind(this)); - }, - /** - * function: drawGraphBar - * - * parameters: - * {Object} graphData - * - * description: - * This function is called when an individual series in GraphData is bar graph and plots it - */ - drawGraphBar: function(graphData) - { - this.drawGraphBars(graphData, 0, this.graphData.length, this.barDataRange); - }, - /** - * function: plotBars - * - * parameters: - * {Object} graphData - * {Object} data - * {Object} barWidth - * {Object} offset - * {Object} fill - * {Object} counter - * {Object} total - * {Object} barDataRange - * - * description: - * Helper function that draws the bar graph based on data. - */ - plotBars: function(graphData, data, barWidth, offset, fill,counter, total, barDataRange) { - var shift = 0; - - if(total % 2 == 0) - { - shift = (1 + ((counter - total /2 ) - 1)) * barWidth; - } - else - { - var interval = 0.5; - if(counter == (total/2 - interval )) { - shift = - barWidth * interval; - } - else { - shift = (interval + (counter - Math.round(total/2))) * barWidth; - } - } - - var rangeData = []; - data.each(function(d){ - if(!d) return; - - var x = d[0], y = d[1]; - var drawLeft = true, drawTop = true, drawRight = true; - var left = x + shift, right = x + barWidth + shift, bottom = 0, top = y; - var rangeDataPoint = {}; - rangeDataPoint.left = left; - rangeDataPoint.right = right; - rangeDataPoint.value = top; - rangeData.push(rangeDataPoint); - - if (right < this.xaxis.min || left > this.xaxis.max || top < this.yaxis.min || bottom > this.yaxis.max) - return; - - // clip - if (left < this.xaxis.min) { - left = this.xaxis.min; - drawLeft = false; - } - - if (right > this.xaxis.max) { - right = this.xaxis.max; - drawRight = false; - } - - if (bottom < this.yaxis.min) - bottom = this.yaxis.min; - - if (top > this.yaxis.max) { - top = this.yaxis.max; - drawTop = false; - } - - if(graphData.bars.showShadow && graphData.shadowSize > 0) - this.plotShadowOutline(graphData, this.context.strokeStyle, left, bottom, top, right, drawLeft, drawRight, drawTop); - - // fill the bar - if (fill) { - this.context.beginPath(); - this.context.moveTo(this.translateHoz(left), this.translateVert(bottom) + offset); - this.context.lineTo(this.translateHoz(left), this.translateVert(top) + offset); - this.context.lineTo(this.translateHoz(right), this.translateVert(top) + offset); - this.context.lineTo(this.translateHoz(right), this.translateVert(bottom) + offset); - this.context.fill(); - } - - // draw outline - if (drawLeft || drawRight || drawTop) { - this.context.beginPath(); - this.context.moveTo(this.translateHoz(left), this.translateVert(bottom) + offset); - if (drawLeft) - this.context.lineTo(this.translateHoz(left), this.translateVert(top) + offset); - else - this.context.moveTo(this.translateHoz(left), this.translateVert(top) + offset); - - if (drawTop) - this.context.lineTo(this.translateHoz(right), this.translateVert(top) + offset); - else - this.context.moveTo(this.translateHoz(right), this.translateVert(top) + offset); - if (drawRight) - this.context.lineTo(this.translateHoz(right), this.translateVert(bottom) + offset); - else - this.context.moveTo(this.translateHoz(right), this.translateVert(bottom) + offset); - this.context.stroke(); - } - }.bind(this)); - - barDataRange.push(rangeData); - }, - /** - * function: plotShadowOutline - * - * parameters: - * {Object} graphData - * {Object} orgStrokeStyle - * {Object} left - * {Object} bottom - * {Object} top - * {Object} right - * {Object} drawLeft - * {Object} drawRight - * {Object} drawTop - * - * description: - * Helper function that draws a outline simulating shadow for bar chart - */ - plotShadowOutline: function(graphData, orgStrokeStyle, left, bottom, top, right, drawLeft, drawRight, drawTop) - { - var orgOpac = 0.3; - - for(var n = 1; n <= this.options.shadowSize/2; n++) - { - var opac = orgOpac * n; - this.context.beginPath(); - this.context.strokeStyle = "rgba(0,0,0," + opac + ")"; - - this.context.moveTo(this.translateHoz(left) + n, this.translateVert(bottom)); - - if(drawLeft) - this.context.lineTo(this.translateHoz(left) + n, this.translateVert(top) - n); - else - this.context.moveTo(this.translateHoz(left) + n, this.translateVert(top) - n); - - if(drawTop) - this.context.lineTo(this.translateHoz(right) + n, this.translateVert(top) - n); - else - this.context.moveTo(this.translateHoz(right) + n, this.translateVert(top) - n); - - if(drawRight) - this.context.lineTo(this.translateHoz(right) + n, this.translateVert(bottom)); - else - this.context.lineTo(this.translateHoz(right) + n, this.translateVert(bottom)); - - this.context.stroke(); - this.context.closePath(); - } - - this.context.strokeStyle = orgStrokeStyle; - }, - /** - * function: drawGraphBars - * - * parameters: - * {Object} graphData - * {Object} counter - * {Object} total - * {Object} barDataRange - * - * description: - * Draws the actual bar graphs. Calls <plotBars> to draw the individual bar - */ - drawGraphBars: function(graphData, counter, total, barDataRange){ - this.context.save(); - this.context.translate(this.chartOffset.left, this.chartOffset.top); - this.context.lineJoin = "round"; - - var bw = graphData.bars.barWidth; - var lw = Math.min(graphData.bars.lineWidth, bw); - - - this.context.lineWidth = lw; - this.context.strokeStyle = graphData.color; - if (graphData.bars.fill) { - this.context.fillStyle = graphData.bars.fillColor != null ? graphData.bars.fillColor : this.parseColor(graphData.color).scale(null, null, null, this.options.bars.fillOpacity).toString(); - } - this.plotBars(graphData, graphData.data, bw, 0, graphData.bars.fill, counter, total, barDataRange); - this.context.restore(); - }, - /** - * function: insertLegend - * - * description: - * inserts legend onto the graph. *legend: {show: true}* must be set in <options> - * for for this to work. - */ - insertLegend: function() { - this.domObj.select(".legend").invoke('remove'); - - if (!this.options.legend.show) - return; - - var fragments = []; - var rowStarted = false; - this.graphData.each(function(gd, index){ - if(!gd.label) { - return; - } - if(index % this.options.legend.noColumns == 0) { - if(rowStarted) { - fragments.push('</tr>'); - } - fragments.push('<tr>'); - rowStarted = true; - } - var label = gd.label; - if(this.options.legend.labelFormatter != null) { - label = this.options.legend.labelFormatter(label); - } - - fragments.push( - '<td class="legendColorBox"><div style="border:1px solid ' + this.options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:14px;height:10px;background-color:' + gd.color + ';overflow:hidden"></div></div></td>' + - '<td class="legendLabel">' + label + '</td>'); - - }.bind(this)); - - if (rowStarted) - fragments.push('</tr>'); - - if(fragments.length > 0){ - var table = '<table style="font-size:smaller;color:' + this.options.grid.color + '">' + fragments.join("") + '</table>'; - if($(this.options.legend.container) != null){ - $(this.options.legend.container).insert(table); - }else{ - var pos = ''; - var p = this.options.legend.position, m = this.options.legend.margin; - - if(p.charAt(0) == 'n') pos += 'top:' + (m + this.chartOffset.top) + 'px;'; - else if(p.charAt(0) == 's') pos += 'bottom:' + (m + this.chartOffset.bottom) + 'px;'; - if(p.charAt(1) == 'e') pos += 'right:' + (m + this.chartOffset.right) + 'px;'; - else if(p.charAt(1) == 'w') pos += 'left:' + (m + this.chartOffset.bottom) + 'px;'; - var div = this.domObj.insert('<div class="ProtoChart-legend" style="border: 1px solid '+this.options.legend.borderColor+'; position:absolute;z-index:2;' + pos +'">' + table + '</div>').getElementsBySelector('div.ProtoChart-legend').first(); - - if(this.options.legend.backgroundOpacity != 0.0){ - var c = this.options.legend.backgroundColor; - if(c == null){ - var tmp = (this.options.grid.backgroundColor != null) ? this.options.grid.backgroundColor : this.extractColor(div); - c = this.parseColor(tmp).adjust(null, null, null, 1).toString(); - } - this.domObj.insert('<div class="ProtoChart-legend-bg" style="position:absolute;width:' + div.getWidth() + 'px;height:' + div.getHeight() + 'px;' + pos +'background-color:' + c + ';"> </div>').select('div.ProtoChart-legend-bg').first().setStyle({ - 'opacity': this.options.legend.backgroundOpacity - }); - } - } - } - }, - /** - * Function: onMouseMove - * - * parameters: - * event: {Object} ev - * - * Description: - * Called whenever the mouse is moved on the graph. This takes care of the mousetracking. - * This event also fires <ProtoChart:mousemove> event, which gets current position of the - * mouse as a parameters. - */ - onMouseMove: function(ev) { - var e = ev || window.event; - if (e.pageX == null && e.clientX != null) { - var de = document.documentElement, b = $(document.body); - this.lastMousePos.pageX = e.clientX + (de && de.scrollLeft || b.scrollLeft || 0); - this.lastMousePos.pageY = e.clientY + (de && de.scrollTop || b.scrollTop || 0); - } - else { - this.lastMousePos.pageX = e.pageX; - this.lastMousePos.pageY = e.pageY; - } - - var offset = this.overlay.cumulativeOffset(); - var pos = { - x: this.xaxis.min + (e.pageX - offset.left - this.chartOffset.left) / this.hozScale, - y: this.yaxis.max - (e.pageY - offset.top - this.chartOffset.top) / this.vertScale - }; - - if(this.options.mouse.track && this.selectionInterval == null) { - this.hit(ev, pos); - } - this.domObj.fire("ProtoChart:mousemove", [ pos ]); - }, - /** - * Function: onMouseDown - * - * Parameters: - * Event - {Object} e - * - * Description: - * Called whenever the mouse is clicked. - */ - onMouseDown: function(e) { - if (e.which != 1) // only accept left-click - return; - - document.body.focus(); - - if (document.onselectstart !== undefined && this.workarounds.onselectstart == null) { - this.workarounds.onselectstart = document.onselectstart; - document.onselectstart = function () { return false; }; - } - if (document.ondrag !== undefined && this.workarounds.ondrag == null) { - this.workarounds.ondrag = document.ondrag; - document.ondrag = function () { return false; }; - } - - this.setSelectionPos(this.selection.first, e); - - if (this.selectionInterval != null) - clearInterval(this.selectionInterval); - this.lastMousePos.pageX = null; - this.selectionInterval = setInterval(this.updateSelectionOnMouseMove.bind(this), 200); - - this.overlay.observe("mouseup", this.onSelectionMouseUp.bind(this)); - }, - /** - * Function: onClick - * parameters: - * Event - {Object} e - * Description: - * Handles the "click" event on the chart. This function fires <ProtoChart:plotclick> event. If - * <options.allowDataClick> is enabled then it also fires <ProtoChart:dataclick> event which gives - * you access to exact data point where user clicked. - */ - onClick: function(e) { - if (this.ignoreClick) { - this.ignoreClick = false; - return; - } - var offset = this.overlay.cumulativeOffset(); - var pos ={ - x: this.xaxis.min + (e.pageX - offset.left - this.chartOffset.left) / this.hozScale, - y: this.yaxis.max - (e.pageY - offset.top - this.chartOffset.top) / this.vertScale - }; - this.domObj.fire("ProtoChart:plotclick", [ pos ]); - - if(this.options.allowDataClick) - { - var dataPoint = {}; - if(this.options.points.show) - { - dataPoint = this.getDataClickPoint(pos, this.options); - this.domObj.fire("ProtoChart:dataclick", [dataPoint]); - } - else if(this.options.lines.show && this.options.points.show) - { - dataPoint = this.getDataClickPoint(pos, this.options); - this.domObj.fire("ProtoChart:dataclick", [dataPoint]); - } - else if(this.options.bars.show) - { - if(this.barDataRange.length > 0) - { - dataPoint = this.getDataClickPoint(pos, this.options, this.barDataRange); - this.domObj.fire("ProtoChart:dataclick", [dataPoint]); - } - } - } - }, - /** - * Internal function used by onClick method. - */ - getDataClickPoint: function(pos, options, barDataRange) - { - pos.x = parseInt(pos.x); - pos.y = parseInt(pos.y); - var yClick = pos.y.toFixed(0); - var dataVal = {}; - - dataVal.position = pos; - dataVal.value = ''; - - if(options.points.show) - { - this.graphData.each(function(gd){ - var temp = gd.data; - var xClick = parseInt(pos.x.toFixed(0)); - if(xClick < 0) { xClick = 0; } - if(temp[xClick] && yClick >= temp[xClick][1] - (this.options.points.radius * 10) && yClick <= temp[xClick][1] + (this.options.points.radius * 10)) { - dataVal.value = temp[xClick][1]; - throw $break; - } - - }.bind(this)); - } - else if(options.bars.show) - { - xClick = pos.x; - this.barDataRange.each(function(barData){ - barData.each(function(data){ - var temp = data; - if(xClick > temp.left && xClick < temp.right) { - dataVal.value = temp.value; - throw $break; - } - }.bind(this)); - }.bind(this)); - - } - - return dataVal; - }, - /** - * Function: triggerSelectedEvent - * - * Description: - * Internal function called when a selection on the graph is made. This function - * fires <ProtoChart:selected> event which has a parameter representing the selection - * { - * x1: {int}, y1: {int}, - * x2: {int}, y2: {int} - * } - */ - triggerSelectedEvent: function() { - var x1, x2, y1, y2; - if (this.selection.first.x <= this.selection.second.x) { - x1 = this.selection.first.x; - x2 = this.selection.second.x; - } - else { - x1 = this.selection.second.x; - x2 = this.selection.first.x; - } - - if (this.selection.first.y >= this.selection.second.y) { - y1 = this.selection.first.y; - y2 = this.selection.second.y; - } - else { - y1 = this.selection.second.y; - y2 = this.selection.first.y; - } - - x1 = this.xaxis.min + x1 / this.hozScale; - x2 = this.xaxis.min + x2 / this.hozScale; - - y1 = this.yaxis.max - y1 / this.vertScale; - y2 = this.yaxis.max - y2 / this.vertScale; - - this.domObj.fire("ProtoChart:selected", [ { x1: x1, y1: y1, x2: x2, y2: y2 } ]); - }, - /** - * Internal function - */ - onSelectionMouseUp: function(e) { - if (document.onselectstart !== undefined) - document.onselectstart = this.workarounds.onselectstart; - if (document.ondrag !== undefined) - document.ondrag = this.workarounds.ondrag; - - if (this.selectionInterval != null) { - clearInterval(this.selectionInterval); - this.selectionInterval = null; - } - - this.setSelectionPos(this.selection.second, e); - this.clearSelection(); - if (!this.selectionIsSane() || e.which != 1) - return false; - - this.drawSelection(); - this.triggerSelectedEvent(); - this.ignoreClick = true; - - return false; - }, - setSelectionPos: function(pos, e) { - var offset = $(this.overlay).cumulativeOffset(); - if (this.options.selection.mode == "y") { - if (pos == this.selection.first) - pos.x = 0; - else - pos.x = this.chartWidth; - } - else { - pos.x = e.pageX - offset.left - this.chartOffset.left; - pos.x = Math.min(Math.max(0, pos.x), this.chartWidth); - } - - if (this.options.selection.mode == "x") { - if (pos == this.selection.first) - pos.y = 0; - else - pos.y = this.chartHeight; - } - else { - pos.y = e.pageY - offset.top - this.chartOffset.top; - pos.y = Math.min(Math.max(0, pos.y), this.chartHeight); - } - }, - updateSelectionOnMouseMove: function() { - if (this.lastMousePos.pageX == null) - return; - - this.setSelectionPos(this.selection.second, this.lastMousePos); - this.clearSelection(); - if (this.selectionIsSane()) - this.drawSelection(); - }, - clearSelection: function() { - if (this.prevSelection == null) - return; - - var x = Math.min(this.prevSelection.first.x, this.prevSelection.second.x), - y = Math.min(this.prevSelection.first.y, this.prevSelection.second.y), - w = Math.abs(this.prevSelection.second.x - this.prevSelection.first.x), - h = Math.abs(this.prevSelection.second.y - this.prevSelection.first.y); - - this.overlayContext.clearRect(x + this.chartOffset.left - this.overlayContext.lineWidth, - y + this.chartOffset.top - this.overlayContext.lineWidth, - w + this.overlayContext.lineWidth*2, - h + this.overlayContext.lineWidth*2); - - this.prevSelection = null; - }, - /** - * Function: setSelection - * - * Parameters: - * Area - {Object} area represented as a range like: {x1: 3, y1: 3, x2: 4, y2: 8} - * - * Description: - * Sets the current graph selection to the provided range. Calls <drawSelection> and - * <triggerSelectedEvent> functions internally. - */ - setSelection: function(area) { - this.clearSelection(); - - if (this.options.selection.mode == "x") { - this.selection.first.y = 0; - this.selection.second.y = this.chartHeight; - } - else { - this.selection.first.y = (this.yaxis.max - area.y1) * this.vertScale; - this.selection.second.y = (this.yaxis.max - area.y2) * this.vertScale; - } - if (this.options.selection.mode == "y") { - this.selection.first.x = 0; - this.selection.second.x = this.chartWidth; - } - else { - this.selection.first.x = (area.x1 - this.xaxis.min) * this.hozScale; - this.selection.second.x = (area.x2 - this.xaxis.min) * this.hozScale; - } - - this.drawSelection(); - this.triggerSelectedEvent(); - }, - /** - * Function: drawSelection - * Description: Internal function called to draw the selection made on the graph. - */ - drawSelection: function() { - if (this.prevSelection != null && - this.selection.first.x == this.prevSelection.first.x && - this.selection.first.y == this.prevSelection.first.y && - this.selection.second.x == this.prevSelection.second.x && - this.selection.second.y == this.prevSelection.second.y) - { - return; - } - - this.overlayContext.strokeStyle = this.parseColor(this.options.selection.color).scale(null, null, null, 0.8).toString(); - this.overlayContext.lineWidth = 1; - this.context.lineJoin = "round"; - this.overlayContext.fillStyle = this.parseColor(this.options.selection.color).scale(null, null, null, 0.4).toString(); - - this.prevSelection = { first: { x: this.selection.first.x, - y: this.selection.first.y }, - second: { x: this.selection.second.x, - y: this.selection.second.y } }; - - var x = Math.min(this.selection.first.x, this.selection.second.x), - y = Math.min(this.selection.first.y, this.selection.second.y), - w = Math.abs(this.selection.second.x - this.selection.first.x), - h = Math.abs(this.selection.second.y - this.selection.first.y); - - this.overlayContext.fillRect(x + this.chartOffset.left, y + this.chartOffset.top, w, h); - this.overlayContext.strokeRect(x + this.chartOffset.left, y + this.chartOffset.top, w, h); - }, - /** - * Internal function - */ - selectionIsSane: function() { - var minSize = 5; - return Math.abs(this.selection.second.x - this.selection.first.x) >= minSize && - Math.abs(this.selection.second.y - this.selection.first.y) >= minSize; - }, - /** - * Internal function that formats the track. This is the format the text is shown when mouse - * tracking is enabled. - */ - defaultTrackFormatter: function(val) - { - return '['+val.x+', '+val.y+']'; - }, - /** - * Function: clearHit - */ - clearHit: function(){ - if(this.prevHit){ - this.overlayContext.clearRect( - this.translateHoz(this.prevHit.x) + this.chartOffset.left - this.options.mouse.radius*2, - this.translateVert(this.prevHit.y) + this.chartOffset.top - this.options.mouse.radius*2, - this.options.mouse.radius*3 + this.options.points.lineWidth*3, - this.options.mouse.radius*3 + this.options.points.lineWidth*3 - ); - this.prevHit = null; - } - }, - /** - * Function: hit - * - * Parameters: - * event - {Object} event object - * mouse - {Object} mouse object that is used to keep track of mouse movement - * - * Description: - * If hit occurs this function will fire a ProtoChart:hit event. - */ - hit: function(event, mouse){ - /** - * Nearest data element. - */ - var n = { - dist:Number.MAX_VALUE, - x:null, - y:null, - mouse:null - }; - - - for(var i = 0, data, xsens, ysens; i < this.graphData.length; i++){ - if(!this.graphData[i].mouse.track) continue; - data = this.graphData[i].data; - xsens = (this.hozScale*this.graphData[i].mouse.sensibility); - ysens = (this.vertScale*this.graphData[i].mouse.sensibility); - for(var j = 0, xabs, yabs; j < data.length; j++){ - xabs = this.hozScale*Math.abs(data[j][0] - mouse.x); - yabs = this.vertScale*Math.abs(data[j][1] - mouse.y); - - if(xabs < xsens && yabs < ysens && (xabs+yabs) < n.dist){ - n.dist = (xabs+yabs); - n.x = data[j][0]; - n.y = data[j][1]; - n.mouse = this.graphData[i].mouse; - } - } - } - - if(n.mouse && n.mouse.track && !this.prevHit || (this.prevHit && n.x != this.prevHit.x && n.y != this.prevHit.y)){ - var el = this.domObj.select('.'+this.options.mouse.clsName).first(); - if(!el){ - var pos = '', p = this.options.mouse.position, m = this.options.mouse.margin; - if(p.charAt(0) == 'n') pos += 'top:' + (m + this.chartOffset.top) + 'px;'; - else if(p.charAt(0) == 's') pos += 'bottom:' + (m + this.chartOffset.bottom) + 'px;'; - if(p.charAt(1) == 'e') pos += 'right:' + (m + this.chartOffset.right) + 'px;'; - else if(p.charAt(1) == 'w') pos += 'left:' + (m + this.chartOffset.bottom) + 'px;'; - - this.domObj.insert('<div class="'+this.options.mouse.clsName+'" style="display:none;position:absolute;'+pos+'"></div>'); - return; - } - if(n.x !== null && n.y !== null){ - el.setStyle({display:'block'}); - - this.clearHit(); - if(n.mouse.lineColor != null){ - this.overlayContext.save(); - this.overlayContext.translate(this.chartOffset.left, this.chartOffset.top); - this.overlayContext.lineWidth = this.options.points.lineWidth; - this.overlayContext.strokeStyle = n.mouse.lineColor; - this.overlayContext.fillStyle = '#ffffff'; - this.overlayContext.beginPath(); - - - this.overlayContext.arc(this.translateHoz(n.x), this.translateVert(n.y), this.options.mouse.radius, 0, 2 * Math.PI, true); - this.overlayContext.fill(); - this.overlayContext.stroke(); - this.overlayContext.restore(); - } - this.prevHit = n; - - var decimals = n.mouse.trackDecimals; - if(decimals == null || decimals < 0) decimals = 0; - if(!this.options.mouse.fixedPosition) - { - el.setStyle({ - left: (this.translateHoz(n.x) + this.options.mouse.radius + 10) + "px", - top: (this.translateVert(n.y) + this.options.mouse.radius + 10) + "px" - }); - } - el.innerHTML = n.mouse.trackFormatter({x: n.x.toFixed(decimals), y: n.y.toFixed(decimals)}); - this.domObj.fire( 'ProtoChart:hit', [n] ) - }else if(this.options.prevHit){ - el.setStyle({display:'none'}); - this.clearHit(); - } - } - }, - /** - * Internal function - */ - floorInBase: function(n, base) { - return base * Math.floor(n / base); - }, - /** - * Function: extractColor - * - * Parameters: - * element - HTML element or ID of an HTML element - * - * Returns: - * color in string format - */ - extractColor: function(element) - { - var color; - do - { - color = $(element).getStyle('background-color').toLowerCase(); - if(color != '' && color != 'transparent') - { - break; - } - element = element.up(0); //or else just get the parent .... - } while(element.nodeName.toLowerCase() != 'body'); - - //safari fix - if(color == 'rgba(0, 0, 0, 0)') - return 'transparent'; - return color; - }, - /** - * Function: parseColor - * - * Parameters: - * str - color string in different formats - * - * Returns: - * a Proto.Color Object - use toString() function to retreive the color in rgba/rgb format - */ - parseColor: function(str) - { - var result; - - /** - * rgb(num,num,num) - */ - if((result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))) - return new Proto.Color(parseInt(result[1]), parseInt(result[2]), parseInt(result[3])); - - /** - * rgba(num,num,num,num) - */ - if((result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))) - return new Proto.Color(parseInt(result[1]), parseInt(result[2]), parseInt(result[3]), parseFloat(result[4])); - - /** - * rgb(num%,num%,num%) - */ - if((result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))) - return new Proto.Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55); - - /** - * rgba(num%,num%,num%,num) - */ - if((result = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))) - return new Proto.Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4])); - - /** - * #a0b1c2 - */ - if((result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))) - return new Proto.Color(parseInt(result[1],16), parseInt(result[2],16), parseInt(result[3],16)); - - /** - * #fff - */ - if((result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))) - return new Proto.Color(parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16)); - - /** - * Otherwise, check if user wants transparent .. or we just return a standard color; - */ - var name = str.strip().toLowerCase(); - if(name == 'transparent'){ - return new Proto.Color(255, 255, 255, 0); - } - - return new Proto.Color(100,100,100, 1); - - } -}); - -if(!Proto) var Proto = {}; - -/** - * Class: Proto.Color - * - * Helper class that manipulates colors using RGBA values. - * - */ - -Proto.Color = Class.create({ - initialize: function(r, g, b, a) { - this.rgba = ['r', 'g', 'b', 'a']; - var x = 4; - while(-1<--x) { - this[this.rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0); - } - }, - toString: function() { - if(this.a >= 1.0) { - return "rgb(" + [this.r, this.g, this.b].join(",") +")"; - } - else { - return "rgba("+[this.r, this.g, this.b, this.a].join(",")+")"; - } - }, - scale: function(rf, gf, bf, af) { - x = 4; - while(-1<--x) { - if(arguments[x] != null) { - this[this.rgba[x]] *= arguments[x]; - } - } - return this.normalize(); - }, - adjust: function(rd, gd, bd, ad) { - x = 4; //rgba.length - while (-1<--x) { - if (arguments[x] != null) - this[this.rgba[x]] += arguments[x]; - } - return this.normalize(); - }, - clone: function() { - return new Proto.Color(this.r, this.b, this.g, this.a); - }, - limit: function(val,minVal,maxVal) { - return Math.max(Math.min(val, maxVal), minVal); - }, - normalize: function() { - this.r = this.limit(parseInt(this.r), 0, 255); - this.g = this.limit(parseInt(this.g), 0, 255); - this.b = this.limit(parseInt(this.b), 0, 255); - this.a = this.limit(this.a, 0, 1); - return this; - } -});
\ No newline at end of file |