diff --git a/Gruntfile.js b/Gruntfile.js index 96382f0..1a2db5d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -38,6 +38,15 @@ module.exports = function (grunt) { } }, + 'html-prettyprinter-dir': { + expansion: { + src: [ + 'examples/*.html' + ], + dest: 'examples/' + } + }, + jshint: { options: { curly: true, @@ -48,14 +57,17 @@ module.exports = function (grunt) { noarg: true, sub: true, undef: true, - unused: true, + unused: false, //! boss: true, eqnull: true, browser: true, node: true, jquery: true, globals: { - d3: true + d3: true, + define: true, + require: true, + BootstrapD3: true } }, files: [ @@ -115,10 +127,12 @@ module.exports = function (grunt) { grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-jsbeautifier'); + grunt.loadNpmTasks('grunt-html-prettyprinter'); // Default task. grunt.registerTask('default', [ 'jsbeautifier', + 'html-prettyprinter-dir', 'jshint', //'qunit', 'concat', diff --git a/README.md b/README.md index 9c71c3b..5ac163a 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,16 @@ Integrating d3js with twitter.bootstrap. ### Running the examples on Windows -Make sure node.js is installed (we use [Chocolatey][1] a lot): +Make sure [node.js][2] is installed (we use [Chocolatey][1] a lot): cinst nodejs.install -Note that this package (in contrary to `nodejs`) includes NPM. +Note that this package (in contrary to `nodejs`) includes [NPM][5]. -Install grunt and bower: + +### Installing Development Tools + +Install [Grunt][3] and [Bower][4]: npm install -g grunt-cli npm install -g bower @@ -19,22 +22,31 @@ Install grunt and bower: Form the project root, run: npm install + bower install grunt -The first command will install all dependencies for this project, -the second will run the default tasks in Gruntfile.js. -This all should run without errros +The first command and second commands will install all dependencies for this project. The third will execute the default build task. This all should run without errors -Next install this projects dependencies: - - How should this be done? I'm missing jQuery and d3js. Running bower does nothing. +### Running The Local Server -Then, from the root of the project run: +Then, from the root of the project run the provided local server: node app.js and browse to http://localhost:8888/examples/pie-chart.html +### Development Mode + +The grunt file provided also has a development mode, which is set to beautify the JS as well as run JSHint on it on every save. To do this, at the root of the project, run the following: + + grunt develop + +Keep the terminal window open. Upon save, messages from the executed task will appear on that window. + [1]: http:///chocolatey.org + [2]: http://nodejs.org/ + [3]: http://gruntjs.com/ + [4]: http://bower.io/ + [5]: https://npmjs.org/ \ No newline at end of file diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..2e984cf --- /dev/null +++ b/bower.json @@ -0,0 +1,11 @@ +{ + "name": "bootstrap-d3", + "version": "0.0.0", + "dependencies": { + "d3": "3.1.5", + "jquery": "~2.0.2", + "d3-tip": "~0.5.2", + "bootstrap": "~2.3.2", + "tipsy": "~1.0.0a" + } +} \ No newline at end of file diff --git a/dist/bootstrap-d3.js b/dist/bootstrap-d3.js index 2171bf8..bb75ebb 100644 --- a/dist/bootstrap-d3.js +++ b/dist/bootstrap-d3.js @@ -1,6 +1,6 @@ /* bootstrap-d3 v0.0.0 -2013-06-21 +2013-07-08 https://github.com/openkas/bootstrap-d3 Contributors: @@ -9,127 +9,901 @@ Marijn van der Zee http://serraict.com License: BSD */ +(function (window, BootstrapD3, $, d3) { + 'use strict'; + //Utility functions common through all modules + var utils = {}; -//Placed this up here since the JSHint doesn't seem to pick up the one defined -//in the IIFE below. -var BootStrapD3; + utils.shadeColor = function (color, percent) { + var num = parseInt(color, 16), + amt = Math.round(2.55 * percent), + R = (num >> 16) + amt, + B = (num >> 8 & 0x00FF) + amt, + G = (num & 0x0000FF) + amt; + return(0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 + (B < 255 ? B < 1 ? 0 : B : 255) * 0x100 + (G < 255 ? G < 1 ? 0 : G : 255)) + .toString(16) + .slice(1); + }; -(function (exports, $, d3) { + //Module adder + //Nothing special. Just wanted a private scope on the module + //as well as an "official" way to ad module and provide it tools. + BootstrapD3.module = function (name, definition) { + BootstrapD3[name] = definition.call(null, $, d3, utils); + }; - //Utility functions common through all modules - var utils = {}; + //Cross-module patcher + if(typeof module === "object" && module && typeof module.exports === "object") { + module.exports = BootstrapD3; + } else if(typeof define === "function" && define.amd) { + + //This script requires the following properly shimmed by AMD loaders so that + //they load before this plugin + define("bootstrapd3", [ + 'jquery', + 'd3', + 'bootstrap' + ], function () { + return BootstrapD3; + }); + } + + if(typeof window === "object" && typeof window.document === "object") { + window.BootstrapD3 = BootstrapD3; + } + +}(this, {}, jQuery, d3)); + +BootstrapD3.module('MultiSeriesLineChart', function ($, d3, utils) { + 'use strict'; - //Bootstrap base colors - utils.baseColors = [ - '#049cdb', - '#0064cd', - '#46a546', - '#9d261d', - '#ffc40d', - '#f89406', - '#c3325f', - '#7a43b6' - ]; - - exports.module = function (name, definition) { - - //Create an object which the module will attach exported stuff to - var moduleExport = exports[name] = {}; - - //Execute the definition to create module - definition.call(null, moduleExport, $, d3, utils); + //Default configuration + var defaultConfig = { + colors: [ + '#049cdb', + //'#0064cd', //same as tooltip color + '#46a546', + '#9d261d', + '#ffc40d', + '#f89406', + '#c3325f', + '#7a43b6' + ], + data: {}, + line: { + text: { + attr: { + x: 3, + dy: '.35em' + }, + text: function (d) { + return d.name; + } + } + }, + margin: { + top: 20, + right: 100, + bottom: 30, + left: 40 + }, + stage: { + attr: { + 'class': 'bootstrapd3-stage' + }, + width: 960, + height: 500 + }, + target: 'body', + tooltip: { + attr: { + 'class': 'tooltip' + }, + text: 'tooltip' + }, + x: '', + y: '', + __params: { + OLD_FILL_PROPERTY: 'data-oldFill' + } }; -}(this.BootStrapD3 = this.BootStrapD3 || {}, jQuery, d3)); -/** - * Pie chart module - */ -BootStrapD3.module('pieChart', function (exports, $, d3, utils) { + function builder(error, data, config) { + + //Some modified dimensions + var margin = config.margin; + var width = config.stage.width - margin.left - margin.right; + var height = config.stage.height - margin.top - margin.bottom; + + //TODO: This should not assume date + var parseDate = d3.time.format('%Y%m%d') + .parse; + + //Scale functions + var x = d3.time.scale() + .range([0, width]); + + var y = d3.scale.linear() + .range([height, 0]); + + //Color range + var color = d3.scale.ordinal() + .range(config.colors); + + //Axis + var xAxis = d3.svg.axis() + .scale(x) + .orient('bottom'); + + var yAxis = d3.svg.axis() + .scale(y) + .orient('left'); + + //Line function + var line = d3.svg.line() + .interpolate('basis') + .x(function (d) { + return x(d[config.x]); + }) + .y(function (d) { + //TODO: This should not assume temperature + return y(d.temperature); + }); + + //Tooltip + var tip = d3.tip() + .offset([-10, 0]) + .html(config.tooltip.text) + .attr('class', 'tooltip'); + //Passing an object as an argument to tip.attr returns a native selection + //rather than a d3-tip function which causes the tip to fire an error. + //.attr(config.tooltip.attr); + + //The following patch should do the trick + $.each(config.tooltip.attr, function (key, value) { + tip.attr(key, value); + }); + + //Stage + var svg = d3.select('body') + .append('svg') + .attr(config.stage.attr) + .attr('width', width + margin.left + margin.right) + .attr('height', height + margin.top + margin.bottom) + .append('g') + .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + color.domain(d3.keys(data[0]) + .filter(function (key) { + return key !== config.x; + })); + + data.forEach(function (d) { + d.date = parseDate(d[config.x]); + }); + + var cities = color.domain() + .map(function (name) { + return { + name: name, + values: data.map(function (d) { + return { + date: d.date, + temperature: +d[name] + }; + }) + }; + }); + + x.domain(d3.extent(data, function (d) { + return d.date; + })); + + y.domain([ + d3.min(cities, function (c) { + return d3.min(c.values, function (v) { + return v.temperature; + }); + }), + d3.max(cities, function (c) { + return d3.max(c.values, function (v) { + return v.temperature; + }); + }) + ]); + + //Axis + svg.append('g') + .attr('class', 'x axis') + .attr('transform', 'translate(0,' + height + ')') + .call(xAxis); + + svg.append('g') + .attr('class', 'y axis') + .call(yAxis) + .append('text') + .attr('y', 6) + .attr('transform', 'rotate(-90)') + .attr('dy', '.71em') + .style('text-anchor', 'end'); + //.text('Temperature (ºF)'); + + //City group + var city = svg.selectAll('.city') + .data(cities) + .enter() + .append('g') + .attr('class', 'city'); + + var path = city.append('path') + .attr('class', 'line') + .attr('d', function (d) { + return line(d.values); + }) + .style('stroke', function (d) { + return color(d.name); + }); + + var circle = svg.append("circle") + .attr("cx", 100) + .attr("cy", 350) + .attr("r", 3) + .attr("fill", "red"); - //Default pie configuration - var defaultConfig = {}; + var pathEl = path.node(); + var pathLength = pathEl.getTotalLength(); + var BBox = pathEl.getBBox(); + var scale = pathLength / BBox.width; + var offsetLeft = 0; //svg.offsetLeft; + + svg.on("mousemove", function () { + var x = d3.event.pageX - offsetLeft; + var beginning = x, + end = pathLength, + target, pos; + while(true) { + target = Math.floor((beginning + end) / 2); + pos = pathEl.getPointAtLength(target); + if((target === end || target === beginning) && pos.x !== x) { + break; + } + if(pos.x > x) { + end = target; + } else if(pos.x < x) { + beginning = target; + } else { + break; + } //position found + } + circle + .attr("opacity", 1) + .attr("cx", x) + .attr("cy", pos.y); + }); - defaultConfig.width = 960; - defaultConfig.height = 500; - defaultConfig.radius = Math.min(defaultConfig.width, defaultConfig.height) / 2; + city.append('text') + .datum(function (d) { + return { + name: d.name, + value: d.values[d.values.length - 1] + }; + }) + .attr('transform', function (d) { + return 'translate(' + x(d.value.date) + ',' + y(d.value.temperature) + ')'; + }) + .attr(config.line.text.attr) + .text(config.line.text.text); + + } //Exposed functions - exports.create = function (userConfig) { + return function (userConfig) { //Merge default and user config to final config - var config = $.extend({}, defaultConfig, userConfig); + var config = $.extend(true, {}, defaultConfig, userConfig); - //Create color auto-picker - //TODO: Dynamically generate colors from Bootstrap base colors - var color = d3.scale.ordinal() - .range(utils.baseColors); + //Load data accordingly + if(config.data instanceof Array) { + //If data is already an Array of data + builder(null, config.data, config); + } else if(typeof config.data === 'string') { + //If string, assume it's a url + d3.json(config.data, function (error, data) { + builder(error, data, config); + }); + } + }; - //Create arc function for path to turn into an arc - var arc = d3.svg.arc() - .outerRadius(config.radius - 10) - .innerRadius(0); +}); + +(function (window, document, Openkas, undefined) { + + Openkas.processContainerChart = function (config) { + + var margin = { + top: 50, + right: 20, + bottom: 50, + left: 60 + }; + + var width = 960 - margin.left - margin.right; + var height = 500 - margin.top - margin.bottom; + + // Date parser function + // Converts the input date at given format into a Date object + var parseDate = d3.time + .format('%Y-%m-%d') + .parse; + + // X axis scale function + var x = d3.time + .scale() + .range([0, width]); + + // Y axis scale function + var y = d3.scale + .linear() + .range([height, 0]); + + // X axis + var xAxis = d3.svg + .axis() + .scale(x) + .orient('bottom') + .tickFormat(d3.time.format('%Y-%m-%d')) + .tickSize(-height, 0, 0); + + // Y axis + var yAxis = d3.svg + .axis() + .scale(y) + .orient('left') + .tickSize(-width, 0, 0); + + // Line function with points set by data + var line = d3.svg + .line() + .x(function (d) { + return x(d.date); + }) + .y(function (d) { + return y(d.containerCount); + }); + + var svg = d3.select(config.container) + .append('svg') + .attr('width', width + margin.left + margin.right) + .attr('height', height + margin.top + margin.bottom) + .append('g') + .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + d3.json(config.data, function (error, rawData) { + + // Parse data + // TODO: ES5 tech here, watch out for old browsers + + // Pluxk out the header row + var headers = rawData.shift(); + var data = {}; + + // Parse the data + // First we merge + var plantCount = 0; + var containerCount = 0; + rawData.forEach(function (row, rowIndex, dataArray) { + + // Create or use existing data at date + var dateData; + + if(data.hasOwnProperty(row[4])) { + dateData = data[row[4]]; + } else { + dateData = data[row[4]] = {}; + + // Remap rowData into the data object + // TODO: This remap will only write the first unique row data + headers.forEach(function (headerValue, headerIndex) { + dateData[headerValue] = row[headerIndex]; + }); + } + + // Append count + plantCount += (+row[5]); + containerCount += (+row[6]); + + // Reflect numbers to data point + dateData.plantCount = plantCount; + dateData.containerCount = containerCount; + + }); + + // Then convert into an array that d3 can work with + data = Object.keys(data) + .map(function (value, index, array) { + return data[value]; + }); + + // Converting data into workable values + data.forEach(function (d) { + + // Parse date + d.date = parseDate(d.TimeStamp); - //Create pie function to format data to a pie + // Convert count changes to numbers + d.PlantCountChange = +d.PlantCountChange; + d.ContainerCountChange = +d.ContainerCountChange; + }); + + // Reading the min/max values for the domain + x.domain(d3.extent(data, function (d) { + return d.date; + })); + + y.domain([ + 0, + d3.max(data, function (d) { + return d.containerCount; + }) + 2 + ]); + + // Append X axis + svg.append('g') + .attr('class', 'x axis') + .attr('transform', 'translate(0,' + height + ')') + .call(xAxis) + .append('text') + .attr('class', 'title') + .attr('x', width / 2) + .attr('y', 40) + .text(config.xAxisTitle || 'X Axis'); + + // Append Y axis + svg.append('g') + .attr('class', 'y axis') + .call(yAxis) + .append('text') + .attr('class', 'title') + .attr('transform', 'rotate(-90)') + .attr('x', -(height / 2)) + .attr('y', -40) + .text(config.yAxisTitle || 'Y Axis'); + + // Append data line + svg.append('path') + .datum(data) + .attr('class', 'line') + .attr('d', line); + + // Add border rectangle + svg.append("rect") + .attr('class', 'border') + .attr("x", 0) + .attr("y", 0) + .attr("height", height) + .attr("width", width); + + // Append title + svg.append('text') + .attr('class', 'chart title') + .attr("x", 0) + .attr("y", -30) + .text(config.chartTitle || 'Chart Title'); + + }); + }; + + // Note that this uses a new Openkas namespace +}(window, document, this.Openkas = this.Openkas || {})); + +BootstrapD3.module('PieChart', function ($, d3, utils) { + 'use strict'; + + //Default configuration + var defaultConfig = { + arc: { + attr: { + 'class': 'arc' + }, + }, + colors: [ + '#049cdb', + //'#0064cd', //same as tooltip color + '#46a546', + '#9d261d', + '#ffc40d', + '#f89406', + '#c3325f', + '#7a43b6' + ], + data: {}, + label: { + attr: { + 'class': 'label', + 'dy': '.35em' + }, + offset: [50, 0], + text: 'label' + }, + stage: { + attr: { + 'class': 'bootstrapd3-stage' + }, + width: 960, + height: 500 + }, + target: 'body', + tooltip: { + attr: { + 'class': 'tooltip' + }, + text: 'tooltip' + }, + x: '', + y: '', + __params: { + OLD_FILL_PROPERTY: 'data-oldFill' + } + }; + + function builder(error, data, config) { + + //Error traps + //TODO: Make them more informative + if(error) { + throw new Error('Error in loaded data: ' + error); + } + if(!data) { + throw new Error('Data is undefined'); + } + + //Color range + //TODO: Dynamically generate colors from Bootstrap base colors depending on data + var color = d3.scale.ordinal() + .range(config.colors); + + //Pie function var pie = d3.layout.pie() .sort(null) .value(function (d) { - return d.population; + return d[config.y]; }); - //Create our svg stage and create a centered group for our pie - var svg = d3.select('body') + //Create arc function + var arc = d3.svg.arc() + .outerRadius(config.arc.radius - 10) + .innerRadius(0); + + //Create our svg stage + var svg = d3.select(config.target) .append('svg') - .attr('width', config.width) - .attr('height', config.height) + .attr(config.stage.attr) .append('g') - .attr('transform', 'translate(' + config.width / 2 + ',' + config.height / 2 + ')'); + .attr('transform', 'translate(' + config.stage.width / 2 + ',' + config.stage.height / 2 + ')'); + //TODO: These types of attributes should be accessible, which means + //internals must be visible to users somehow. - //Load data - d3.json('data.json', function (error, data) { + //Tooltips + var tip = d3.tip() + .attr('class', 'tooltip') + .offset([50, 0]) + .html(config.tooltip.text); + //.attr(config.tooltip.attr); + //Passing an object as an argument to tip.attr returns a native + //selection rather than a d3-tip function which causes the tip to + //fire an error. The following patch should do the trick. + $.each(config.tooltip.attr, function (key, value) { + tip.attr(key, value); + }); - //Error traps - //TODO: Make them more informative - if(error) { - throw new Error('Error in loaded data:' + error); - } - if(!data) { - throw new Error('Data is undefined'); + //Create a slice group + var slice = svg.selectAll('.slice') + .data(pie(data)) + .enter() + .append('g') + .call(tip) + .on('mouseover', tip.show) + .on('mouseout', tip.hide); + + //Arcs + slice.append('path') + .attr(config.arc.attr) + .attr('d', arc) + .style('fill', function (d) { + return color(d.data[config.x]); + }) + .on('mouseover', function (d, i) { + var currentColor = this.style.fill.substr(1); + var newShade = utils.shadeColor(currentColor, 10); + this[config.OLD_FILL_PROPERTY] = currentColor; + this.style.fill = '#' + newShade; + }) + .on('mouseout', function (d, i) { + this.style.fill = '#' + this[config.OLD_FILL_PROPERTY]; + }); + + //Labels + //TODO: Text movable outside the pie + slice.append('text') + .attr(config.label.attr) + .attr('transform', function (d) { + return 'translate(' + arc.centroid(d) + ')'; + }) + .text(config.label.text); + + } + + //Exposed functions + return function (userConfig) { + + //Merge config + var config = $.extend(true, {}, defaultConfig, userConfig); + + //Post-merge config + config.arc.radius = Math.min(config.stage.width, config.stage.height) / 2; + + //Load data accordingly + if(config.data instanceof Array) { + //If data is already an Array of data + builder.call(null, null, config.data, config); + } else if(typeof config.data === 'string') { + //If string, assume it's a url + d3.json(config.data, function (error, data) { + builder.call(null, error, data, config); + }); + } + + }; +}); + +BootstrapD3.module('StackedBarChart', function ($, d3, utils) { + 'use strict'; + + //Default configuration + var defaultConfig = { + colors: [ + '#049cdb', + //'#0064cd', //same as tooltip color + '#46a546', + '#9d261d', + '#ffc40d', + '#f89406', + '#c3325f', + '#7a43b6' + ], + data: {}, + legend: { + attr: { + 'class': 'legend' + }, + squares: { + attr: { + 'class': 'legendColor', + 'width': 18, + 'height': 18 + } + }, + text: { + attr: { + dy: '.35em' + }, + text: function (d) { + return d; + } } + }, + margin: { + top: 20, + right: 20, + bottom: 30, + left: 40 + }, + stage: { + attr: { + 'class': 'bootstrapd3-stage' + }, + width: 960, + height: 500 + }, + target: 'body', + tooltip: { + attr: { + 'class': 'tooltip' + }, + text: 'tooltip' + }, + x: '', + y: '', + __params: { + OLD_FILL_PROPERTY: 'data-oldFill' + } + }; - //Binding all arcs with data - //Arcs are just groups by the way - var g = svg.selectAll('.arc') - .data(pie(data)) - .enter() - .append('g') - .attr('class', 'arc'); - - //Arcs are just closed paths - g.append('path') - .attr('d', arc) - .style('fill', function (d) { - console.log(d); - return color(d.data.age); - }); + function builder(error, data, config) { + + //Some modified dimensions + var margin = config.margin; + var width = config.stage.width - margin.left - margin.right; + var height = config.stage.height - margin.top - margin.bottom; - //Add text to the center of the arc - //TODO: Bootstrap typography - // Move text outside the pie - // Highlight hover - // Tooltip - g.append('text') - .attr('transform', function (d) { - return 'translate(' + arc.centroid(d) + ')'; - }) - .attr('class', 'pieText') - .attr('dy', '.35em') - .text(function (d) { - return d.data.age; + //Dimension ranges + var x = d3.scale.ordinal() + .rangeRoundBands([0, width], 0.1); + + var y = d3.scale.linear() + .rangeRound([height, 0]); + + //Color range + var color = d3.scale.ordinal() + .range(config.colors); + + //Axis functions + var xAxis = d3.svg.axis() + .scale(x) + .orient('bottom'); + + var yAxis = d3.svg.axis() + .scale(y) + .orient('left') + .tickFormat(d3.format('.2s')); + + //Tooltip + var tip = d3.tip() + .offset([-10, 0]) + .html(config.tooltip.text) + .attr('class', 'tooltip'); + //Passing an object as an argument to tip.attr returns a native selection + //rather than a d3-tip function which causes the tip to fire an error. + //.attr(config.tooltip.attr); + + //The following patch should do the trick + $.each(config.tooltip.attr, function (key, value) { + tip.attr(key, value); + }); + + //Stage + var svg = d3.select(config.target) + .append('svg') + .attr(config.stage.attr) + .attr('width', config.stage.width) + .attr('height', config.stage.height) + .append('g') + .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //Filter out the x from the data set for colors + color.domain(d3.keys(data[0]) + .filter(function (key) { + return key !== config.x; + })); + + //TODO: Find actual numbers + data.forEach(function (d) { + var y0 = 0; + d.ages = color.domain() + .map(function (name) { + return { + name: name, + value: d[name], + y0: y0, + y1: y0 += +d[name] + }; }); + d.total = d.ages[d.ages.length - 1].y1; + }); + //Sort greatest to least + data.sort(function (a, b) { + return b.total - a.total; }); - }; -}); + //Fetch the data for labels in data set + x.domain(data.map(function (d) { + return d[config.x]; + })); + + y.domain([0, d3.max(data, function (d) { + return d.total; + })]); + + //Axis + svg.append('g') + .attr('class', 'x axis') + .attr('transform', 'translate(0,' + height + ')') + .call(xAxis); + svg.append('g') + .attr('class', 'y axis') + .call(yAxis) + .append('text') + .attr('transform', 'rotate(-90)') + .attr('y', 6) + .attr('dy', '.71em') + .style('text-anchor', 'end'); + //.text('Population'); + + //Columns + var state = svg.selectAll('.state') + .data(data) + .enter() + .append('g') + .attr('class', 'g') + .attr('transform', function (d) { + return 'translate(' + x(d.State) + ',0)'; + }); + + //blocks + state.selectAll('rect') + .data(function (d) { + return d.ages; + }) + .enter() + .append('rect') + .attr('width', x.rangeBand()) + .attr('y', function (d) { + return y(d.y1); + }) + .attr('height', function (d) { + return y(d.y0) - y(d.y1); + }) + .style('fill', function (d) { + return color(d.name); + }) + .call(tip) + .on('mouseover', function (d, i) { + var currentColor = this.style.fill.substr(1); + var newShade = utils.shadeColor(currentColor, 30); + this[config.OLD_FILL_PROPERTY] = currentColor; + this.style.fill = '#' + newShade; + tip.show.apply(this, arguments); + }) + .on('mouseout', function (d, i) { + this.style.fill = '#' + this[config.OLD_FILL_PROPERTY]; + tip.hide.apply(this, arguments); + }); + + //Legend + var legend = svg.selectAll('.legend') + .data(color.domain() + .slice() + .reverse()) + .enter() + .append('g') + .attr(config.legend.attr) + .attr('transform', function (d, i) { + return 'translate(0,' + i * 20 + ')'; + }); + + legend.append('rect') + .attr(config.legend.squares.attr) + .attr('x', width - 18) + .style('fill', color); + + legend.append('text') + .attr(config.legend.text.attr) + .attr('x', width - 24) + .attr('y', 9) + .style('text-anchor', 'end') + .text(config.legend.text.text); + + } + + //Exposed functions + return function (userConfig) { + + //Merge default and user config to final config + var config = $.extend(true, {}, defaultConfig, userConfig); + + //Load data accordingly + if(config.data instanceof Array) { + //If data is already an Array of data + builder(null, config.data, config); + } else if(typeof config.data === 'string') { + //If string, assume it's a url + d3.json(config.data, function (error, data) { + builder(error, data, config); + }); + } + }; + +}); diff --git a/dist/bootstrap-d3.min.js b/dist/bootstrap-d3.min.js index 6e827c0..b00e87f 100644 --- a/dist/bootstrap-d3.min.js +++ b/dist/bootstrap-d3.min.js @@ -1,6 +1,6 @@ /* bootstrap-d3 v0.0.0 -2013-06-21 +2013-07-08 https://github.com/openkas/bootstrap-d3 Contributors: @@ -9,4 +9,4 @@ Marijn van der Zee http://serraict.com License: BSD */ -!function(a,b){b.BootstrapD3=a;var c;!function(a,b,c){var d={};d.baseColors=["#049cdb","#0064cd","#46a546","#9d261d","#ffc40d","#f89406","#c3325f","#7a43b6"],a.module=function(e,f){var g=a[e]={};f.call(null,g,b,c,d)}}(this.BootStrapD3=this.BootStrapD3||{},jQuery,d3),c.module("pieChart",function(a,b,c,d){var e={};e.width=960,e.height=500,e.radius=Math.min(e.width,e.height)/2,a.create=function(a){var f=b.extend({},e,a),g=c.scale.ordinal().range(d.baseColors),h=c.svg.arc().outerRadius(f.radius-10).innerRadius(0),i=c.layout.pie().sort(null).value(function(a){return a.population}),j=c.select("body").append("svg").attr("width",f.width).attr("height",f.height).append("g").attr("transform","translate("+f.width/2+","+f.height/2+")");c.json("data.json",function(a,b){if(a)throw new Error("Error in loaded data:"+a);if(!b)throw new Error("Data is undefined");var c=j.selectAll(".arc").data(i(b)).enter().append("g").attr("class","arc");c.append("path").attr("d",h).style("fill",function(a){return console.log(a),g(a.data.age)}),c.append("text").attr("transform",function(a){return"translate("+h.centroid(a)+")"}).attr("class","pieText").attr("dy",".35em").text(function(a){return a.data.age})})}})}({},function(){return this}()); \ No newline at end of file +!function(a,b){b.BootstrapD3=a,function(a,b,c,d){"use strict";var e={};e.shadeColor=function(a,b){var c=parseInt(a,16),d=Math.round(2.55*b),e=(c>>16)+d,f=(255&c>>8)+d,g=(255&c)+d;return(16777216+65536*(255>e?1>e?0:e:255)+256*(255>f?1>f?0:f:255)+(255>g?1>g?0:g:255)).toString(16).slice(1)},b.module=function(a,f){b[a]=f.call(null,c,d,e)},"object"==typeof module&&module&&"object"==typeof module.exports?module.exports=b:"function"==typeof define&&define.amd&&define("bootstrapd3",["jquery","d3","bootstrap"],function(){return b}),"object"==typeof a&&"object"==typeof a.document&&(a.BootstrapD3=b)}(this,{},jQuery,d3),BootstrapD3.module("MultiSeriesLineChart",function(a,b){"use strict";function c(c,d,e){var f=e.margin,g=e.stage.width-f.left-f.right,h=e.stage.height-f.top-f.bottom,i=b.time.format("%Y%m%d").parse,j=b.time.scale().range([0,g]),k=b.scale.linear().range([h,0]),l=b.scale.ordinal().range(e.colors),m=b.svg.axis().scale(j).orient("bottom"),n=b.svg.axis().scale(k).orient("left"),o=b.svg.line().interpolate("basis").x(function(a){return j(a[e.x])}).y(function(a){return k(a.temperature)}),p=b.tip().offset([-10,0]).html(e.tooltip.text).attr("class","tooltip");a.each(e.tooltip.attr,function(a,b){p.attr(a,b)});var q=b.select("body").append("svg").attr(e.stage.attr).attr("width",g+f.left+f.right).attr("height",h+f.top+f.bottom).append("g").attr("transform","translate("+f.left+","+f.top+")");l.domain(b.keys(d[0]).filter(function(a){return a!==e.x})),d.forEach(function(a){a.date=i(a[e.x])});var r=l.domain().map(function(a){return{name:a,values:d.map(function(b){return{date:b.date,temperature:+b[a]}})}});j.domain(b.extent(d,function(a){return a.date})),k.domain([b.min(r,function(a){return b.min(a.values,function(a){return a.temperature})}),b.max(r,function(a){return b.max(a.values,function(a){return a.temperature})})]),q.append("g").attr("class","x axis").attr("transform","translate(0,"+h+")").call(m),q.append("g").attr("class","y axis").call(n).append("text").attr("y",6).attr("transform","rotate(-90)").attr("dy",".71em").style("text-anchor","end");var s=q.selectAll(".city").data(r).enter().append("g").attr("class","city"),t=s.append("path").attr("class","line").attr("d",function(a){return o(a.values)}).style("stroke",function(a){return l(a.name)}),u=q.append("circle").attr("cx",100).attr("cy",350).attr("r",3).attr("fill","red"),v=t.node(),w=v.getTotalLength(),x=v.getBBox();w/x.width;var y=0;q.on("mousemove",function(){for(var a,c,d=b.event.pageX-y,e=d,f=w;;){if(a=Math.floor((e+f)/2),c=v.getPointAtLength(a),(a===f||a===e)&&c.x!==d)break;if(c.x>d)f=a;else{if(!(c.x + + + + Openkas Process-Container Chart + + + + +
+ + + + + + + \ No newline at end of file diff --git a/examples/pie-chart.html b/examples/pie-chart.html deleted file mode 100644 index a763f2a..0000000 --- a/examples/pie-chart.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/examples/data.json b/examples/pie-chart/data.json similarity index 100% rename from examples/data.json rename to examples/pie-chart/data.json diff --git a/examples/pie-chart/index.html b/examples/pie-chart/index.html new file mode 100644 index 0000000..311238a --- /dev/null +++ b/examples/pie-chart/index.html @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/stacked-bar-chart.html b/examples/stacked-bar-chart.html deleted file mode 100644 index e69de29..0000000 diff --git a/examples/stacked-bar-chart/data.json b/examples/stacked-bar-chart/data.json new file mode 100644 index 0000000..8463ac5 --- /dev/null +++ b/examples/stacked-bar-chart/data.json @@ -0,0 +1,512 @@ +[ + { + "State": "AL", + "Under 5 Years": "310504", + "5 to 13 Years": "552339", + "14 to 17 Years": "259034", + "18 to 24 Years": "450818", + "25 to 44 Years": "1231572", + "45 to 64 Years": "1215966", + "65 Years and Over": "641667" + }, + { + "State": "AK", + "Under 5 Years": "52083", + "5 to 13 Years": "85640", + "14 to 17 Years": "42153", + "18 to 24 Years": "74257", + "25 to 44 Years": "198724", + "45 to 64 Years": "183159", + "65 Years and Over": "50277" + }, + { + "State": "AZ", + "Under 5 Years": "515910", + "5 to 13 Years": "828669", + "14 to 17 Years": "362642", + "18 to 24 Years": "601943", + "25 to 44 Years": "1804762", + "45 to 64 Years": "1523681", + "65 Years and Over": "862573" + }, + { + "State": "AR", + "Under 5 Years": "202070", + "5 to 13 Years": "343207", + "14 to 17 Years": "157204", + "18 to 24 Years": "264160", + "25 to 44 Years": "754420", + "45 to 64 Years": "727124", + "65 Years and Over": "407205" + }, + { + "State": "CA", + "Under 5 Years": "2704659", + "5 to 13 Years": "4499890", + "14 to 17 Years": "2159981", + "18 to 24 Years": "3853788", + "25 to 44 Years": "10604510", + "45 to 64 Years": "8819342", + "65 Years and Over": "4114496" + }, + { + "State": "CO", + "Under 5 Years": "358280", + "5 to 13 Years": "587154", + "14 to 17 Years": "261701", + "18 to 24 Years": "466194", + "25 to 44 Years": "1464939", + "45 to 64 Years": "1290094", + "65 Years and Over": "511094" + }, + { + "State": "CT", + "Under 5 Years": "211637", + "5 to 13 Years": "403658", + "14 to 17 Years": "196918", + "18 to 24 Years": "325110", + "25 to 44 Years": "916955", + "45 to 64 Years": "968967", + "65 Years and Over": "478007" + }, + { + "State": "DE", + "Under 5 Years": "59319", + "5 to 13 Years": "99496", + "14 to 17 Years": "47414", + "18 to 24 Years": "84464", + "25 to 44 Years": "230183", + "45 to 64 Years": "230528", + "65 Years and Over": "121688" + }, + { + "State": "DC", + "Under 5 Years": "36352", + "5 to 13 Years": "50439", + "14 to 17 Years": "25225", + "18 to 24 Years": "75569", + "25 to 44 Years": "193557", + "45 to 64 Years": "140043", + "65 Years and Over": "70648" + }, + { + "State": "FL", + "Under 5 Years": "1140516", + "5 to 13 Years": "1938695", + "14 to 17 Years": "925060", + "18 to 24 Years": "1607297", + "25 to 44 Years": "4782119", + "45 to 64 Years": "4746856", + "65 Years and Over": "3187797" + }, + { + "State": "GA", + "Under 5 Years": "740521", + "5 to 13 Years": "1250460", + "14 to 17 Years": "557860", + "18 to 24 Years": "919876", + "25 to 44 Years": "2846985", + "45 to 64 Years": "2389018", + "65 Years and Over": "981024" + }, + { + "State": "HI", + "Under 5 Years": "87207", + "5 to 13 Years": "134025", + "14 to 17 Years": "64011", + "18 to 24 Years": "124834", + "25 to 44 Years": "356237", + "45 to 64 Years": "331817", + "65 Years and Over": "190067" + }, + { + "State": "ID", + "Under 5 Years": "121746", + "5 to 13 Years": "201192", + "14 to 17 Years": "89702", + "18 to 24 Years": "147606", + "25 to 44 Years": "406247", + "45 to 64 Years": "375173", + "65 Years and Over": "182150" + }, + { + "State": "IL", + "Under 5 Years": "894368", + "5 to 13 Years": "1558919", + "14 to 17 Years": "725973", + "18 to 24 Years": "1311479", + "25 to 44 Years": "3596343", + "45 to 64 Years": "3239173", + "65 Years and Over": "1575308" + }, + { + "State": "IN", + "Under 5 Years": "443089", + "5 to 13 Years": "780199", + "14 to 17 Years": "361393", + "18 to 24 Years": "605863", + "25 to 44 Years": "1724528", + "45 to 64 Years": "1647881", + "65 Years and Over": "813839" + }, + { + "State": "IA", + "Under 5 Years": "201321", + "5 to 13 Years": "345409", + "14 to 17 Years": "165883", + "18 to 24 Years": "306398", + "25 to 44 Years": "750505", + "45 to 64 Years": "788485", + "65 Years and Over": "444554" + }, + { + "State": "KS", + "Under 5 Years": "202529", + "5 to 13 Years": "342134", + "14 to 17 Years": "155822", + "18 to 24 Years": "293114", + "25 to 44 Years": "728166", + "45 to 64 Years": "713663", + "65 Years and Over": "366706" + }, + { + "State": "KY", + "Under 5 Years": "284601", + "5 to 13 Years": "493536", + "14 to 17 Years": "229927", + "18 to 24 Years": "381394", + "25 to 44 Years": "1179637", + "45 to 64 Years": "1134283", + "65 Years and Over": "565867" + }, + { + "State": "LA", + "Under 5 Years": "310716", + "5 to 13 Years": "542341", + "14 to 17 Years": "254916", + "18 to 24 Years": "471275", + "25 to 44 Years": "1162463", + "45 to 64 Years": "1128771", + "65 Years and Over": "540314" + }, + { + "State": "ME", + "Under 5 Years": "71459", + "5 to 13 Years": "133656", + "14 to 17 Years": "69752", + "18 to 24 Years": "112682", + "25 to 44 Years": "331809", + "45 to 64 Years": "397911", + "65 Years and Over": "199187" + }, + { + "State": "MD", + "Under 5 Years": "371787", + "5 to 13 Years": "651923", + "14 to 17 Years": "316873", + "18 to 24 Years": "543470", + "25 to 44 Years": "1556225", + "45 to 64 Years": "1513754", + "65 Years and Over": "679565" + }, + { + "State": "MA", + "Under 5 Years": "383568", + "5 to 13 Years": "701752", + "14 to 17 Years": "341713", + "18 to 24 Years": "665879", + "25 to 44 Years": "1782449", + "45 to 64 Years": "1751508", + "65 Years and Over": "871098" + }, + { + "State": "MI", + "Under 5 Years": "625526", + "5 to 13 Years": "1179503", + "14 to 17 Years": "585169", + "18 to 24 Years": "974480", + "25 to 44 Years": "2628322", + "45 to 64 Years": "2706100", + "65 Years and Over": "1304322" + }, + { + "State": "MN", + "Under 5 Years": "358471", + "5 to 13 Years": "606802", + "14 to 17 Years": "289371", + "18 to 24 Years": "507289", + "25 to 44 Years": "1416063", + "45 to 64 Years": "1391878", + "65 Years and Over": "650519" + }, + { + "State": "MS", + "Under 5 Years": "220813", + "5 to 13 Years": "371502", + "14 to 17 Years": "174405", + "18 to 24 Years": "305964", + "25 to 44 Years": "764203", + "45 to 64 Years": "730133", + "65 Years and Over": "371598" + }, + { + "State": "MO", + "Under 5 Years": "399450", + "5 to 13 Years": "690476", + "14 to 17 Years": "331543", + "18 to 24 Years": "560463", + "25 to 44 Years": "1569626", + "45 to 64 Years": "1554812", + "65 Years and Over": "805235" + }, + { + "State": "MT", + "Under 5 Years": "61114", + "5 to 13 Years": "106088", + "14 to 17 Years": "53156", + "18 to 24 Years": "95232", + "25 to 44 Years": "236297", + "45 to 64 Years": "278241", + "65 Years and Over": "137312" + }, + { + "State": "NE", + "Under 5 Years": "132092", + "5 to 13 Years": "215265", + "14 to 17 Years": "99638", + "18 to 24 Years": "186657", + "25 to 44 Years": "457177", + "45 to 64 Years": "451756", + "65 Years and Over": "240847" + }, + { + "State": "NV", + "Under 5 Years": "199175", + "5 to 13 Years": "325650", + "14 to 17 Years": "142976", + "18 to 24 Years": "212379", + "25 to 44 Years": "769913", + "45 to 64 Years": "653357", + "65 Years and Over": "296717" + }, + { + "State": "NH", + "Under 5 Years": "75297", + "5 to 13 Years": "144235", + "14 to 17 Years": "73826", + "18 to 24 Years": "119114", + "25 to 44 Years": "345109", + "45 to 64 Years": "388250", + "65 Years and Over": "169978" + }, + { + "State": "NJ", + "Under 5 Years": "557421", + "5 to 13 Years": "1011656", + "14 to 17 Years": "478505", + "18 to 24 Years": "769321", + "25 to 44 Years": "2379649", + "45 to 64 Years": "2335168", + "65 Years and Over": "1150941" + }, + { + "State": "NM", + "Under 5 Years": "148323", + "5 to 13 Years": "241326", + "14 to 17 Years": "112801", + "18 to 24 Years": "203097", + "25 to 44 Years": "517154", + "45 to 64 Years": "501604", + "65 Years and Over": "260051" + }, + { + "State": "NY", + "Under 5 Years": "1208495", + "5 to 13 Years": "2141490", + "14 to 17 Years": "1058031", + "18 to 24 Years": "1999120", + "25 to 44 Years": "5355235", + "45 to 64 Years": "5120254", + "65 Years and Over": "2607672" + }, + { + "State": "NC", + "Under 5 Years": "652823", + "5 to 13 Years": "1097890", + "14 to 17 Years": "492964", + "18 to 24 Years": "883397", + "25 to 44 Years": "2575603", + "45 to 64 Years": "2380685", + "65 Years and Over": "1139052" + }, + { + "State": "ND", + "Under 5 Years": "41896", + "5 to 13 Years": "67358", + "14 to 17 Years": "33794", + "18 to 24 Years": "82629", + "25 to 44 Years": "154913", + "45 to 64 Years": "166615", + "65 Years and Over": "94276" + }, + { + "State": "OH", + "Under 5 Years": "743750", + "5 to 13 Years": "1340492", + "14 to 17 Years": "646135", + "18 to 24 Years": "1081734", + "25 to 44 Years": "3019147", + "45 to 64 Years": "3083815", + "65 Years and Over": "1570837" + }, + { + "State": "OK", + "Under 5 Years": "266547", + "5 to 13 Years": "438926", + "14 to 17 Years": "200562", + "18 to 24 Years": "369916", + "25 to 44 Years": "957085", + "45 to 64 Years": "918688", + "65 Years and Over": "490637" + }, + { + "State": "OR", + "Under 5 Years": "243483", + "5 to 13 Years": "424167", + "14 to 17 Years": "199925", + "18 to 24 Years": "338162", + "25 to 44 Years": "1044056", + "45 to 64 Years": "1036269", + "65 Years and Over": "503998" + }, + { + "State": "PA", + "Under 5 Years": "737462", + "5 to 13 Years": "1345341", + "14 to 17 Years": "679201", + "18 to 24 Years": "1203944", + "25 to 44 Years": "3157759", + "45 to 64 Years": "3414001", + "65 Years and Over": "1910571" + }, + { + "State": "RI", + "Under 5 Years": "60934", + "5 to 13 Years": "111408", + "14 to 17 Years": "56198", + "18 to 24 Years": "114502", + "25 to 44 Years": "277779", + "45 to 64 Years": "282321", + "65 Years and Over": "147646" + }, + { + "State": "SC", + "Under 5 Years": "303024", + "5 to 13 Years": "517803", + "14 to 17 Years": "245400", + "18 to 24 Years": "438147", + "25 to 44 Years": "1193112", + "45 to 64 Years": "1186019", + "65 Years and Over": "596295" + }, + { + "State": "SD", + "Under 5 Years": "58566", + "5 to 13 Years": "94438", + "14 to 17 Years": "45305", + "18 to 24 Years": "82869", + "25 to 44 Years": "196738", + "45 to 64 Years": "210178", + "65 Years and Over": "116100" + }, + { + "State": "TN", + "Under 5 Years": "416334", + "5 to 13 Years": "725948", + "14 to 17 Years": "336312", + "18 to 24 Years": "550612", + "25 to 44 Years": "1719433", + "45 to 64 Years": "1646623", + "65 Years and Over": "819626" + }, + { + "State": "TX", + "Under 5 Years": "2027307", + "5 to 13 Years": "3277946", + "14 to 17 Years": "1420518", + "18 to 24 Years": "2454721", + "25 to 44 Years": "7017731", + "45 to 64 Years": "5656528", + "65 Years and Over": "2472223" + }, + { + "State": "UT", + "Under 5 Years": "268916", + "5 to 13 Years": "413034", + "14 to 17 Years": "167685", + "18 to 24 Years": "329585", + "25 to 44 Years": "772024", + "45 to 64 Years": "538978", + "65 Years and Over": "246202" + }, + { + "State": "VT", + "Under 5 Years": "32635", + "5 to 13 Years": "62538", + "14 to 17 Years": "33757", + "18 to 24 Years": "61679", + "25 to 44 Years": "155419", + "45 to 64 Years": "188593", + "65 Years and Over": "86649" + }, + { + "State": "VA", + "Under 5 Years": "522672", + "5 to 13 Years": "887525", + "14 to 17 Years": "413004", + "18 to 24 Years": "768475", + "25 to 44 Years": "2203286", + "45 to 64 Years": "2033550", + "65 Years and Over": "940577" + }, + { + "State": "WA", + "Under 5 Years": "433119", + "5 to 13 Years": "750274", + "14 to 17 Years": "357782", + "18 to 24 Years": "610378", + "25 to 44 Years": "1850983", + "45 to 64 Years": "1762811", + "65 Years and Over": "783877" + }, + { + "State": "WV", + "Under 5 Years": "105435", + "5 to 13 Years": "189649", + "14 to 17 Years": "91074", + "18 to 24 Years": "157989", + "25 to 44 Years": "470749", + "45 to 64 Years": "514505", + "65 Years and Over": "285067" + }, + { + "State": "WI", + "Under 5 Years": "362277", + "5 to 13 Years": "640286", + "14 to 17 Years": "311849", + "18 to 24 Years": "553914", + "25 to 44 Years": "1487457", + "45 to 64 Years": "1522038", + "65 Years and Over": "750146" + }, + { + "State": "WY", + "Under 5 Years": "38253", + "5 to 13 Years": "60890", + "14 to 17 Years": "29314", + "18 to 24 Years": "53980", + "25 to 44 Years": "137338", + "45 to 64 Years": "147279", + "65 Years and Over": "65614" + } +] \ No newline at end of file diff --git a/examples/stacked-bar-chart/index.html b/examples/stacked-bar-chart/index.html new file mode 100644 index 0000000..8c4654b --- /dev/null +++ b/examples/stacked-bar-chart/index.html @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/package.json b/package.json index 8090480..e49939f 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "grunt-contrib-jshint": "~0.6.0", "grunt": "~0.4.1", "grunt-contrib-qunit": "~0.2.2", - "grunt-jsbeautifier": "~0.1.9" + "grunt-jsbeautifier": "~0.1.9", + "grunt-html-prettyprinter": "~1.3.1" } } diff --git a/src/base.js b/src/base.js new file mode 100644 index 0000000..ec25e17 --- /dev/null +++ b/src/base.js @@ -0,0 +1,45 @@ +(function (window, BootstrapD3, $, d3) { + 'use strict'; + + //Utility functions common through all modules + var utils = {}; + + utils.shadeColor = function (color, percent) { + var num = parseInt(color, 16), + amt = Math.round(2.55 * percent), + R = (num >> 16) + amt, + B = (num >> 8 & 0x00FF) + amt, + G = (num & 0x0000FF) + amt; + return(0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 + (B < 255 ? B < 1 ? 0 : B : 255) * 0x100 + (G < 255 ? G < 1 ? 0 : G : 255)) + .toString(16) + .slice(1); + }; + + //Module adder + //Nothing special. Just wanted a private scope on the module + //as well as an "official" way to ad module and provide it tools. + BootstrapD3.module = function (name, definition) { + BootstrapD3[name] = definition.call(null, $, d3, utils); + }; + + //Cross-module patcher + if(typeof module === "object" && module && typeof module.exports === "object") { + module.exports = BootstrapD3; + } else if(typeof define === "function" && define.amd) { + + //This script requires the following properly shimmed by AMD loaders so that + //they load before this plugin + define("bootstrapd3", [ + 'jquery', + 'd3', + 'bootstrap' + ], function () { + return BootstrapD3; + }); + } + + if(typeof window === "object" && typeof window.document === "object") { + window.BootstrapD3 = BootstrapD3; + } + +}(this, {}, jQuery, d3)); diff --git a/src/multi-series-line-chart.js b/src/multi-series-line-chart.js deleted file mode 100644 index 8b13789..0000000 --- a/src/multi-series-line-chart.js +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/multiserieslinechart.js b/src/multiserieslinechart.js new file mode 100644 index 0000000..18d71df --- /dev/null +++ b/src/multiserieslinechart.js @@ -0,0 +1,261 @@ +BootstrapD3.module('MultiSeriesLineChart', function ($, d3, utils) { + 'use strict'; + + //Default configuration + var defaultConfig = { + colors: [ + '#049cdb', + //'#0064cd', //same as tooltip color + '#46a546', + '#9d261d', + '#ffc40d', + '#f89406', + '#c3325f', + '#7a43b6' + ], + data: {}, + line: { + text: { + attr: { + x: 3, + dy: '.35em' + }, + text: function (d) { + return d.name; + } + } + }, + margin: { + top: 20, + right: 100, + bottom: 30, + left: 40 + }, + stage: { + attr: { + 'class': 'bootstrapd3-stage' + }, + width: 960, + height: 500 + }, + target: 'body', + tooltip: { + attr: { + 'class': 'tooltip' + }, + text: 'tooltip' + }, + x: '', + y: '', + __params: { + OLD_FILL_PROPERTY: 'data-oldFill' + } + }; + + function builder(error, data, config) { + + //Some modified dimensions + var margin = config.margin; + var width = config.stage.width - margin.left - margin.right; + var height = config.stage.height - margin.top - margin.bottom; + + //TODO: This should not assume date + var parseDate = d3.time.format('%Y%m%d') + .parse; + + //Scale functions + var x = d3.time.scale() + .range([0, width]); + + var y = d3.scale.linear() + .range([height, 0]); + + //Color range + var color = d3.scale.ordinal() + .range(config.colors); + + //Axis + var xAxis = d3.svg.axis() + .scale(x) + .orient('bottom'); + + var yAxis = d3.svg.axis() + .scale(y) + .orient('left'); + + //Line function + var line = d3.svg.line() + .interpolate('basis') + .x(function (d) { + return x(d[config.x]); + }) + .y(function (d) { + //TODO: This should not assume temperature + return y(d.temperature); + }); + + //Tooltip + var tip = d3.tip() + .offset([-10, 0]) + .html(config.tooltip.text) + .attr('class', 'tooltip'); + //Passing an object as an argument to tip.attr returns a native selection + //rather than a d3-tip function which causes the tip to fire an error. + //.attr(config.tooltip.attr); + + //The following patch should do the trick + $.each(config.tooltip.attr, function (key, value) { + tip.attr(key, value); + }); + + //Stage + var svg = d3.select('body') + .append('svg') + .attr(config.stage.attr) + .attr('width', width + margin.left + margin.right) + .attr('height', height + margin.top + margin.bottom) + .append('g') + .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + color.domain(d3.keys(data[0]) + .filter(function (key) { + return key !== config.x; + })); + + data.forEach(function (d) { + d.date = parseDate(d[config.x]); + }); + + var cities = color.domain() + .map(function (name) { + return { + name: name, + values: data.map(function (d) { + return { + date: d.date, + temperature: +d[name] + }; + }) + }; + }); + + x.domain(d3.extent(data, function (d) { + return d.date; + })); + + y.domain([ + d3.min(cities, function (c) { + return d3.min(c.values, function (v) { + return v.temperature; + }); + }), + d3.max(cities, function (c) { + return d3.max(c.values, function (v) { + return v.temperature; + }); + }) + ]); + + //Axis + svg.append('g') + .attr('class', 'x axis') + .attr('transform', 'translate(0,' + height + ')') + .call(xAxis); + + svg.append('g') + .attr('class', 'y axis') + .call(yAxis) + .append('text') + .attr('y', 6) + .attr('transform', 'rotate(-90)') + .attr('dy', '.71em') + .style('text-anchor', 'end'); + //.text('Temperature (ºF)'); + + //City group + var city = svg.selectAll('.city') + .data(cities) + .enter() + .append('g') + .attr('class', 'city'); + + var path = city.append('path') + .attr('class', 'line') + .attr('d', function (d) { + return line(d.values); + }) + .style('stroke', function (d) { + return color(d.name); + }); + + var circle = svg.append("circle") + .attr("cx", 100) + .attr("cy", 350) + .attr("r", 3) + .attr("fill", "red"); + + var pathEl = path.node(); + var pathLength = pathEl.getTotalLength(); + var BBox = pathEl.getBBox(); + var scale = pathLength / BBox.width; + var offsetLeft = 0; //svg.offsetLeft; + + svg.on("mousemove", function () { + var x = d3.event.pageX - offsetLeft; + var beginning = x, + end = pathLength, + target, pos; + while(true) { + target = Math.floor((beginning + end) / 2); + pos = pathEl.getPointAtLength(target); + if((target === end || target === beginning) && pos.x !== x) { + break; + } + if(pos.x > x) { + end = target; + } else if(pos.x < x) { + beginning = target; + } else { + break; + } //position found + } + circle + .attr("opacity", 1) + .attr("cx", x) + .attr("cy", pos.y); + }); + + city.append('text') + .datum(function (d) { + return { + name: d.name, + value: d.values[d.values.length - 1] + }; + }) + .attr('transform', function (d) { + return 'translate(' + x(d.value.date) + ',' + y(d.value.temperature) + ')'; + }) + .attr(config.line.text.attr) + .text(config.line.text.text); + + } + + //Exposed functions + return function (userConfig) { + + //Merge default and user config to final config + var config = $.extend(true, {}, defaultConfig, userConfig); + + //Load data accordingly + if(config.data instanceof Array) { + //If data is already an Array of data + builder(null, config.data, config); + } else if(typeof config.data === 'string') { + //If string, assume it's a url + d3.json(config.data, function (error, data) { + builder(error, data, config); + }); + } + }; + +}); diff --git a/src/openkas-process-container-chart.js b/src/openkas-process-container-chart.js new file mode 100644 index 0000000..9b2dde6 --- /dev/null +++ b/src/openkas-process-container-chart.js @@ -0,0 +1,179 @@ +(function (window, document, Openkas, undefined) { + + Openkas.processSegmentContainerChart = function (config) { + + var margin = { + top: 50, + right: 20, + bottom: 50, + left: 60 + }; + + var width = 960 - margin.left - margin.right; + var height = 500 - margin.top - margin.bottom; + + // Date parser function + // Converts the input date at given format into a Date object + var parseDate = d3.time + .format('%Y-%m-%d') + .parse; + + // X axis scale function + var x = d3.time + .scale() + .range([0, width]); + + // Y axis scale function + var y = d3.scale + .linear() + .range([height, 0]); + + // X axis + var xAxis = d3.svg + .axis() + .scale(x) + .orient('bottom') + .tickFormat(d3.time.format('%Y-%m-%d')) + .tickSize(-height, 0, 0); + + // Y axis + var yAxis = d3.svg + .axis() + .scale(y) + .orient('left') + .tickSize(-width, 0, 0); + + // Line function with points set by data + var line = d3.svg + .line() + .x(function (d) { + return x(d.date); + }) + .y(function (d) { + return y(d.containerCount); + }); + + var svg = d3.select(config.container) + .append('svg') + .attr('width', width + margin.left + margin.right) + .attr('height', height + margin.top + margin.bottom) + .append('g') + .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + d3.json(config.data, function (error, rawData) { + + // Parse data + // TODO: ES5 tech here, watch out for old browsers + + // Pluxk out the header row + var headers = rawData.shift(); + var data = {}; + + // Parse the data + // First we merge + var plantCount = 0; + var containerCount = 0; + rawData.forEach(function (row, rowIndex, dataArray) { + + // Create or use existing data at date + var dateData; + + if(data.hasOwnProperty(row[4])) { + dateData = data[row[4]]; + } else { + dateData = data[row[4]] = {}; + + // Remap rowData into the data object + // TODO: This remap will only write the first unique row data + headers.forEach(function (headerValue, headerIndex) { + dateData[headerValue] = row[headerIndex]; + }); + } + + // Append count + plantCount += (+row[5]); + containerCount += (+row[6]); + + // Reflect numbers to data point + dateData.plantCount = plantCount; + dateData.containerCount = containerCount; + + }); + + // Then convert into an array that d3 can work with + data = Object.keys(data) + .map(function (value, index, array) { + return data[value]; + }); + + // Converting data into workable values + data.forEach(function (d) { + + // Parse date + d.date = parseDate(d.TimeStamp); + + // Convert count changes to numbers + d.PlantCountChange = +d.PlantCountChange; + d.ContainerCountChange = +d.ContainerCountChange; + }); + + // Reading the min/max values for the domain + x.domain(d3.extent(data, function (d) { + return d.date; + })); + + y.domain([ + 0, + d3.max(data, function (d) { + return d.containerCount; + }) + 2 + ]); + + // Append X axis + svg.append('g') + .attr('class', 'x axis') + .attr('transform', 'translate(0,' + height + ')') + .call(xAxis) + .append('text') + .attr('class', 'title') + .attr('x', width / 2) + .attr('y', 40) + .text(config.xAxisTitle || 'X Axis'); + + // Append Y axis + svg.append('g') + .attr('class', 'y axis') + .call(yAxis) + .append('text') + .attr('class', 'title') + .attr('transform', 'rotate(-90)') + .attr('x', -(height / 2)) + .attr('y', -40) + .text(config.yAxisTitle || 'Y Axis'); + + // Append data line + svg.append('path') + .datum(data) + .attr('class', 'line') + .attr('d', line); + + // Add border rectangle + svg.append("rect") + .attr('class', 'border') + .attr("x", 0) + .attr("y", 0) + .attr("height", height) + .attr("width", width); + + // Append title + svg.append('text') + .attr('class', 'chart title') + .attr("x", 0) + .attr("y", -30) + .text(config.chartTitle || 'Chart Title'); + + }); + }; + + // Note that this uses a new Openkas namespace +}(window, document, this.Openkas = this.Openkas || {})); diff --git a/src/pie-chart.js b/src/pie-chart.js deleted file mode 100644 index 05e407d..0000000 --- a/src/pie-chart.js +++ /dev/null @@ -1,120 +0,0 @@ -//Placed this up here since the JSHint doesn't seem to pick up the one defined -//in the IIFE below. -var BootStrapD3; - -(function (exports, $, d3) { - - //Utility functions common through all modules - var utils = {}; - - //Bootstrap base colors - utils.baseColors = [ - '#049cdb', - '#0064cd', - '#46a546', - '#9d261d', - '#ffc40d', - '#f89406', - '#c3325f', - '#7a43b6' - ]; - - exports.module = function (name, definition) { - - //Create an object which the module will attach exported stuff to - var moduleExport = exports[name] = {}; - - //Execute the definition to create module - definition.call(null, moduleExport, $, d3, utils); - }; -}(this.BootStrapD3 = this.BootStrapD3 || {}, jQuery, d3)); - -/** - * Pie chart module - */ -BootStrapD3.module('pieChart', function (exports, $, d3, utils) { - - //Default pie configuration - var defaultConfig = {}; - - defaultConfig.width = 960; - defaultConfig.height = 500; - defaultConfig.radius = Math.min(defaultConfig.width, defaultConfig.height) / 2; - - //Exposed functions - exports.create = function (userConfig) { - - //Merge default and user config to final config - var config = $.extend({}, defaultConfig, userConfig); - - //Create color auto-picker - //TODO: Dynamically generate colors from Bootstrap base colors - var color = d3.scale.ordinal() - .range(utils.baseColors); - - //Create arc function for path to turn into an arc - var arc = d3.svg.arc() - .outerRadius(config.radius - 10) - .innerRadius(0); - - //Create pie function to format data to a pie - var pie = d3.layout.pie() - .sort(null) - .value(function (d) { - return d.population; - }); - - //Create our svg stage and create a centered group for our pie - var svg = d3.select('body') - .append('svg') - .attr('width', config.width) - .attr('height', config.height) - .append('g') - .attr('transform', 'translate(' + config.width / 2 + ',' + config.height / 2 + ')'); - - //Load data - d3.json('data.json', function (error, data) { - - //Error traps - //TODO: Make them more informative - if(error) { - throw new Error('Error in loaded data:' + error); - } - if(!data) { - throw new Error('Data is undefined'); - } - - //Binding all arcs with data - //Arcs are just groups by the way - var g = svg.selectAll('.arc') - .data(pie(data)) - .enter() - .append('g') - .attr('class', 'arc'); - - //Arcs are just closed paths - g.append('path') - .attr('d', arc) - .style('fill', function (d) { - console.log(d); - return color(d.data.age); - }); - - //Add text to the center of the arc - //TODO: Bootstrap typography - // Move text outside the pie - // Highlight hover - // Tooltip - g.append('text') - .attr('transform', function (d) { - return 'translate(' + arc.centroid(d) + ')'; - }) - .attr('class', 'pieText') - .attr('dy', '.35em') - .text(function (d) { - return d.data.age; - }); - - }); - }; -}); diff --git a/src/piechart.js b/src/piechart.js new file mode 100644 index 0000000..6f938ee --- /dev/null +++ b/src/piechart.js @@ -0,0 +1,162 @@ +/** + * Pie chart module + */ +BootstrapD3.module('PieChart', function ($, d3, utils) { + 'use strict'; + + //Default configuration + var defaultConfig = { + arc: { + attr: { + 'class': 'arc' + }, + }, + colors: [ + '#049cdb', + //'#0064cd', //same as tooltip color + '#46a546', + '#9d261d', + '#ffc40d', + '#f89406', + '#c3325f', + '#7a43b6' + ], + data: {}, + label: { + attr: { + 'class': 'label', + 'dy': '.35em' + }, + offset: [50, 0], + text: 'label' + }, + stage: { + attr: { + 'class': 'bootstrapd3-stage' + }, + width: 960, + height: 500 + }, + target: 'body', + tooltip: { + attr: { + 'class': 'tooltip' + }, + text: 'tooltip' + }, + x: '', + y: '', + __params: { + OLD_FILL_PROPERTY: 'data-oldFill' + } + }; + + function builder(error, data, config) { + + //Error traps + //TODO: Make them more informative + if(error) { + throw new Error('Error in loaded data: ' + error); + } + if(!data) { + throw new Error('Data is undefined'); + } + + //Color range + //TODO: Dynamically generate colors from Bootstrap base colors depending on data + var color = d3.scale.ordinal() + .range(config.colors); + + //Pie function + var pie = d3.layout.pie() + .sort(null) + .value(function (d) { + return d[config.y]; + }); + + //Create arc function + var arc = d3.svg.arc() + .outerRadius(config.arc.radius - 10) + .innerRadius(0); + + //Create our svg stage + var svg = d3.select(config.target) + .append('svg') + .attr(config.stage.attr) + .append('g') + .attr('transform', 'translate(' + config.stage.width / 2 + ',' + config.stage.height / 2 + ')'); + //TODO: These types of attributes should be accessible, which means + //internals must be visible to users somehow. + + //Tooltips + var tip = d3.tip() + .attr('class', 'tooltip') + .offset([50, 0]) + .html(config.tooltip.text); + //.attr(config.tooltip.attr); + //Passing an object as an argument to tip.attr returns a native + //selection rather than a d3-tip function which causes the tip to + //fire an error. The following patch should do the trick. + $.each(config.tooltip.attr, function (key, value) { + tip.attr(key, value); + }); + + //Create a slice group + var slice = svg.selectAll('.slice') + .data(pie(data)) + .enter() + .append('g') + .call(tip) + .on('mouseover', tip.show) + .on('mouseout', tip.hide); + + //Arcs + slice.append('path') + .attr(config.arc.attr) + .attr('d', arc) + .style('fill', function (d) { + return color(d.data[config.x]); + }) + .on('mouseover', function (d, i) { + var currentColor = this.style.fill.substr(1); + var newShade = utils.shadeColor(currentColor, 10); + this[config.OLD_FILL_PROPERTY] = currentColor; + this.style.fill = '#' + newShade; + }) + .on('mouseout', function (d, i) { + this.style.fill = '#' + this[config.OLD_FILL_PROPERTY]; + }); + + //Labels + //TODO: Text movable outside the pie + slice.append('text') + .attr(config.label.attr) + .attr('transform', function (d) { + return 'translate(' + arc.centroid(d) + ')'; + }) + .text(config.label.text); + + } + + //Exposed functions + return function (userConfig) { + + //Merge config + var config = $.extend(true, {}, defaultConfig, userConfig); + + //Post-merge config + config.arc.radius = Math.min(config.stage.width, config.stage.height) / 2; + + //Load data accordingly + if(config.data instanceof Array) { + //If data is already an Array of data + builder.call(null, null, config.data, config); + } else if(typeof config.data === 'string') { + //If string, assume it's a url + d3.json(config.data, function (error, data) { + builder.call(null, error, data, config); + }); + } + + }; +}); diff --git a/src/stacked-bar-chart.js b/src/stacked-bar-chart.js deleted file mode 100644 index 8b13789..0000000 --- a/src/stacked-bar-chart.js +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/stackedbarchart.js b/src/stackedbarchart.js new file mode 100644 index 0000000..a1a4d09 --- /dev/null +++ b/src/stackedbarchart.js @@ -0,0 +1,250 @@ +BootstrapD3.module('StackedBarChart', function ($, d3, utils) { + 'use strict'; + + //Default configuration + var defaultConfig = { + colors: [ + '#049cdb', + //'#0064cd', //same as tooltip color + '#46a546', + '#9d261d', + '#ffc40d', + '#f89406', + '#c3325f', + '#7a43b6' + ], + data: {}, + legend: { + attr: { + 'class': 'legend' + }, + squares: { + attr: { + 'class': 'legendColor', + 'width': 18, + 'height': 18 + } + }, + text: { + attr: { + dy: '.35em' + }, + text: function (d) { + return d; + } + } + }, + margin: { + top: 20, + right: 20, + bottom: 30, + left: 40 + }, + stage: { + attr: { + 'class': 'bootstrapd3-stage' + }, + width: 960, + height: 500 + }, + target: 'body', + tooltip: { + attr: { + 'class': 'tooltip' + }, + text: 'tooltip' + }, + x: '', + y: '', + __params: { + OLD_FILL_PROPERTY: 'data-oldFill' + } + }; + + function builder(error, data, config) { + + //Some modified dimensions + var margin = config.margin; + var width = config.stage.width - margin.left - margin.right; + var height = config.stage.height - margin.top - margin.bottom; + + //Dimension ranges + var x = d3.scale.ordinal() + .rangeRoundBands([0, width], 0.1); + + var y = d3.scale.linear() + .rangeRound([height, 0]); + + //Color range + var color = d3.scale.ordinal() + .range(config.colors); + + //Axis functions + var xAxis = d3.svg.axis() + .scale(x) + .orient('bottom'); + + var yAxis = d3.svg.axis() + .scale(y) + .orient('left') + .tickFormat(d3.format('.2s')); + + //Tooltip + var tip = d3.tip() + .offset([-10, 0]) + .html(config.tooltip.text) + .attr('class', 'tooltip'); + //Passing an object as an argument to tip.attr returns a native selection + //rather than a d3-tip function which causes the tip to fire an error. + //.attr(config.tooltip.attr); + + //The following patch should do the trick + $.each(config.tooltip.attr, function (key, value) { + tip.attr(key, value); + }); + + //Stage + var svg = d3.select(config.target) + .append('svg') + .attr(config.stage.attr) + .attr('width', config.stage.width) + .attr('height', config.stage.height) + .append('g') + .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //Filter out the x from the data set for colors + color.domain(d3.keys(data[0]) + .filter(function (key) { + return key !== config.x; + })); + + //TODO: Find actual numbers + data.forEach(function (d) { + var y0 = 0; + d.ages = color.domain() + .map(function (name) { + return { + name: name, + value: d[name], + y0: y0, + y1: y0 += +d[name] + }; + }); + d.total = d.ages[d.ages.length - 1].y1; + }); + + //Sort greatest to least + data.sort(function (a, b) { + return b.total - a.total; + }); + + //Fetch the data for labels in data set + x.domain(data.map(function (d) { + return d[config.x]; + })); + + y.domain([0, d3.max(data, function (d) { + return d.total; + })]); + + //Axis + svg.append('g') + .attr('class', 'x axis') + .attr('transform', 'translate(0,' + height + ')') + .call(xAxis); + + svg.append('g') + .attr('class', 'y axis') + .call(yAxis) + .append('text') + .attr('transform', 'rotate(-90)') + .attr('y', 6) + .attr('dy', '.71em') + .style('text-anchor', 'end'); + //.text('Population'); + + //Columns + var state = svg.selectAll('.state') + .data(data) + .enter() + .append('g') + .attr('class', 'g') + .attr('transform', function (d) { + return 'translate(' + x(d.State) + ',0)'; + }); + + //blocks + state.selectAll('rect') + .data(function (d) { + return d.ages; + }) + .enter() + .append('rect') + .attr('width', x.rangeBand()) + .attr('y', function (d) { + return y(d.y1); + }) + .attr('height', function (d) { + return y(d.y0) - y(d.y1); + }) + .style('fill', function (d) { + return color(d.name); + }) + .call(tip) + .on('mouseover', function (d, i) { + var currentColor = this.style.fill.substr(1); + var newShade = utils.shadeColor(currentColor, 30); + this[config.OLD_FILL_PROPERTY] = currentColor; + this.style.fill = '#' + newShade; + tip.show.apply(this, arguments); + }) + .on('mouseout', function (d, i) { + this.style.fill = '#' + this[config.OLD_FILL_PROPERTY]; + tip.hide.apply(this, arguments); + }); + + //Legend + var legend = svg.selectAll('.legend') + .data(color.domain() + .slice() + .reverse()) + .enter() + .append('g') + .attr(config.legend.attr) + .attr('transform', function (d, i) { + return 'translate(0,' + i * 20 + ')'; + }); + + legend.append('rect') + .attr(config.legend.squares.attr) + .attr('x', width - 18) + .style('fill', color); + + legend.append('text') + .attr(config.legend.text.attr) + .attr('x', width - 24) + .attr('y', 9) + .style('text-anchor', 'end') + .text(config.legend.text.text); + + } + + //Exposed functions + return function (userConfig) { + + //Merge default and user config to final config + var config = $.extend(true, {}, defaultConfig, userConfig); + + //Load data accordingly + if(config.data instanceof Array) { + //If data is already an Array of data + builder(null, config.data, config); + } else if(typeof config.data === 'string') { + //If string, assume it's a url + d3.json(config.data, function (error, data) { + builder(error, data, config); + }); + } + }; + +});