diff --git a/examples/index.html b/examples/index.html index f9ee940..458e829 100644 --- a/examples/index.html +++ b/examples/index.html @@ -20,12 +20,7 @@ - diff --git a/examples/main.controller.js b/examples/main.controller.js index db1c807..c1d58eb 100644 --- a/examples/main.controller.js +++ b/examples/main.controller.js @@ -1,6 +1,6 @@ angular.module('example', ['angularCharts']); -function MainController($scope) { +function MainController($scope, acChartLogic) { $scope.data1 = { series: ['Sales', 'Income', 'Expense', 'Laptops', 'Keyboards'], data: [{ @@ -57,4 +57,6 @@ function MainController($scope) { }, lineLegend: 'traditional' } + + $scope.availableCharts = acChartLogic.getAvailableCharts(); } \ No newline at end of file diff --git a/src/angular-charts.js b/src/angular-charts.js index 23460e6..6236343 100644 --- a/src/angular-charts.js +++ b/src/angular-charts.js @@ -8,34 +8,6 @@ angular.module('angularCharts', ['angularChartsTemplates']); */ angular.module('angularCharts').directive('acChart', function($templateCache, $compile, $rootElement, $window, $timeout, $sce, acChartLogic) { - var defaultColors = [ - 'rgb(255,153,0)', - 'rgb(220,57,18)', - 'rgb(70,132,238)', - 'rgb(73,66,204)', - 'rgb(0,128,0)', - 'rgb(0, 169, 221)', - 'steelBlue', - 'rgb(0, 169, 221)', - 'rgb(50, 205, 252)', - 'rgb(70,132,238)', - 'rgb(0, 169, 221)', - 'rgb(5, 150, 194)', - 'rgb(50, 183, 224)', - 'steelBlue', - 'rgb(2, 185, 241)', - 'rgb(0, 169, 221)', - 'steelBlue', - 'rgb(0, 169, 221)', - 'rgb(50, 205, 252)', - 'rgb(70,132,238)', - 'rgb(0, 169, 221)', - 'rgb(5, 150, 194)', - 'rgb(50, 183, 224)', - 'steelBlue', - 'rgb(2, 185, 241)' - ]; - /** * Utility function that gets the child that matches the classname * because Angular.element.children() doesn't take selectors @@ -79,7 +51,7 @@ angular.module('angularCharts').directive('acChart', function($templateCache, $c position: 'left', htmlEnabled: false }, - colors: defaultColors, + colors: acChartLogic.getDefaultColors(), innerRadius: 0, // Only on pie Charts lineLegend: 'lineEnd', // Only on line Charts lineCurveType: 'cardinal', @@ -106,6 +78,12 @@ angular.module('angularCharts').directive('acChart', function($templateCache, $c var box = { height: 0, width: 0, + margin: { + top: 0, + right: 40, + bottom: 20, + left: 40 + }, chartContainer: null, legendContainer: null, yMaxData: null, @@ -147,6 +125,12 @@ angular.module('angularCharts').directive('acChart', function($templateCache, $c prepareData(); setHeightWidth(); setContainers(); + box.margin = { + top: 0, + right: 40, + bottom: 20, + left: 40 + }; acChartLogic.callChartFunction(chartType, config, box, domFunctions, series, points); setYMaxData(); drawLegend(); diff --git a/src/provider.js b/src/provider.js index 37d0baa..8f62ec1 100644 --- a/src/provider.js +++ b/src/provider.js @@ -1,12 +1,72 @@ /** - * Chart Provider + * Singleton Chart Logic + * + * Provides an API for customizing and adding charts at the application level */ angular.module('angularCharts').provider('acChartLogic', function(){ + + /** + * Provider object + * + * @access private + * @type {Object} + */ + var acChartLogicProvider = {}; - var service = {}; - var chartFunctions = []; + /** + * Stores chart functions + * + * @access private + * @type {Object} + */ + var chartFunctions = {}; + + /** + * angular $injector service + * Set when acChartLogic is injected into directive + * + * @access private + */ var injector; + /** + * Beautiful default colors + * + * @type {Array} + */ + var defaultColors = [ + 'rgb(255,153,0)', + 'rgb(220,57,18)', + 'rgb(70,132,238)', + 'rgb(73,66,204)', + 'rgb(0,128,0)', + 'rgb(0, 169, 221)', + 'steelBlue', + 'rgb(0, 169, 221)', + 'rgb(50, 205, 252)', + 'rgb(70,132,238)', + 'rgb(0, 169, 221)', + 'rgb(5, 150, 194)', + 'rgb(50, 183, 224)', + 'steelBlue', + 'rgb(2, 185, 241)', + 'rgb(0, 169, 221)', + 'steelBlue', + 'rgb(0, 169, 221)', + 'rgb(50, 205, 252)', + 'rgb(70,132,238)', + 'rgb(0, 169, 221)', + 'rgb(5, 150, 194)', + 'rgb(50, 183, 224)', + 'steelBlue', + 'rgb(2, 185, 241)' + ]; + + /** + * HTML Character replacement map + * + * @type {Object} + */ var HTML_ENTITY_MAP = { "&": "&", "<": "<", @@ -18,27 +78,102 @@ /** * Utility function to check if parameter is $injector.invoke() able + * + * @access private * @param {Function || array} subject * @return {Boolean} */ - function isInvokable(subject){ - var response = false; - - if(typeof subject == 'function' - || ( ( typeof subject == 'array' || typeof subject == 'object' ) - && typeof subject[subject.length - 1] == 'function') ) - response = true; + function isInvokable(subject){ + var response = false; + + if(typeof subject == 'function' + || ( ( typeof subject == 'array' || typeof subject == 'object' ) + && typeof subject[subject.length - 1] == 'function') ) + response = true; - return response; - } + return response; + } + + /** + * Public API when injected into anything but the module config + * + * @return {Object} + */ + acChartLogicProvider.$get = ['$injector', function($injector){ + + //Provides usable version of injector to acChartLogicProvider + injector = $injector; + + return { + /** + * Invokes chart function injecting acChartLogicProvider as this + * @access public + */ + callChartFunction: function (type, config, box, domFunctions, series, points){ + // == null will catch undefined while === will not + if(type == null || config == null || box == null || domFunctions == null || series == null || points == null) + throw new Error('Missing Parameter(s) expects (string type, object config, object box, object domFunctions, array series, array points)'); + + if(!chartFunctions.hasOwnProperty(type)) + throw new Error('Chart type "'+type+'" does not exist'); + + var localInjections = { + config: config, + box: box, + domFunctions: domFunctions, + series: series, + points: points + }; + + $injector.invoke(chartFunctions[type].chart, acChartLogicProvider, localInjections); + + //Blank legends before calling function + box.legends = []; + $injector.invoke(chartFunctions[type].legend, acChartLogicProvider, localInjections); + }, + + /** + * Retrieves default color list + * + * @return {Array} + */ + getDefaultColors: function(){ + return defaultColors; + }, + + /** + * Get available charts as array + * + * @return {Array} + */ + getAvailableCharts: function (){ + return Object.keys(chartFunctions); + } + }; + }]; + + /** + * Set default colors + * + * @param {Array} list + * @return acChartLogicProvider + */ + acChartLogicProvider.setDefaultColors = function(list) { + if(!Array.isArray(list)) + throw new Error('setDefaultColors expects an array'); + + defaultColors = list; + + return acChartLogicProvider; + }; /** * Utility function to call when we run out of colors! * @access config * @return {String} Hexadecimal color */ - service.getRandomColor = function () { + acChartLogicProvider.getRandomColor = function () { var r = (Math.round(Math.random() * 127) + 127).toString(16); var g = (Math.round(Math.random() * 127) + 127).toString(16); var b = (Math.round(Math.random() * 127) + 127).toString(16); @@ -48,236 +183,403 @@ /** * Used to add chart functions by type * @access config - * @return {Object} this + * @return {Object} acChartLogicProvider */ - service.addChart = function (type, chartFunction, legendFunction){ + acChartLogicProvider.addChart = function (type, chartFunction, legendFunction){ if(!isInvokable(chartFunction)){ throw new Error('addChart expects parameter 2 to be function'); } if(legendFunction != null && !isInvokable(legendFunction)){ - console.log(legendFunction) throw new Error('addChart expects parameter 3 if set to be function'); } chartFunctions[type] = { chart: chartFunction, - legend: (legendFunction != null)? legendFunction : service.defaultLegend + legend: (legendFunction != null)? legendFunction : acChartLogicProvider.defaultLegend }; - return service; + return acChartLogicProvider; }; - /** - * Filters down the x axis labels if a limit is specified - * @access public - */ - service.filterXAxis = function (config, xAxis, x){ - var allTicks = x.domain(); - if (config.xAxisMaxTicks && allTicks.length > config.xAxisMaxTicks) { - var mod = Math.ceil(allTicks.length / config.xAxisMaxTicks); - xAxis.tickValues(allTicks.filter(function(e, i) { - return (i % mod) === 0; - })); - } - }; - - /** - * Rotates xAxis labels by config option - * @param {[selection]} - */ - service.rotateAxisLabels = function (config, selection){ - selection - .style("text-anchor", "end") - .attr('dx', '-.8em') - .attr('dy', '.15em') - .attr('transform', function (d){return "rotate(" + config.xAxisLabelRotation + ")"}); - } - /** * Checks if index is available in color * else returns a random color + * + * @access config * @param {[type]} i [description] * @return {[type]} [description] */ - service.getColor = function (config, i) { + acChartLogicProvider.getColor = function (config, i) { if (i < config.colors.length) { return config.colors[i]; } else { - var color = service.getRandomColor(); + var color = acChartLogicProvider.getRandomColor(); config.colors.push(color); return color; } } /** - * Default Legend + * Default Legend + * + * @access config */ - service.defaultLegend = function (config, box, series, points){ - var service = this; + acChartLogicProvider.defaultLegend = function (config, box, series, points){ + var acChartLogicProvider = this; angular.forEach(series, function(value, key) { box.legends.push({ color: config.colors[key], - title: service.getBindableTextForLegend(config, value) + title: acChartLogicProvider.getBindableTextForLegend(config, value) }); }); }; - service.defaultLegend['$inject'] = ['config', 'box', 'series', 'points']; - - + /** + * Make defaultLegend injectable + */ + acChartLogicProvider.defaultLegend['$inject'] = ['config', 'box', 'series', 'points']; - service.escapeHtml = function (string) { + /** + * Escapes html to safe characters + * + * @access config + * @param {String} string + * @return {String} + */ + acChartLogicProvider.escapeHtml = function (string) { return String(string).replace(/[&<>"'\/]/g, function(char) { return HTML_ENTITY_MAP[char]; }); } - service.getBindableTextForLegend = function (config, text) { - var returnText; - injector.invoke(['$sce', function($sce){ - returnText = $sce.trustAsHtml(config.legend.htmlEnabled ? text : service.escapeHtml(text)); - }], service); - return returnText; - } - /** - * Public API when injected into anything but the module config + * Gets text for legend label + * + * @access config + * @param {Object} config + * @param {String} text + * @return {String} */ - service.$get = ['$injector', function($injector){ - - //Provide later version of injector to service after all providers have been handled - injector = $injector; - - return { - /** - * Invokes chart function injecting service as this - * @access public - */ - callChartFunction: function (type, config, box, domFunctions, series, points){ - // == null will catch undefined while === will not - if(type == null || config == null || box == null || domFunctions == null || series == null || points == null) - throw new Error('Missing Parameter(s) expects (string type, object config, object box, object domFunctions, array series, array points)'); - - if(!chartFunctions.hasOwnProperty(type)) - throw new Error('Chart type "'+type+'" does not exist'); - - var localInjections = { - config: config, - box: box, - domFunctions: domFunctions, - series: series, - points: points - }; + acChartLogicProvider.getBindableTextForLegend = function (config, text) { + var $sce = injector.get('$sce'); - $injector.invoke(chartFunctions[type].chart, service, localInjections); + return $sce.trustAsHtml(config.legend.htmlEnabled ? text : acChartLogicProvider.escapeHtml(text)); + } - //Blank legends before calling function - box.legends = []; - $injector.invoke(chartFunctions[type].legend, service, localInjections); - } - }; - }]; + /** + * Binds domFunctions to events + * @access config + * @param {Object} config + * @param {Object} domFunctions + * @param {d3.selection} selection + * @param {object} tooltipConfig + */ + acChartLogicProvider.bindTooltipEvents = function (config, domFunctions, selection){ + selection.on("mouseover", function(d) { + domFunctions.makeToolTip({ + index: d.x, + value: d.tooltip ? d.tooltip : d.y, + series: d.series + }, d, d3.event); + }) + .on("mouseleave", function(d) { + domFunctions.removeToolTip(); + }) + .on("mousemove", function(d) { + domFunctions.updateToolTip(d, d3.event); + }) + .on("click", function(d) { + domFunctions.click(d, d3.event); + }); + }; - service.addChart('bar', ['config', 'box', 'domFunctions', 'series', 'points', function (config, box, domFunctions, series, points){ - var service = this; - /** - * Setup date attributes - * @type {Object} - */ - box.margin = { - top: 0, - right: 20, - bottom: 30, - left: 40 - }; + acChartLogicProvider.applyMargins = function (box){ box.width -= box.margin.left + box.margin.right; box.height -= box.margin.top + box.margin.bottom; + }; - var x = d3.scale.ordinal() - .rangeRoundBands([0, box.width], 0.1); - - var y = d3.scale.linear() - .range([box.height, 10]); - - var x0 = d3.scale.ordinal() - .rangeRoundBands([0, box.width], 0.1); + acChartLogicProvider.getYMaxPoints = function (points){ + return d3.max(points.map(function(d) { return d.y.length; })); + }; + /** + * Gets yData and sets nicedata + * + * @param {Array} points + * @param {Array} series - for series string in nicedata + * @return {Array} yData; + */ + acChartLogicProvider.getYData = function(points, series){ var yData = [0]; - points.forEach(function(d) { + points.forEach(function(d){ d.nicedata = d.y.map(function(e, i) { yData.push(e); - return { + var nicedata = { x: d.x, y: e, s: i, tooltip: angular.isArray(d.tooltip) ? d.tooltip[i] : d.tooltip }; + if(series != null){ + nicedata.series = series[i]; + } + return nicedata; }); }); - var yMaxPoints = d3.max(points.map(function(d) { - return d.y.length; - })); + return yData; + } + + /** + * Draw SVG element + * + * @param {Array} box + * @return {d3.selection} + */ + acChartLogicProvider.getSvg = function(box){ + return d3.select(box.chartContainer[0]).append("svg") + .attr("width", box.width + box.margin.left + box.margin.right) + .attr("height", box.height + box.margin.top + box.margin.bottom) + .append("g") + .attr("transform", "translate(" + box.margin.left + "," + box.margin.top + ")") + ; + } + + /** + * Get LineData + */ + acChartLogicProvider.getLineData = function(points, series){ + var lineData = []; + series.slice(0, this.getYMaxPoints(points)).forEach(function(value, index) { + var d = {}; + d.series = value; + d.values = points.map(function(point) { + + if(point.nicedata[index] != null) + return point.nicedata[index]; + else + return { + x: point.x, + y: 0 + }; + + }); + lineData.push(d); + }); + + return lineData; + } + + /** + * Returns x point of line point + * @param {[type]} d [description] + * @return {[type]} [description] + */ + acChartLogicProvider.getX = function (x, d) { + return Math.round(x(d)) + x.rangeBand() / 2; + } + + /** + * Filters down the x axis labels if a limit is specified + * + * @access config + */ + acChartLogicProvider.filterXAxis = function (config, xAxis, x){ + var allTicks = x.domain(); + if (config.xAxisMaxTicks && allTicks.length > config.xAxisMaxTicks) { + var mod = Math.ceil(allTicks.length / config.xAxisMaxTicks); + xAxis.tickValues(allTicks.filter(function(e, i) { + return (i % mod) === 0; + })); + } + }; - x.domain(points.map(function(d) { - return d.x; + /** + * Rotates xAxis labels by config option + * + * @access config + * @param {[selection]} + */ + acChartLogicProvider.rotateAxisLabels = function (config, box, y, xAxisSelection){ + if(config.xAxisLabelRotation == null || !config.xAxisLabelRotation) + return; + var rotation = (config.xAxisLabelRotation>0)? -1 * config.xAxisLabelRotation:config.xAxisLabelRotation; + //Rotate text by config degrees + xAxisSelection.selectAll('text') + .style("text-anchor", "end") + .attr('dx', '-.8em') + .attr('dy', '.15em') + .attr('transform', function (d){ + return "rotate(" + rotation + ")"; + }) + ; + //Recalculate chart height + box.height = (box.height + box.margin.bottom) - xAxisSelection.node().getBBox().height; + //Set new bottom margin value + box.margin.bottom = xAxisSelection.node().getBBox().height; + //Move x axis to new bottom of chart + xAxisSelection.attr("transform", "translate(0," + box.height + ")"); + //Redraw the yAxis scale to new height + y.range([box.height, 10]); + + return this; + } + /** + * Set stack data + * + * @param {Array} points + * @return this + */ + acChartLogicProvider.setStackData = function (points) { + points.forEach(function (d, i){ + var y0 = 0; + d.nicedata.forEach(function (nd, i) { + nd.y0 = y0; + nd.y1 = y0 += nd.y; + }); + d.total = d.nicedata[d.nicedata.length - 1].y1; + }); + + return acChartLogicProvider; + } + + /** + * Get Max stack value + * + * @param {Array} points + * @return {Number} + */ + acChartLogicProvider.getMaxStack = function (points){ + return d3.max(points.map(function (d){ + return d.total; })); - var padding = d3.max(yData) * 0.20; + } - y.domain([d3.min(yData), d3.max(yData) + padding]); + /** + * Build and return graph object + * + * @return {Object} + */ + acChartLogicProvider.getGraph = function (config, box, x, y, horizontal){ - x0.domain(d3.range(yMaxPoints)).rangeRoundBands([0, x.rangeBand()]); + var graph = {}; - /** - * Create scales using d3 - * @type {[type]} - */ - var xAxis = d3.svg.axis() - .scale(x) - .orient("bottom"); - service.filterXAxis(config, xAxis, x); + //Creates xAxis using scale var x + graph.xAxis = d3.svg.axis().scale(x).orient("bottom"); + + if(horizontal) + graph.xAxis.orient('left'); - var yAxis = d3.svg.axis() + //Creates yAxis using scale var y + graph.yAxis = d3.svg.axis() .scale(y) .orient("left") - .ticks(10) + .ticks(5) .tickFormat(d3.format(config.yAxisTickFormat)); - /** - * Start drawing the chart! - * @type {[type]} - */ - var svg = d3.select(box.chartContainer[0]).append("svg") - .attr("width", box.width + box.margin.left + box.margin.right) - .attr("height", box.height + box.margin.top + box.margin.bottom) - .append("g") - .attr("transform", "translate(" + box.margin.left + "," + box.margin.top + ")"); + if(horizontal) + graph.yAxis.orient('bottom'); + + //Limits xAxis label to config limit if set + acChartLogicProvider.filterXAxis(config, graph.xAxis, x); + + //Draws the SVG element + graph.svg = acChartLogicProvider.getSvg(box); - var xAxisSelection = svg.append("g") + //Draws the xAxis and saves the selection + graph.xAxisSelection = graph.svg.append("g") .attr("class", "x axis") - .attr("transform", "translate(0," + box.height + ")") - .call(xAxis); - - if(config.xAxisLabelRotation){ - service.rotateAxisLabels(config, xAxisSelection.selectAll("text")); //Call before getting measurements - box.height = (box.height + box.margin.bottom) - xAxisSelection.node().getBBox().height; - box.margin.bottom = xAxisSelection.node().getBBox().height; - xAxisSelection.attr("transform", "translate(0," + box.height + ")"); - y.range([box.height, 10]); - } + ; + if(!horizontal) + graph.xAxisSelection.attr("transform", "translate(0," + box.height + ")") + graph.xAxisSelection.call(graph.xAxis); + + //Rotates labels and adjusts box height + if(!horizontal) + acChartLogicProvider.rotateAxisLabels(config, box, y, graph.xAxisSelection); + + if(horizontal) + acChartLogicProvider.horizontalGraphMarginAdjust(config, box, y, graph.xAxisSelection); - svg.append("g") + //Draws the yAxis + graph.yAxisSelection = graph.svg.append("g") .attr("class", "y axis") - .call(yAxis); + ; + if(horizontal) + graph.yAxisSelection.attr("transform", "translate(0," + box.height + ")") + graph.yAxisSelection.call(graph.yAxis); + + return graph; + }; - /** - * Add bars - * @type {[type]} - */ - var barGroups = svg.selectAll(".state") + /** + * Rotates xAxis labels by config option + * + * @access config + * @param {[selection]} + */ + acChartLogicProvider.horizontalGraphMarginAdjust = function (config, box, y, xAxisSelection){ + + var newMargin = xAxisSelection.node().getBBox().width; + + //Recalculate chart height + box.width = (box.width + box.margin.left) - newMargin; + + box.margin.left = newMargin; + + //Move x axis to new bottom of chart + xAxisSelection.attr("transform", "translate(" + newMargin + ", 0)"); + //Redraw the yAxis scale to new height + y.range([newMargin, box.width]); + + return this; + } + + /** + * Bar Chart Definition + */ + acChartLogicProvider.addChart('bar', ['config', 'box', 'domFunctions', 'series', 'points', function (config, box, domFunctions, series, points){ + var acChartLogicProvider = this; + + acChartLogicProvider.applyMargins(box); + + // Sets all yData in a single array + var yData = acChartLogicProvider.getYData(points, series); + + //Calculates maximum data series count + var yMaxPoints = acChartLogicProvider.getYMaxPoints(points); + + //Creates the x Scale + var x = d3.scale.ordinal() + .domain(points.map(function (d) { + //returns xAxis label + return d.x; + })) + .rangeRoundBands([0, box.width], 0.1) + ; + + //Adds padding to top of Y axis + var padding = d3.max(yData) * 0.20; + + //Creates the y Scale + var y = d3.scale.linear() + .range([box.height, 10]) + .domain([d3.min(yData), d3.max(yData) + padding]) + ; + + // Creates the scale for series bars inside x Scale + var x0 = d3.scale.ordinal() + .domain(d3.range(yMaxPoints)) + .rangeRoundBands([0, x.rangeBand()]) + ; + + var graph = acChartLogicProvider.getGraph(config, box, x, y); + + //Add point data to x axis + var barGroups = graph.svg.selectAll(".state") .data(points) .enter().append("g") .attr("class", "g") @@ -285,22 +587,23 @@ return "translate(" + x(d.x) + ",0)"; }); + //Draws series data var bars = barGroups.selectAll("rect") .data(function(d) { return d.nicedata; }) - .enter().append("rect"); + .enter().append("rect") + .attr("width", x0.rangeBand())//Sets bar width based on series scale (x0) + .attr("x", function(d, i) { return x0(i); })//Sets x position based on series scale (x0) + .style("fill", function(d) { return acChartLogicProvider.getColor(config, d.s); })//Sets bar color + ; - bars.attr("width", x0.rangeBand()); - - bars.attr("x", function(d, i) { - return x0(i); - }) - .attr("y", box.height) - .style("fill", function(d) { - return service.getColor(config, d.s); - }) + /** + * Animate bar height from 0% to 100% + */ + bars .attr("height", 0) + .attr("y", box.height) .transition() .ease("cubic-in-out") .duration(config.isAnimate ? 1000 : 0) @@ -310,29 +613,11 @@ .attr("height", function(d) { return Math.abs(y(d.y) - y(0)); }); + /** * Add events for tooltip - * @param {[type]} d [description] - * @return {[type]} [description] */ - bars.on("mouseover", function(d) { - - domFunctions.makeToolTip({ - index: d.x, - value: d.tooltip ? d.tooltip : d.y, - series: series[d.s] - }, d, d3.event); - - }) - .on("mouseleave", function(d) { - domFunctions.removeToolTip(); - }) - .on("mousemove", function(d) { - domFunctions.updateToolTip(d, d3.event); - }) - .on("click", function(d) { - domFunctions.click(d, d3.event); - }); + acChartLogicProvider.bindTooltipEvents(config, domFunctions, bars); /** * Create labels @@ -347,9 +632,8 @@ return x0(i); }) .attr("y", function(d) { - return box.height - Math.abs(y(d.y) - y(0)); + return box.height - Math.abs(y(d.y) - y(0)) - 5; }) - // .attr("transform", "rotate(270)") .text(function(d) { return d.y; }); @@ -358,120 +642,170 @@ /** * Draw one zero line in case negative values exist */ - svg.append("line") + graph.svg.append("line") .attr("x1", box.width) .attr("y1", y(0)) .attr("y2", y(0)) .style("stroke", "silver"); + }]); - service.addChart('line', ['config', 'box', 'domFunctions', 'series', 'points', function (config, box, domFunctions, series, points) { - var service = this; - box.margin = { - top: 0, - right: 40, - bottom: 20, - left: 40 - }; - box.width -= box.margin.left + box.margin.right; - box.height -= box.margin.top + box.margin.bottom; + /** + * Bar Chart Definition + */ + acChartLogicProvider.addChart('bar-stacked', ['config', 'box', 'domFunctions', 'series', 'points', function (config, box, domFunctions, series, points){ + var acChartLogicProvider = this; + + acChartLogicProvider.applyMargins(box); + // Sets all yData in a single array + var yData = acChartLogicProvider.getYData(points, series); + //Calculates maximum data series count + var yMaxPoints = acChartLogicProvider.getYMaxPoints(points); + + acChartLogicProvider.setStackData(points); + var maxStacked = acChartLogicProvider.getMaxStack(points); + + //Creates the x Scale var x = d3.scale.ordinal() - .domain(points.map(function(d) { + .domain(points.map(function (d) { + //returns xAxis label return d.x; })) - .rangeRoundBands([0, box.width]); + .rangeRoundBands([0, box.width], 0.1) + ; + //Adds padding to top of Y axis + var padding = maxStacked * 0.20; + + //Creates the y Scale var y = d3.scale.linear() - .range([box.height, 10]); + .range([box.height, 10]) + .domain([d3.min(yData), maxStacked + padding]) + ; - var xAxis = d3.svg.axis() - .scale(x) - .orient("bottom"); - service.filterXAxis(config, xAxis, x); + var graph = acChartLogicProvider.getGraph(config, box, x, y); - var yAxis = d3.svg.axis() - .scale(y) - .orient("left") - .ticks(5) - .tickFormat(d3.format(config.yAxisTickFormat)); + //Add point data to x axis + var barGroups = graph.svg.selectAll(".state") + .data(points) + .enter().append("g") + .attr("class", "g") + .attr("transform", function(d) { + return "translate(" + x(d.x) + ",0)"; + }); - var line = d3.svg.line() - .interpolate(config.lineCurveType) - .x(function(d) { - return getX(d.x); + //Draws series data + var bars = barGroups.selectAll("rect") + .data(function(d) { + return d.nicedata; }) - .y(function(d) { - return y(d.y); - }); + .enter().append("rect") + .attr("width", x.rangeBand())//Sets bar width based on series scale (x0) + .attr("x", function(d, i) { return x(i); })//Sets x position based on series scale (x0) + .style("fill", function(d) { return acChartLogicProvider.getColor(config, d.s); })//Sets bar color + ; - var yData = [0]; - var linedata = []; + /** + * Animate bar height from 0% to 100% + */ + bars + .attr("height", 0) + .attr("y", box.height) + .transition() + .ease("cubic-in-out") + .duration(config.isAnimate ? 1000 : 0) + .attr("y", function(d, i) { return y(d.y1); }) + .attr("height", function(d) { return y(d.y0) - y(d.y1); }); - points.forEach(function(d) { - d.y.map(function(e) { - yData.push(e); - }); - }); + /** + * Add events for tooltip + */ + acChartLogicProvider.bindTooltipEvents(config, domFunctions, bars); - var yMaxPoints = d3.max(points.map(function(d) { - return d.y.length; - })); + /** + * Create labels + */ + if (config.labels) { + barGroups.selectAll('not-a-class') + .data(function(d) { + return d.nicedata.filter(function (nd){ return nd.y > 0; }); + }) + .enter().append("text") + .text(function(d) { + return d.y; + }) + .each(function(d,i){ + var width = this.getBBox().width; + var height = this.getBBox().height; + if(height > ( y(d.y1) - y(d.y0) )*-1 ){ + d3.select(this).remove(); + return; + } + d3.select(this) + .attr("x", function(d, i) { + return ( x.rangeBand(i)/2 ) - (width/2); + }) + .attr("y", function(d) { + return y(d.y1) + height; + }) + ; + }) + ; + } - series.slice(0, yMaxPoints).forEach(function(value, index) { - var d = {}; - d.series = value; - d.values = points.map(function(point) { - return point.y.map(function(e) { - return { - x: point.x, - y: e, - tooltip: point.tooltip - }; - })[index] || { - x: points[index].x, - y: 0 - }; - }); - linedata.push(d); - }); + /** + * Draw one zero line in case negative values exist + */ + graph.svg.append("line") + .attr("x1", box.width) + .attr("y1", y(0)) + .attr("y2", y(0)) + .style("stroke", "silver"); - var svg = d3.select(box.chartContainer[0]).append("svg") - .attr("width", box.width + box.margin.left + box.margin.right) - .attr("height", box.height + box.margin.top + box.margin.bottom) - .append("g") - .attr("transform", "translate(" + box.margin.left + "," + box.margin.top + ")"); + }]); + + /** + * Line Chart Definition + * Has Legend override + */ + acChartLogicProvider.addChart('line', ['config', 'box', 'domFunctions', 'series', 'points', function (config, box, domFunctions, series, points) { + var acChartLogicProvider = this; + + acChartLogicProvider.applyMargins(box); + var yData = acChartLogicProvider.getYData(points, series); + var yMaxPoints = acChartLogicProvider.getYMaxPoints(points); + var linedata = acChartLogicProvider.getLineData(points, series); var padding = d3.max(yData) * 0.20; - y.domain([d3.min(yData), d3.max(yData) + padding]); + var x = d3.scale.ordinal() + .domain(points.map(function(d) { return d.x; })) + .rangeRoundBands([0, box.width]) + ; - var xAxisSelection = svg.append("g") - .attr("class", "x axis") - .attr("transform", "translate(0," + box.height + ")") - .call(xAxis); - - if(config.xAxisLabelRotation){ - service.rotateAxisLabels(config, xAxisSelection.selectAll("text")); //Call before getting measurements - box.height = (box.height + box.margin.bottom) - xAxisSelection.node().getBBox().height; - box.margin.bottom = xAxisSelection.node().getBBox().height; - xAxisSelection.attr("transform", "translate(0," + box.height + ")"); - y.range([box.height, 10]); - } + var y = d3.scale.linear() + .range([box.height, 10]) + .domain([d3.min(yData), d3.max(yData) + padding]) + ; - svg.append("g") - .attr("class", "y axis") - .call(yAxis); + var graph = acChartLogicProvider.getGraph(config, box, x, y); + + var line = d3.svg.line() + .interpolate(config.lineCurveType) + .x(function(d) { return acChartLogicProvider.getX(x, d.x); }) + .y(function(d) { return y(d.y); }) + ; - var point = svg.selectAll(".points") + var point = graph.svg.selectAll(".points") .data(linedata) .enter().append("g"); - var path = point.attr("points", "points") + var path = point.attr("class", "path") .append("path") .attr("class", "ac-line") .style("stroke", function(d, i) { - return service.getColor(config, i); + return acChartLogicProvider.getColor(config, i); }) .attr("d", function(d) { return line(d.values); @@ -506,52 +840,32 @@ * @return {[type]} [description] */ angular.forEach(linedata, function(value, key) { - var points = svg.selectAll('.circle') - .data(value.values) - .enter(); - - points.append("circle") - .attr("cx", function(d) { - return getX(d.x); + var points = graph.svg.selectAll('.circle').data(value.values).enter().append('g').attr('class', 'point'); + var dots = points.append('circle').attr("cx", function(d) { + return acChartLogicProvider.getX(x, d.x); }) .attr("cy", function(d) { return y(d.y); }) .attr("r", 3) - .style("fill", service.getColor(config, linedata.indexOf(value))) - .style("stroke", service.getColor(config, linedata.indexOf(value))) - .on("mouseover", (function(series) { - return function(d) { + .style("fill", acChartLogicProvider.getColor(config, linedata.indexOf(value))) + .style("stroke", acChartLogicProvider.getColor(config, linedata.indexOf(value))) + ; - domFunctions.makeToolTip({ - index: d.x, - value: d.tooltip ? d.tooltip : d.y, - series: series - }, d, d3.event); - - }; - })(value.series)) - .on("mouseleave", function(d) { - domFunctions.removeToolTip(d, d3.event); - }) - .on("mousemove", function(d) { - domFunctions.updateToolTip(d, d3.event); - }) - .on("click", function(d) { - domFunctions.click(d, d3.event); - }); + acChartLogicProvider.bindTooltipEvents(config, domFunctions, points); if (config.labels) { points.append("text") + .text(function(d) { + return d.y; + }) .attr("x", function(d) { - return getX(d.x); + return acChartLogicProvider.getX(x, d.x) - (this.getBBox().width + box.width*0.01); }) .attr("y", function(d) { return y(d.y); }) - .text(function(d) { - return d.y; - }); + ; } }); @@ -568,23 +882,13 @@ }; }) .attr("transform", function(d) { - return "translate(" + getX(d.value.x) + "," + y(d.value.y) + ")"; + return "translate(" + acChartLogicProvider.getX(x, d.value.x) + "," + y(d.value.y) + ")"; }) .attr("x", 3) .text(function(d) { return d.name; }); } - - /** - * Returns x point of line point - * @param {[type]} d [description] - * @return {[type]} [description] - */ - function getX(d) { - return Math.round(x(d)) + x.rangeBand() / 2; - } - return linedata; }], ['config', 'box', 'series', 'points', function (config, box, series, points){ if(config.lineLegend == "traditional"){ @@ -592,111 +896,47 @@ } }]); - service.addChart('area', ['config', 'box', 'domFunctions', 'series', 'points', function (config, box, domFunctions, series, points){ - var service = this; - box.margin = { - top: 0, - right: 40, - bottom: 20, - left: 40 - }; - box.width -= box.margin.left + box.margin.right; - box.height -= box.margin.top + box.margin.bottom; + /** + * Area Chart Definition + */ + acChartLogicProvider.addChart('area', ['config', 'box', 'domFunctions', 'series', 'points', function (config, box, domFunctions, series, points){ + var acChartLogicProvider = this; + + acChartLogicProvider.applyMargins(box); + + var yData = acChartLogicProvider.getYData(points, series); + var yMaxPoints = acChartLogicProvider.getYMaxPoints(points); + var linedata = acChartLogicProvider.getLineData(points, series); + var padding = d3.max(yData) * 0.20; var x = d3.scale.ordinal() .domain(points.map(function(d) { return d.x; })) - .rangePoints([0, box.width]); + .rangePoints([0, box.width]) + ; var y = d3.scale.linear() - .range([box.height, 10]); + .range([box.height, 10]) + .domain([d3.min(yData), d3.max(yData) + padding]) + ; - var xAxis = d3.svg.axis() - .scale(x) - .orient("bottom"); - service.filterXAxis(config, xAxis, x); - - var yAxis = d3.svg.axis() - .scale(y) - .orient("left") - .ticks(5) - .tickFormat(d3.format(config.yAxisTickFormat)); + var graph = acChartLogicProvider.getGraph(config, box, x, y); d3.svg.line() .interpolate(config.lineCurveType) - .x(function(d) { - return getX(d.x); - }) - .y(function(d) { - return y(d.y); - }); - - var yData = [0]; - var linedata = []; - - points.forEach(function(d) { - d.y.map(function(e) { - yData.push(e); - }); - }); - - var yMaxPoints = d3.max(points.map(function(d) { - return d.y.length; - })); - - series.slice(0, yMaxPoints).forEach(function(value, index) { - var d = {}; - d.series = value; - d.values = points.map(function(point) { - return point.y.map(function(e) { - return { - x: point.x, - y: e - }; - })[index] || { - x: points[index].x, - y: 0 - }; - }); - linedata.push(d); - }); - - var svg = d3.select(box.chartContainer[0]).append("svg") - .attr("width", box.width + box.margin.left + box.margin.right) - .attr("height", box.height + box.margin.top + box.margin.bottom) - .append("g") - .attr("transform", "translate(" + box.margin.left + "," + box.margin.top + ")"); - - var padding = d3.max(yData) * 0.20; - - y.domain([d3.min(yData), d3.max(yData) + padding]); - - var xAxisSelection = svg.append("g") - .attr("class", "x axis") - .attr("transform", "translate(0," + box.height + ")") - .call(xAxis); - - if(config.xAxisLabelRotation){ - service.rotateAxisLabels(config, xAxisSelection.selectAll("text")); //Call before getting measurements - box.height = (box.height + box.margin.bottom) - xAxisSelection.node().getBBox().height; - box.margin.bottom = xAxisSelection.node().getBBox().height; - xAxisSelection.attr("transform", "translate(0," + box.height + ")"); - y.range([box.height, 10]); - } + .x(function(d) { return acChartLogicProvider.getX(x, d.x); }) + .y(function(d) { return y(d.y); }) + ; - svg.append("g") - .attr("class", "y axis") - .call(yAxis); - - var point = svg.selectAll(".points") + var point = graph.svg.selectAll(".points") .data(linedata) .enter().append("g"); var area = d3.svg.area() .interpolate('basis') .x(function(d) { - return getX(d.x); + return acChartLogicProvider.getX(x, d.x); }) .y0(function() { return y(0); @@ -711,104 +951,39 @@ return area(d.values); }) .style("fill", function(d, i) { - return service.getColor(config, i); + return acChartLogicProvider.getColor(config, i); }) .style("opacity", "0.7"); - - function getX(d) { - return Math.round(x(d)) + x.rangeBand() / 2; - } }]); + + /** + * Point Chart Definition + */ + acChartLogicProvider.addChart('point', ['config', 'box', 'domFunctions', 'series', 'points', function (config, box, domFunctions, series, points){ + var acChartLogicProvider = this; + + acChartLogicProvider.applyMargins(box); - service.addChart('point', ['config', 'box', 'domFunctions', 'series', 'points', function (config, box, domFunctions, series, points){ - var service = this; - box.margin = { - top: 0, - right: 40, - bottom: 20, - left: 40 - }; - box.width -= box.margin.left - box.margin.right; - box.height -= box.margin.top - box.margin.bottom; + var yData = acChartLogicProvider.getYData(points, series); + var yMaxPoints = acChartLogicProvider.getYMaxPoints(points); + var linedata = acChartLogicProvider.getLineData(points, series); + var padding = d3.max(yData) * 0.20; var x = d3.scale.ordinal() .domain(points.map(function(d) { return d.x; })) - .rangeRoundBands([0, box.width]); + .rangeRoundBands([0, box.width]) + ; var y = d3.scale.linear() - .range([box.height, 10]); - - var xAxis = d3.svg.axis() - .scale(x) - .orient("bottom"); - service.filterXAxis(config, xAxis, x); - - var yAxis = d3.svg.axis() - .scale(y) - .orient("left") - .ticks(5) - .tickFormat(d3.format(config.yAxisTickFormat)); - - var yData = [0]; - var linedata = []; - - points.forEach(function(d) { - d.y.map(function(e, i) { - yData.push(e); - }); - }); - - var yMaxPoints = d3.max(points.map(function(d) { - return d.y.length; - })); - - series.slice(0, yMaxPoints).forEach(function(value, index) { - var d = {}; - d.series = value; - d.values = points.map(function(point) { - return point.y.map(function(e) { - return { - x: point.x, - y: e - }; - })[index] || { - x: points[index].x, - y: 0 - }; - }); - linedata.push(d); - }); - - var svg = d3.select(box.chartContainer[0]).append("svg") - .attr("width", box.width + box.margin.left + box.margin.right) - .attr("height", box.height + box.margin.top + box.margin.bottom) - .append("g") - .attr("transform", "translate(" + box.margin.left + "," + box.margin.top + ")"); - - var padding = d3.max(yData) * 0.20; - - y.domain([d3.min(yData), d3.max(yData) + padding]); + .range([box.height, 10]) + .domain([d3.min(yData), d3.max(yData) + padding]) + ; - var xAxisSelection = svg.append("g") - .attr("class", "x axis") - .attr("transform", "translate(0," + box.height + ")") - .call(xAxis); - - if(config.xAxisLabelRotation){ - service.rotateAxisLabels(config, xAxisSelection.selectAll("text")); //Call before getting measurements - box.height = (box.height + box.margin.bottom) - xAxisSelection.node().getBBox().height; - box.margin.bottom = xAxisSelection.node().getBBox().height; - xAxisSelection.attr("transform", "translate(0," + box.height + ")"); - y.range([box.height, 10]); - } - - svg.append("g") - .attr("class", "y axis") - .call(yAxis); + var graph = acChartLogicProvider.getGraph(config, box, x, y); - svg.selectAll(".points") + graph.svg.selectAll(".points") .data(linedata) .enter().append("g"); @@ -819,74 +994,51 @@ * @return {[type]} [description] */ angular.forEach(linedata, function(value, key) { - var points = svg.selectAll('.circle') - .data(value.values) - .enter(); - - points.append("circle") + var points = graph.svg.selectAll('.circle').data(value.values).enter().append('g').attr('class', 'point'); + var dots = points + .append("circle") .attr("cx", function(d) { - return getX(d.x); + return acChartLogicProvider.getX(x, d.x); }) .attr("cy", function(d) { return y(d.y); }) .attr("r", 3) - .style("fill", service.getColor(config, linedata.indexOf(value))) - .style("stroke", service.getColor(config, linedata.indexOf(value))) - .on("mouseover", (function(series) { - return function(d) { - - domFunctions.makeToolTip({ - index: d.x, - value: d.tooltip ? d.tooltip : d.y, - series: series - }, d, d3.event); + .style("fill", acChartLogicProvider.getColor(config, linedata.indexOf(value))) + .style("stroke", acChartLogicProvider.getColor(config, linedata.indexOf(value))) + ; - }; - })(value.series)) - .on("mouseleave", function(d) { - domFunctions.removeToolTip(d, d3.event); - }) - .on("mousemove", function(d) { - domFunctions.updateToolTip(d, d3.event); - }) - .on("click", function(d) { - domFunctions.click(d, d3.event); - }); + acChartLogicProvider.bindTooltipEvents(config, domFunctions, points); if (config.labels) { points.append("text") + .text(function(d) { + return d.y; + }) .attr("x", function(d) { - return getX(d.x); + return acChartLogicProvider.getX(x, d.x) - (this.getBBox().width + box.width*0.01); }) .attr("y", function(d) { return y(d.y); }) - .text(function(d) { - return d.y; - }); + ; } }); - - /** - * Returns x point of line point - * @param {[type]} d [description] - * @return {[type]} [description] - */ - function getX(d) { - return Math.round(x(d)) + x.rangeBand() / 2; - } }]); - service.addChart('pie', ['config', 'box', 'domFunctions', 'series', 'points', function (config, box, domFunctions, series, points){ - var service = this; + /** + * Pie Chart Definition + * Has Legend override + */ + acChartLogicProvider.addChart('pie', ['config', 'box', 'domFunctions', 'series', 'points', function (config, box, domFunctions, series, points){ + var acChartLogicProvider = this; var radius = Math.min(box.width, box.height) / 2; - var svg = d3.select(box.chartContainer[0]).append("svg") - .attr("width", box.width) - .attr("height", box.height) - .append("g") - .attr("transform", "translate(" + box.width / 2 + "," + box.height / 2 + ")"); + + var svg = acChartLogicProvider.getSvg(box) + .attr("transform", "translate(" + box.width / 2 + "," + box.height / 2 + ")") //Override margin translate in getSvg() + ; + var innerRadius = 0; if (config.innerRadius) { @@ -919,6 +1071,12 @@ var path = svg.selectAll(".arc") .data(pie(points).filter(function (d){ return d.value > 0; + }).map(function(d){ + // pie() puts existing data into data property, events need this + d.x = d.data.x; + d.y = d.data.y[0]; + d.tooltip = d.data.tooltip; + return d; })) .enter().append("g"); @@ -926,7 +1084,7 @@ path.append("path") .style("fill", function(d, i) { - return service.getColor(config, i); + return acChartLogicProvider.getColor(config, i); }) .transition() .ease("linear") @@ -937,38 +1095,7 @@ //avoid firing multiple times if (!complete) { complete = true; - - //Add listeners when transition is done - path.on("mouseover", function(d) { - - domFunctions.makeToolTip({ - value: d.data.tooltip ? d.data.tooltip : d.data.y[0] - }, d, d3.event); - - d3.select(this) - .select('path') - .transition() - .duration(200) - .style("stroke", "white") - .style("stroke-width", "2px"); - }) - .on("mouseleave", function(d) { - d3.select(this) - .select('path') - .transition() - .duration(200) - .style("stroke", "") - .style("stroke-width", ""); - - domFunctions.removeToolTip(d, d3.event); - }) - .on("mousemove", function(d) { - domFunctions.updateToolTip(d, d3.event); - }) - .on("click", function(d) { - domFunctions.click(d, d3.event); - }); - + acChartLogicProvider.bindTooltipEvents(config, domFunctions, path); } }); @@ -995,20 +1122,225 @@ }; } }], ['config', 'box', 'series', 'points', function (config, box, series, points){ - var service = this; + var acChartLogicProvider = this; var filteredPoints = points.filter(function (d){return d.y[0] > 0;}); angular.forEach(filteredPoints, function(value, key) { box.legends.push({ color: config.colors[key], - title: service.getBindableTextForLegend(config, value.x) + title: acChartLogicProvider.getBindableTextForLegend(config, value.x) }); }); box.yMaxData = filteredPoints.length; }]); - return service; + /** + * Slab Chart Definition + */ + acChartLogicProvider.addChart('slab', ['config', 'box', 'domFunctions', 'series', 'points', function (config, box, domFunctions, series, points){ + var acChartLogicProvider = this; + + acChartLogicProvider.applyMargins(box); + + + var yData = acChartLogicProvider.getYData(points, series); + var yMaxPoints = acChartLogicProvider.getYMaxPoints(points); + var padding = d3.max(yData) * 0.20; + + var x = d3.scale.ordinal() + .domain(points.map(function (d) { + return d.x; + })) + .rangeRoundBands([0, box.height], 0.1) + + var y = d3.scale.linear() + .range([0, box.width]) + .domain([d3.min(yData), d3.max(yData) + padding]) + ; + + var graph = acChartLogicProvider.getGraph(config, box, x, y, true); + + var x0 = d3.scale.ordinal() + .domain(d3.range(yMaxPoints)) + .rangeRoundBands([0, x.rangeBand()]) + ; + + var barGroups = graph.svg.selectAll(".state") + .data(points) + .enter().append("g") + .attr("class", "g") + .attr("transform", function(d) { + return "translate(0, " + x(d.x) + ")"; + }); + + var bars = barGroups.selectAll("rect") + .data(function(d) { + return d.nicedata; + }) + .enter().append("rect") + .attr("height", x0.rangeBand())//Sets bar width based on series scale (x0) + .attr("y", function(d, i) { return x0(i); })//Sets x position based on series scale (x0) + .style("fill", function(d) { return acChartLogicProvider.getColor(config, d.s); })//Sets bar color + ; + + /** + * Animate bar height from 0% to 100% + */ + bars + .attr("width", 0) + .attr("x", y(0)) + .transition() + .ease("cubic-in-out") + .duration(config.isAnimate ? 1000 : 0) + .attr("width", function(d) { + return Math.abs(y(d.y) - y(0)); + }); + + /** + * Add events for tooltip + */ + acChartLogicProvider.bindTooltipEvents(config, domFunctions, bars); + + /** + * Create labels + */ + if (config.labels) { + barGroups.selectAll('not-a-class') + .data(function(d) { + return d.nicedata; + }) + .enter().append("text") + .attr("y", function(d, i) { + return x0.rangeBand() + x0(i); + }) + .attr("x", function(d) { + return y(d.y); + }) + .text(function(d) { + return d.y; + }); + } + + /** + * Draw one zero line in case negative values exist + */ + graph.svg.append("line") + .attr("y1", box.height) + .attr("x1", y(0)) + .attr("x2", y(0)) + .style("stroke", "silver"); + }]); + + /** + * Slab-Stacked Chart Definition + */ + acChartLogicProvider.addChart('slab-stacked', ['config', 'box', 'domFunctions', 'series', 'points', function (config, box, domFunctions, series, points){ + var acChartLogicProvider = this; + + acChartLogicProvider.applyMargins(box); + + + var yData = acChartLogicProvider.getYData(points, series); + var yMaxPoints = acChartLogicProvider.getYMaxPoints(points); + + + acChartLogicProvider.setStackData(points); + var maxStacked = acChartLogicProvider.getMaxStack(points); + + var padding = maxStacked * 0.20; + + var x = d3.scale.ordinal() + .domain(points.map(function (d) { + return d.x; + })) + .rangeRoundBands([0, box.height], 0.1) + + var y = d3.scale.linear() + .range([0, box.width]) + .domain([d3.min(yData), maxStacked + padding]) + ; + + var graph = acChartLogicProvider.getGraph(config, box, x, y, true); + + var barGroups = graph.svg.selectAll(".state") + .data(points) + .enter().append("g") + .attr("class", "g") + .attr("transform", function(d) { + return "translate(0, " + x(d.x) + ")"; + }); + + var bars = barGroups.selectAll("rect") + .data(function(d) { + return d.nicedata; + }) + .enter().append("rect") + .attr("height", x.rangeBand())//Sets bar width based on series scale (x0) + .attr("y", function(d, i) { return x(i); })//Sets x position based on series scale (x0) + .style("fill", function(d) { return acChartLogicProvider.getColor(config, d.s); })//Sets bar color + ; + + /** + * Animate bar height from 0% to 100% + */ + bars + .attr("width", 0) + .attr("x", y(0)) + .transition() + .ease("cubic-in-out") + .duration(config.isAnimate ? 1000 : 0) + .attr("x", function (d){ return y(d.y0); }) + .attr("width", function(d) { return y(d.y1) - y(d.y0); }) + ; + + /** + * Add events for tooltip + */ + acChartLogicProvider.bindTooltipEvents(config, domFunctions, bars); + + /** + * Create labels + */ + if (config.labels) { + barGroups.selectAll('not-a-class') + .data(function(d) { + return d.nicedata.filter(function (nd){ return nd.y > 0; }); + }) + .enter().append("text") + .text(function(d) { + return d.y; + }) + .each(function (d, i){ + var width = this.getBBox().width; + var height = this.getBBox().height; + if(width > y(d.y1) - y(d.y0)){ + d3.select(this).remove(); + return; + } + d3.select(this) + .attr("x", function(d, i) { + return y(d.y1) - width; + }) + .attr("y", function(d) { + return (x.rangeBand() / 2) + height / 2; + }); + }) + ; + } + + /** + * Draw one zero line in case negative values exist + */ + graph.svg.append("line") + .attr("y1", box.height) + .attr("x1", y(0)) + .attr("x2", y(0)) + .style("stroke", "silver"); + }]); + + + return acChartLogicProvider; }); \ No newline at end of file diff --git a/test/angular-charts-provider.js b/test/angular-charts-provider.js index e8703d3..bd0da5c 100644 --- a/test/angular-charts-provider.js +++ b/test/angular-charts-provider.js @@ -123,5 +123,21 @@ describe('angularCharts', function (){ expect(service).toEqual(chartsProvider); }); + it('should get default color list', function (){ + expect(chartLogic.getDefaultColors().length).toBeGreaterThan(0); + }); + + it('should set default color list', function (){ + var list = [ + 1, + 2 + ]; + chartsProvider.setDefaultColors(list); + expect(chartLogic.getDefaultColors()).toEqual(list); + }); + + it('should get an exception for list not being array', function (){ + expect(function (){chartsProvider.setDefaultColors(1)}).toThrow(new Error('setDefaultColors expects an array')); + }); }); \ No newline at end of file diff --git a/test/angular-charts.js b/test/angular-charts.js index e3c246f..d97fc31 100644 --- a/test/angular-charts.js +++ b/test/angular-charts.js @@ -72,13 +72,15 @@ describe('angularCharts', function() { return $compile(body)($scope) } + var sizeError = new Error('Please set height and width for the chart element'); + it('should throw width/height error', function() { - expect(compileChart).toThrow() + expect(compileChart).toThrow(sizeError) }) it('should throw width/height error', function() { angular.element(document.body).append('') - expect(compileChart).not.toThrow() + expect(compileChart).not.toThrow(sizeError) }) it('should digest scope', function() { @@ -133,6 +135,20 @@ describe('angularCharts', function() { }) + describe('bar-stacked', function() { + + it('should change chartType to bar-stacked', function() { + $scope.chartType = 'bar-stacked'; + compileChart(); + $scope.$digest(); + }) + + it('should have the same amount of graphic items as there are datas', function() { + expect(d3.selectAll('.ac-chart svg > g > g.g').size()).toEqual($scope.data.data.length) + }) + + }) + describe('lines', function() { it('should change chartType to line', function() { @@ -142,11 +158,11 @@ describe('angularCharts', function() { }) it('should have the same amount of graphic items as there are series', function() { - expect(d3.selectAll('.ac-chart svg > g > g:not(.axis)').size()).toEqual($scope.data.series.length) + expect(d3.selectAll('.ac-chart svg > g > g.path').size()).toEqual($scope.data.series.length) }) it('should have the same amount of circles as there are datas points', function() { - expect(d3.selectAll('.ac-chart svg > g > circle').size()).toEqual(numberOfPoints) + expect(d3.selectAll('.ac-chart svg > g > g.point').size()).toEqual(numberOfPoints) }) }) @@ -160,7 +176,7 @@ describe('angularCharts', function() { }) it('should have the same amount of circles as there are datas points', function() { - expect(d3.selectAll('.ac-chart svg > g > circle').size()).toEqual(numberOfPoints) + expect(d3.selectAll('.ac-chart svg > g > g.point').size()).toEqual(numberOfPoints) }) }) @@ -191,6 +207,34 @@ describe('angularCharts', function() { }) }) + describe('slab', function() { + + it('should change chartType to slab', function() { + $scope.chartType = 'slab'; + compileChart(); + $scope.$digest(); + }) + + it('should have the same amount of graphic items as there are datas', function() { + expect(d3.selectAll('.ac-chart svg > g > g.g').size()).toEqual($scope.data.data.length) + }) + + }) + + describe('slab-stacked', function() { + + it('should change chartType to slab-stacked', function() { + $scope.chartType = 'slab-stacked'; + compileChart(); + $scope.$digest(); + }) + + it('should have the same amount of graphic items as there are datas', function() { + expect(d3.selectAll('.ac-chart svg > g > g.g').size()).toEqual($scope.data.data.length) + }) + + }) + describe('styles', function() { it('should add styles to the document', function() {