UNPKG

ico

Version:

A graph plotting library

999 lines (859 loc) 30.1 kB
/*! * Ico * Copyright (C) 2009-2011 Alex R. Young * MIT Licensed */ /** * The Ico object. */ (function(global) { var Ico = { VERSION: '0.3.3', /** * Rounds a float to the specified number of decimal places. * * @param {Float} num A number to round * @param {Integer} dec The number of decimal places * @returns {Float} The rounded result */ round: function(num, dec) { var result = Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec); return result; } }; /** * Determines if a value is valid as a 'step' value. * Steps are the increments between each bar or line. * * @param {Integer} value A number to test * @returns {Integer} A valid step value */ function validStepDivider(value) { return value > 1 ? value - 1 : 1; } /** * Gets a CSS style property. * * @param {Object} el A DOM element * @param {String} styleProp The name of a style property * @returns {Object} The style value */ function getStyle(el, styleProp) { if (typeof window === 'undefined') return; var style; if (el.currentStyle) { style = el.currentStyle[styleProp]; } else if (window.getComputedStyle) { style = document.defaultView.getComputedStyle(el, null).getPropertyValue(styleProp); } if (style && style.length === 0) { style = null; } return style; } var Helpers = {}; Helpers.sum = function(a) { for (var i = 0, sum = 0; i < a.length; sum += a[i++]) {} return sum; }; if (typeof Array.prototype.max === 'undefined') { Helpers.max = function(a) { return Math.max.apply({}, a); }; } else { Helpers.max = function(a) { return a.max(); }; } if (typeof Array.prototype.min === 'undefined') { Helpers.min = function(a) { return Math.min.apply({}, a); }; } else { Helpers.min = function(a) { return a.min(); }; } Helpers.mean = function(a) { return Helpers.sum(a) / a.length; }; Helpers.variance = function(a) { var mean = Helpers.mean(a), variance = 0; for (var i = 0; i < a.length; i++) { variance += Math.pow(a[i] - mean, 2); } return variance / (a.length - 1); }; Helpers.standard_deviation = function(a) { return Math.sqrt(Helpers.variance(a)); }; if (typeof Object.extend === 'undefined') { Helpers.extend = function(destination, source) { for (var property in source) { if (source.hasOwnProperty(property)) { destination[property] = source[property]; } } return destination; }; } else { Helpers.extend = Object.extend; } /** * Normalises lists of values to fit inside a graph. * * @param {Array} data A list of values * @param {Object} options Can be used to set the `start_value` */ Ico.Normaliser = function(data, options) { this.options = { start_value: null }; if (typeof options !== 'undefined') { this.options = options; } this.min = Helpers.min(data); this.max = Helpers.max(data); this.standard_deviation = Helpers.standard_deviation(data); this.range = 0; this.step = this.labelStep(this.max - this.min); this.start_value = this.calculateStart(); this.process(); }; Ico.Normaliser.prototype = { /** * Calculates the start value. This is often 0. * @returns {Float} The start value */ calculateStart: function() { var min = typeof this.options.start_value !== 'undefined' && this.min >= 0 ? this.options.start_value : this.min, start_value = this.round(min, 1); /* This is a boundary condition */ if (this.min > 0 && start_value > this.min) { return 0; } if (this.min === this.max) { return 0; } return start_value; }, /* Given a value, this method rounds it to the nearest good value for an origin */ round: function(value, offset) { offset = offset || 1; var roundedValue = value; if (this.standard_deviation > 0.1) { var multiplier = Math.pow(10, -offset); roundedValue = Math.round(value * multiplier) / multiplier; if (roundedValue > this.min) { return this.round(value - this.step); } } return roundedValue; }, /** * Calculates the range and step values. */ process: function() { this.range = this.max - this.start_value; this.step = this.labelStep(this.range); }, /** * Calculates the label step value. * * @param {Float} value A value to convert to a label position * @returns {Float} The rounded label step result */ labelStep: function(value) { return Math.pow(10, Math.round((Math.log(value) / Math.LN10)) - 1); } }; /*! * Ico * Copyright (C) 2009-2011 Alex R. Young * MIT Licensed */ /** * The Ico.Base object which contains useful generic functions. */ Ico.Base = { /** * Runs this.normalise on each value. * * @param {Array} data Values to normalise * @returns {Array} Normalised values */ normaliseData: function(data) { var values = [], i = 0; for (i = 0; i < data.length; i++) { values.push(this.normalise(data[i])); } return values; }, /** * Flattens objects into an array. * * @param {Object} data Values to flatten * @returns {Array} Flattened values */ flatten: function(data) { var flat_data = []; if (typeof data.length === 'undefined') { if (typeof data === 'object') { for (var key in data) { if (data.hasOwnProperty(key)) flat_data = flat_data.concat(this.flatten(data[key])); } } else { return []; } } for (var i = 0; i < data.length; i++) { if (typeof data[i].length === 'number') { flat_data = flat_data.concat(this.flatten(data[i])); } else { flat_data.push(data[i]); } } return flat_data; }, /** * Handy method to produce an array of numbers. * * @param {Integer} start A number to start at * @param {Integer} end A number to end at * @returns {Array} An array of values */ makeRange: function(start, end) { var values = [], i; for (i = start; i < end; i++) { values.push(i); } return values; } }; /** * Ico.BaseGraph is extended by most of the other graphs. It * uses a simple pattern with methods that can be overridden. */ Ico.BaseGraph = function() { this.initialize.apply(this, arguments); }; Helpers.extend(Ico.BaseGraph.prototype, Ico.Base); Helpers.extend(Ico.BaseGraph.prototype, { /** * This base class is used by the other graphs in Ico. * * Options: * * `width`: The width of the container element * `height`: The height of the container element * `labels`: The textual labels * `label_count`: The number of numerical labels to display * `label_step`: The value to increment each numerical label * `start_value`: The value to start plotting from (generally 0) * * @param {Object} A DOM element * @param {Array|Object} Data to display * @param {Object} Options * */ initialize: function(element, data, options) { options = options || {}; this.element = element; this.data_sets = this.buildDataSets(data, options); this.flat_data = this.flatten(data); this.data_size = this.longestDataSetLength(); /* If one colour is specified, map it to a compatible set */ if (options && options.colour) { options.colours = {}; for (var key in this.data_sets) { if (this.data_sets.hasOwnProperty(key)) options.colours[key] = options.colour; } } this.options = { width: parseInt(getStyle(element, 'width'), 10), height: parseInt(getStyle(element, 'height'), 10), labels: this.makeRange(1, this.data_size + 1), // Label data plot_padding: 10, // Padding for the graph line/bar plots font_size: 10, // Label font size show_horizontal_labels: true, show_vertical_labels: true, background_colour: getStyle(element, 'backgroundColor') || '#ffffff', label_colour: '#666', // Label text colour markers: false, // false, circle marker_size: 5, meanline: false, grid: false, grid_colour: '#ccc', y_padding_top: 20, draw: true }; Helpers.extend(this.options, this.chartDefaults() || {}); Helpers.extend(this.options, options); this.normaliser = new Ico.Normaliser(this.flat_data, this.normaliserOptions()); this.label_step = options.label_step || this.normaliser.step; this.range = this.normaliser.range; this.start_value = options.start_value || this.normaliser.start_value; /* Padding around the graph area to make room for labels */ this.x_padding_left = 10 + this.paddingLeftOffset(); this.x_padding_right = 20; this.x_padding = this.x_padding_left + this.x_padding_right; this.y_padding_top = this.options.y_padding_top; this.y_padding_bottom = 20 + this.paddingBottomOffset(); this.y_padding = this.y_padding_top + this.y_padding_bottom; this.graph_width = this.options.width - (this.x_padding); this.graph_height = this.options.height - (this.y_padding); this.step = this.calculateStep(); /* Calculate how many labels are required */ if (options.label_count) { this.y_label_count = options.label_count; } else { this.y_label_count = Math.ceil(this.range / this.label_step); if ((this.normaliser.min + (this.y_label_count * this.normaliser.step)) < this.normaliser.max) { this.y_label_count += 1; } } this.value_labels = this.makeValueLabels(this.y_label_count); this.top_value = this.value_labels[this.value_labels.length - 1]; /* Grid control options */ this.grid_start_offset = -1; /* Drawing */ if (this.options.draw) { if (typeof this.options.colours === 'undefined') { this.options.colours = this.makeRandomColours(); } this.paper = Raphael(this.element, this.options.width, this.options.height); this.background = this.paper.rect(0, 0, this.options.width, this.options.height); this.background.attr({fill: this.options.background_colour, stroke: 'none' }); if (this.options.meanline === true) { this.options.meanline = { 'stroke-width': '2px', stroke: '#BBBBBB' }; } this.setChartSpecificOptions(); this.lastPoint = { x: 0, y: 0 }; this.draw(); } }, buildDataSets: function(data, options) { return (typeof data.length !== 'undefined') ? { 'one': data } : data; }, normaliserOptions: function() { return {}; }, chartDefaults: function() { /* Define in child class */ return {}; }, drawPlot: function(index, pathString, x, y, colour) { /* Define in child class */ }, calculateStep: function() { /* Define in child classes */ }, makeRandomColours: function() { var colours = {}; for (var key in this.data_sets) { if (!colours.hasOwnProperty(key)) colours[key] = Raphael.hsb2rgb(Math.random(), 1, 0.75).hex; } return colours; }, longestDataSetLength: function() { var length = 0; for (var key in this.data_sets) { if (this.data_sets.hasOwnProperty(key)) { length = this.data_sets[key].length > length ? this.data_sets[key].length : length; } } return length; }, roundValue: function(value, length) { var multiplier = Math.pow(10, length); value *= multiplier; value = Math.round(value) / multiplier; return value; }, roundValues: function(data, length) { var roundedData = []; for (var i = 0; i < data.length; i++) { roundedData.push(this.roundValue(data[i], length)); } return roundedData; }, longestLabel: function(values) { var labels = Array.prototype.slice.call(values || this.options.labels, 0); if (labels.length) { return labels.sort(function(a, b) { return a.toString().length < b.toString().length; })[0].toString().length; } return 0; }, paddingLeftOffset: function() { /* Find the longest label and multiply it by the font size */ var data = this.roundValues(this.flat_data, 2), longest_label_length = 0; longest_label_length = data.sort(function(a, b) { return a.toString().length < b.toString().length; })[0].toString().length; longest_label_length = longest_label_length > 2 ? longest_label_length - 1 : longest_label_length; return 10 + (longest_label_length * this.options.font_size); }, paddingBottomOffset: function() { /* Find the longest label and multiply it by the font size */ return this.options.font_size; }, normalise: function(value) { var total = this.start_value === 0 ? this.top_value : this.range; return ((value / total) * (this.graph_height)); }, draw: function() { if (this.options.grid) { this.drawGrid(); } if (this.options.meanline) { this.drawMeanLine(this.normaliseData(this.flat_data)); } this.drawAxis(); if (this.options.show_vertical_labels) { this.drawVerticalLabels(); } if (this.options.show_horizontal_labels) { this.drawHorizontalLabels(); } for (var key in this.data_sets) { if (this.data_sets.hasOwnProperty(key)) { var data = this.data_sets[key]; this.drawLines(key, this.options.colours[key], this.normaliseData(data)); } } if (this.start_value !== 0) { this.drawFocusHint(); } }, drawGrid: function() { var pathString = '', i; if (this.options.show_vertical_labels) { var y = this.graph_height + this.y_padding_top; for (i = 0; i < this.y_label_count; i++) { y = y - (this.graph_height / this.y_label_count); pathString += 'M' + this.x_padding_left + ',' + y; pathString += 'L' + (this.x_padding_left + this.graph_width) + ',' + y; } } if (this.options.show_horizontal_labels) { var x = this.x_padding_left + this.options.plot_padding + this.grid_start_offset, x_labels = this.options.labels.length; for (i = 0; i < x_labels; i++) { pathString += 'M' + x + ',' + this.y_padding_top; pathString += 'L' + x +',' + (this.y_padding_top + this.graph_height); x = x + this.step; } x = x - this.options.plot_padding - 1; pathString += 'M' + x + ',' + this.y_padding_top; pathString += 'L' + x + ',' + (this.y_padding_top + this.graph_height); } this.paper.path(pathString).attr({ stroke: this.options.grid_colour, 'stroke-width': '1px'}); }, drawLines: function(label, colour, data) { var coords = this.calculateCoords(data), pathString = ''; for (var i = 0; i < coords.length; i++) { var x = coords[i][0] || 0, y = coords[i][1] || 0; pathString = this.drawPlot(i, pathString, x, y, colour); } this.paper.path(pathString).attr({stroke: colour, 'stroke-width': '3px'}); }, calculateCoords: function(data) { var x = this.x_padding_left + this.options.plot_padding - this.step, y_offset = (this.graph_height + this.y_padding_top) + this.normalise(this.start_value), y = 0, coords = []; for (var i = 0; i < data.length; i++) { y = y_offset - data[i]; x = x + this.step; coords.push([x, y]); } return coords; }, drawFocusHint: function() { var length = 5, x = this.x_padding_left + (length / 2) - 1, y = this.options.height - this.y_padding_bottom, pathString = ''; pathString += 'M' + x + ',' + y; pathString += 'L' + (x - length) + ',' + (y - length); pathString += 'M' + x + ',' + (y - length); pathString += 'L' + (x - length) + ',' + (y - (length * 2)); this.paper.path(pathString).attr({stroke: this.options.label_colour, 'stroke-width': 2}); }, drawMeanLine: function(data) { var offset = Helpers.sum(data) / data.length, pathString = ''; pathString += 'M' + (this.x_padding_left - 1) + ',' + (this.options.height - this.y_padding_bottom - offset); pathString += 'L' + (this.graph_width + this.x_padding_left) + ',' + (this.options.height - this.y_padding_bottom - offset); this.paper.path(pathString).attr(this.options.meanline); }, drawAxis: function() { var pathString = ''; pathString += 'M' + (this.x_padding_left - 1) + ',' + (this.options.height - this.y_padding_bottom); pathString += 'L' + (this.graph_width + this.x_padding_left) + ',' + (this.options.height - this.y_padding_bottom); pathString += 'M' + (this.x_padding_left - 1) + ',' + (this.options.height - this.y_padding_bottom); pathString += 'L' + (this.x_padding_left - 1) + ',' + (this.y_padding_top); this.paper.path(pathString).attr({ stroke: this.options.label_colour }); }, makeValueLabels: function(steps) { var step = this.label_step, label = this.start_value, labels = []; for (var i = 0; i < steps; i++) { label = this.roundValue((label + step), 2); labels.push(label); } return labels; }, /* Axis label markers */ drawMarkers: function(labels, direction, step, start_offset, font_offsets, extra_font_options) { function x_offset(value) { return value * direction[0]; } function y_offset(value) { return value * direction[1]; } /* Start at the origin */ var x = this.x_padding_left - 1 + x_offset(start_offset), y = this.options.height - this.y_padding_bottom + y_offset(start_offset), pathString = '', font_options = {"font": this.options.font_size + 'px "Arial"', stroke: "none", fill: "#000"}; Helpers.extend(font_options, extra_font_options || {}); for (var i = 0; i < labels.length; i++) { pathString += 'M' + x + ',' + y; if (typeof labels[i] !== 'undefined' && (labels[i] + '').length > 0) { pathString += 'L' + (x + y_offset(5)) + ',' + (y + x_offset(5)); this.paper.text(x + font_offsets[0], y - font_offsets[1], labels[i]).attr(font_options).toFront(); } x = x + x_offset(step); y = y + y_offset(step); } this.paper.path(pathString).attr({ stroke: this.options.label_colour }); }, drawVerticalLabels: function() { var y_step = this.graph_height / this.y_label_count; this.drawMarkers(this.value_labels, [0, -1], y_step, y_step, [-8, -2], { "text-anchor": 'end' }); }, drawHorizontalLabels: function() { this.drawMarkers(this.options.labels, [1, 0], this.step, this.options.plot_padding, [0, (this.options.font_size + 7) * -1]); } }); /** * The BarGraph class. * * Example: * * new Ico.LineGraph($('linegraph_2'), * [100, 10, 90, 20, 80, 30], * { meanline: { stroke: '#AA0000' }, * grid: true } ); * */ Ico.BarGraph = function() { this.initialize.apply(this, arguments); }; Helpers.extend(Ico.BarGraph.prototype, Ico.BaseGraph.prototype); Helpers.extend(Ico.BarGraph.prototype, { /** * Sensible defaults for BarGraph. */ chartDefaults: function() { return { plot_padding: 0 }; }, /** * Ensures the normalises is always 0. */ normaliserOptions: function() { return { start_value: 0 }; }, /** * Options specific to BarGraph. */ setChartSpecificOptions: function() { this.bar_padding = 5; this.bar_width = this.calculateBarWidth(); this.options.plot_padding = (this.bar_width / 2); this.step = this.calculateStep(); this.grid_start_offset = this.bar_padding - 1; this.start_y = this.options.height - this.y_padding_bottom; }, /** * Calculates the width of each bar. * * @returns {Integer} The bar width */ calculateBarWidth: function() { return (this.graph_width / this.data_size) - this.bar_padding; }, /** * Calculates step used to move from one bar to another. * * @returns {Float} The start value */ calculateStep: function() { return (this.graph_width - (this.options.plot_padding * 2) - (this.bar_padding * 2)) / validStepDivider(this.data_size); }, /** * Generates paths for Raphael. * * @param {Integer} index The index of the data value to plot * @param {String} pathString The pathString so far * @param {Integer} x The x-coord to plot * @param {Integer} y The y-coord to plot * @param {String} colour A string that represents a colour * @returns {String} The resulting path string */ drawPlot: function(index, pathString, x, y, colour) { x = x + this.bar_padding; pathString += 'M' + x + ',' + this.start_y; pathString += 'L' + x + ',' + y; this.paper.path(pathString).attr({stroke: colour, 'stroke-width': this.bar_width + 'px'}); pathString = ''; x = x + this.step; pathString += 'M' + x + ',' + this.start_y; return pathString; }, /* Change the standard options to correctly offset against the bars */ drawHorizontalLabels: function() { var x_start = this.bar_padding + this.options.plot_padding; this.drawMarkers(this.options.labels, [1, 0], this.step, x_start, [0, (this.options.font_size + 7) * -1]); } }); /** * Draws horizontal bar graphs. * * Example: * * new Ico.HorizontalBarGraph(element, * [2, 5, 1, 10, 15, 33, 20, 25, 1], * { font_size: 14 }); * */ Ico.HorizontalBarGraph = function() { this.initialize.apply(this, arguments); }; Helpers.extend(Ico.HorizontalBarGraph.prototype, Ico.BaseGraph.prototype); Helpers.extend(Ico.HorizontalBarGraph.prototype, { setChartSpecificOptions: function() { // Approximate the width required by the labels this.y_padding_top = 0; this.x_padding_left = 20 + this.longestLabel() * (this.options.font_size / 2); this.bar_padding = 5; this.bar_width = this.calculateBarHeight(); this.options.plot_padding = 0; this.step = this.calculateStep(); }, normalise: function(value) { var offset = this.x_padding_left; return ((value / this.range) * (this.graph_width - offset)); }, /* Height */ calculateBarHeight: function() { return (this.graph_height / this.data_size) - this.bar_padding; }, calculateStep: function() { return (this.options.height - this.y_padding_bottom) / validStepDivider(this.data_size); }, drawLines: function(label, colour, data) { var x = this.x_padding_left + (this.options.plot_padding * 2), y = this.options.height - this.y_padding_bottom - (this.step / 2), pathString = 'M' + x + ',' + y, i; for (i = 0; i < data.length; i++) { pathString += 'L' + (x + data[i] - this.normalise(this.start_value)) + ',' + y; y = y - this.step; pathString += 'M' + x + ',' + y; } this.paper.path(pathString).attr({stroke: colour, 'stroke-width': this.bar_width + 'px'}); }, /* Horizontal version */ drawFocusHint: function() { var length = 5, x = this.x_padding_left + (this.step * 2), y = this.options.height - this.y_padding_bottom, pathString = ''; pathString += 'M' + x + ',' + y; pathString += 'L' + (x - length) + ',' + (y + length); pathString += 'M' + (x - length) + ',' + y; pathString += 'L' + (x - (length * 2)) + ',' + (y + length); this.paper.path(pathString).attr({stroke: this.options.label_colour, 'stroke-width': 2}); }, drawVerticalLabels: function() { var y_start = (this.step / 2) - (this.options.plot_padding * 2); this.drawMarkers(this.options.labels, [0, -1], this.step, y_start, [-8, (this.options.font_size / 8)], { 'text-anchor': 'end' }); }, drawHorizontalLabels: function() { var x_step = this.graph_width / this.y_label_count, x_labels = this.makeValueLabels(this.y_label_count); this.drawMarkers(x_labels, [1, 0], x_step, x_step, [0, (this.options.font_size + 7) * -1]); } }); /** * Draws line graphs. * * Example: * * new Ico.LineGraph(element, [10, 5, 22, 44, 4]); * */ Ico.LineGraph = function() { this.initialize.apply(this, arguments); }; Helpers.extend(Ico.LineGraph.prototype, Ico.BaseGraph.prototype); Helpers.extend(Ico.LineGraph.prototype, { normalise: function(value) { var total = this.start_value === 0 ? this.top_value : this.top_value - this.start_value; return ((value / total) * (this.graph_height)); }, chartDefaults: function() { return { plot_padding: 10 }; }, setChartSpecificOptions: function() { // Approximate the width required by the labels var longestLabel = this.longestLabel(this.value_labels); this.x_padding_left = 30 + longestLabel * (this.options.font_size / 2); if (typeof this.options.curve_amount === 'undefined') { this.options.curve_amount = 10; } }, normaliserOptions: function() { return { start_value: this.options.start_value }; }, calculateStep: function() { return (this.graph_width - (this.options.plot_padding * 2)) / validStepDivider(this.data_size); }, startPlot: function(pathString, x, y, colour) { this.lastPoint = { x: x, y: y }; return pathString + 'M' + x + ',' + y; }, drawPlot: function(index, pathString, x, y, colour) { var w = this.options.curve_amount; if (this.options.markers === 'circle') { var circle = this.paper.circle(x, y, this.options.marker_size); circle.attr({ 'stroke-width': '1px', stroke: this.options.background_colour, fill: colour }); } if (index === 0) { return this.startPlot(pathString, x, y, colour); } if (w) { pathString += ['C', this.lastPoint.x + w, this.lastPoint.y, x - w, y, x, y]; } else { pathString += 'L' + x + ',' + y; } this.lastPoint = { x: x, y: y }; return pathString; } }); /** * Draws spark line graphs. * * Example: * * new Ico.SparkLine(element, * [21, 41, 32, 1, 10, 5, 32, 10, 23], * { width: 30, height: 14, * background_colour: '#ccc' }); * * */ Ico.SparkLine = function() { this.initialize.apply(this, arguments); }; Ico.SparkLine.prototype = { initialize: function(element, data, options) { this.element = element; this.data = data; this.options = { width: parseInt(getStyle(element, 'width'), 10), height: parseInt(getStyle(element, 'height'), 10), highlight: false, background_colour: getStyle(element, 'backgroundColor') || '#ffffff', colour: '#036' }; Helpers.extend(this.options, options || { }); this.step = this.calculateStep(); this.paper = Raphael(this.element, this.options.width, this.options.height); if (this.options.acceptable_range) { this.background = this.paper.rect(0, this.options.height - this.normalise(this.options.acceptable_range[1]), this.options.width, this.options.height - this.normalise(this.options.acceptable_range[0])); } else { this.background = this.paper.rect(0, 0, this.options.width, this.options.height); } this.background.attr({fill: this.options.background_colour, stroke: 'none' }); this.draw(); }, calculateStep: function() { return this.options.width / validStepDivider(this.data.length); }, normalise: function(value) { return (this.options.height / Helpers.max(this.data)) * value; }, draw: function() { var data = this.normaliseData(this.data); this.drawLines('', this.options.colour, data); if (this.options.highlight) { this.showHighlight(data); } }, drawLines: function(label, colour, data) { var pathString = '', x = 0, values = data.slice(1), i = 0; pathString = 'M0,' + (this.options.height - data[0]); for (i = 1; i < data.length; i++) { x = x + this.step; pathString += 'L' + x +',' + Ico.round(this.options.height - data[i], 2); } this.paper.path(pathString).attr({stroke: colour}); this.lastPoint = { x: 0, y: this.options.height - data[0] }; }, showHighlight: function(data) { var size = 2, x = this.options.width - size, i = this.options.highlight.index || data.length - 1, y = data[i] + (Math.round(size / 2)); if (typeof(this.options.highlight.index) !== 'undefined') { x = this.step * this.options.highlight.index; } var circle = this.paper.circle(x, this.options.height - y, size); circle.attr({ stroke: false, fill: this.options.highlight.colour}); } }; Helpers.extend(Ico.SparkLine.prototype, Ico.Base); /** * Draws spark bar graphs. * * Example: * * new Ico.SparkBar($('sparkline_2'), * [1, 5, 10, 15, 20, 15, 10, 15, 30, 15, 10], * { width: 30, height: 14, background_colour: '#ccc' }); * */ Ico.SparkBar = function() { this.initialize.apply(this, arguments); }; Helpers.extend(Ico.SparkBar.prototype, Ico.SparkLine.prototype); Helpers.extend(Ico.SparkBar.prototype, { calculateStep: function() { return this.options.width / validStepDivider(this.data.length); }, drawLines: function(label, colour, data) { var width = this.step > 2 ? this.step - 1 : this.step, x = width, pathString = '', i = 0; for (i = 0; i < data.length; i++) { pathString += 'M' + x + ',' + (this.options.height - data[i]); pathString += 'L' + x + ',' + this.options.height; x = x + this.step; } this.paper.path(pathString).attr({ stroke: colour, 'stroke-width': width }); } }); /** * Assign the Ico object as a global property. */ global.Ico = Ico; if (typeof exports !== 'undefined') { module.exports = Ico; } })(typeof window === 'undefined' ? this : window);