From eb3ff3a70c9654ed05f96d7df31594698126e81a Mon Sep 17 00:00:00 2001 From: fskreuz Date: Sat, 22 Jun 2013 08:26:18 +0800 Subject: [PATCH 1/4] Added missing bower dependencies. Initial implementation of tooltips using d3-tip. Updated readme. --- Gruntfile.js | 7 ++- README.md | 32 +++++++++----- bower.json | 10 +++++ dist/bootstrap-d3.js | 62 ++++++++++++++++++--------- dist/bootstrap-d3.min.js | 4 +- examples/bower.json | 9 ---- examples/data.csv | 8 ---- examples/multi-series-line-chart.html | 0 examples/pie-chart.html | 51 ++++++++++++++++++++-- examples/stacked-bar-chart.html | 0 src/base.js | 47 ++++++++++++++++++++ src/multi-series-line-chart.js | 1 - src/{pie-chart.js => piechart.js} | 51 +++++++--------------- src/stacked-bar-chart.js | 1 - 14 files changed, 189 insertions(+), 94 deletions(-) create mode 100644 bower.json delete mode 100644 examples/bower.json delete mode 100644 examples/data.csv delete mode 100644 examples/multi-series-line-chart.html delete mode 100644 examples/stacked-bar-chart.html create mode 100644 src/base.js delete mode 100644 src/multi-series-line-chart.js rename src/{pie-chart.js => piechart.js} (71%) delete mode 100644 src/stacked-bar-chart.js diff --git a/Gruntfile.js b/Gruntfile.js index 96382f0..437fa6e 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -48,14 +48,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: [ 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..35e92dd --- /dev/null +++ b/bower.json @@ -0,0 +1,10 @@ +{ + "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" + } +} \ No newline at end of file diff --git a/dist/bootstrap-d3.js b/dist/bootstrap-d3.js index 2171bf8..6ee0442 100644 --- a/dist/bootstrap-d3.js +++ b/dist/bootstrap-d3.js @@ -1,6 +1,6 @@ /* bootstrap-d3 v0.0.0 -2013-06-21 +2013-06-22 https://github.com/openkas/bootstrap-d3 Contributors: @@ -9,13 +9,7 @@ Marijn van der Zee http://serraict.com License: BSD */ - - -//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) { +(function (window, BootstrapD3, $, d3) { //Utility functions common through all modules var utils = {}; @@ -32,20 +26,38 @@ var BootStrapD3; '#7a43b6' ]; - exports.module = function (name, definition) { + BootstrapD3.module = function (name, definition) { //Create an object which the module will attach exported stuff to - var moduleExport = exports[name] = {}; + var moduleExport = BootstrapD3[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) { + //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('PieChart', function (exports, $, d3, utils) { //Default pie configuration var defaultConfig = {}; @@ -61,7 +73,7 @@ BootStrapD3.module('pieChart', function (exports, $, d3, utils) { var config = $.extend({}, defaultConfig, userConfig); //Create color auto-picker - //TODO: Dynamically generate colors from Bootstrap base colors + //TODO: Dynamically generate colors from Bootstrap base colors depending on data var color = d3.scale.ordinal() .range(utils.baseColors); @@ -77,6 +89,14 @@ BootStrapD3.module('pieChart', function (exports, $, d3, utils) { return d.population; }); + //tooltip + var tip = d3.tip() + .attr('class', 'd3-tip') + .offset([50, 0]) + .html(function (d, i) { + return "Population: " + d.data.population; + }); + //Create our svg stage and create a centered group for our pie var svg = d3.select('body') .append('svg') @@ -86,7 +106,7 @@ BootStrapD3.module('pieChart', function (exports, $, d3, utils) { .attr('transform', 'translate(' + config.width / 2 + ',' + config.height / 2 + ')'); //Load data - d3.json('data.json', function (error, data) { + d3.json(config.data, function (error, data) { //Error traps //TODO: Make them more informative @@ -103,13 +123,15 @@ BootStrapD3.module('pieChart', function (exports, $, d3, utils) { .data(pie(data)) .enter() .append('g') - .attr('class', 'arc'); + .attr('class', 'arc') + .call(tip) + .on('mouseover', tip.show) + .on('mouseout', tip.hide); //Arcs are just closed paths g.append('path') .attr('d', arc) .style('fill', function (d) { - console.log(d); return color(d.data.age); }); @@ -131,5 +153,3 @@ BootStrapD3.module('pieChart', function (exports, $, d3, utils) { }); }; }); - - diff --git a/dist/bootstrap-d3.min.js b/dist/bootstrap-d3.min.js index 6e827c0..4ebaa32 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-06-22 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){var e={};e.baseColors=["#049cdb","#0064cd","#46a546","#9d261d","#ffc40d","#f89406","#c3325f","#7a43b6"],b.module=function(a,f){var g=b[a]={};f.call(null,g,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("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.tip().attr("class","d3-tip").offset([50,0]).html(function(a){return"Population: "+a.data.population}),k=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(f.data,function(a,b){if(a)throw new Error("Error in loaded data:"+a);if(!b)throw new Error("Data is undefined");var c=k.selectAll(".arc").data(i(b)).enter().append("g").attr("class","arc").call(j).on("mouseover",j.show).on("mouseout",j.hide);c.append("path").attr("d",h).style("fill",function(a){return 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 diff --git a/examples/bower.json b/examples/bower.json deleted file mode 100644 index 30b3003..0000000 --- a/examples/bower.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "examples", - "version": "0.0.0", - "ignore": [ - "**/.*", - "node_modules", - "components" - ] -} \ No newline at end of file diff --git a/examples/data.csv b/examples/data.csv deleted file mode 100644 index 9784cea..0000000 --- a/examples/data.csv +++ /dev/null @@ -1,8 +0,0 @@ -age,population -<5,2704659 -5-13,4499890 -14-17,2159981 -18-24,3853788 -25-44,14106543 -45-64,8819342 -≥65,612463 diff --git a/examples/multi-series-line-chart.html b/examples/multi-series-line-chart.html deleted file mode 100644 index e69de29..0000000 diff --git a/examples/pie-chart.html b/examples/pie-chart.html index a763f2a..13ff61d 100644 --- a/examples/pie-chart.html +++ b/examples/pie-chart.html @@ -18,16 +18,59 @@ text-anchor:middle; } + .d3-tip { + background-color: rgb(0, 109, 204); + border:1px solid rgba(0, 0, 0, 0.247059); + border-radius: 4px; + box-shadow: rgba(255, 255, 255, 0.2) 0px 1px 0px 0px inset, rgba(0, 0, 0, 0.0470588) 0px 1px 2px 0px; + color: rgb(255, 255, 255); + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 20px; + margin: 0; + padding: 4px 12px; + text-align: center; + text-shadow: rgba(0, 0, 0, 0.247059) 0px -1px 0px; + text-transform: none; + white-space: nowrap; + word-spacing: 0px; + } + + /* Creates a small triangle extender for the tooltip */ + .d3-tip:after { + padding: 0; + margin: 0; + display: inline; + font-size: 14px; + width: 100%; + line-height: 20px; + color: rgba(0, 109, 204, 1); + position: absolute; + text-align: center; + } + + /* Style northward tooltips differently */ + .d3-tip.n:after { + content: "\25BC"; + margin: -4px 0 0 0; + top: 100%; + left: 0; + } + - - - + + + + + \ 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/src/base.js b/src/base.js new file mode 100644 index 0000000..a71879f --- /dev/null +++ b/src/base.js @@ -0,0 +1,47 @@ +(function (window, BootstrapD3, $, d3) { + + //Utility functions common through all modules + var utils = {}; + + //Bootstrap base colors + utils.baseColors = [ + '#049cdb', + '#0064cd', + '#46a546', + '#9d261d', + '#ffc40d', + '#f89406', + '#c3325f', + '#7a43b6' + ]; + + BootstrapD3.module = function (name, definition) { + + //Create an object which the module will attach exported stuff to + var moduleExport = BootstrapD3[name] = {}; + + //Execute the definition to create module + definition.call(null, moduleExport, $, 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/pie-chart.js b/src/piechart.js similarity index 71% rename from src/pie-chart.js rename to src/piechart.js index 05e407d..ac4dbcb 100644 --- a/src/pie-chart.js +++ b/src/piechart.js @@ -1,38 +1,7 @@ -//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) { +BootstrapD3.module('PieChart', function (exports, $, d3, utils) { //Default pie configuration var defaultConfig = {}; @@ -48,7 +17,7 @@ BootStrapD3.module('pieChart', function (exports, $, d3, utils) { var config = $.extend({}, defaultConfig, userConfig); //Create color auto-picker - //TODO: Dynamically generate colors from Bootstrap base colors + //TODO: Dynamically generate colors from Bootstrap base colors depending on data var color = d3.scale.ordinal() .range(utils.baseColors); @@ -64,6 +33,14 @@ BootStrapD3.module('pieChart', function (exports, $, d3, utils) { return d.population; }); + //tooltip + var tip = d3.tip() + .attr('class', 'd3-tip') + .offset([50, 0]) + .html(function (d, i) { + return "Population: " + d.data.population; + }); + //Create our svg stage and create a centered group for our pie var svg = d3.select('body') .append('svg') @@ -73,7 +50,7 @@ BootStrapD3.module('pieChart', function (exports, $, d3, utils) { .attr('transform', 'translate(' + config.width / 2 + ',' + config.height / 2 + ')'); //Load data - d3.json('data.json', function (error, data) { + d3.json(config.data, function (error, data) { //Error traps //TODO: Make them more informative @@ -90,13 +67,15 @@ BootStrapD3.module('pieChart', function (exports, $, d3, utils) { .data(pie(data)) .enter() .append('g') - .attr('class', 'arc'); + .attr('class', 'arc') + .call(tip) + .on('mouseover', tip.show) + .on('mouseout', tip.hide); //Arcs are just closed paths g.append('path') .attr('d', arc) .style('fill', function (d) { - console.log(d); return color(d.data.age); }); 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 @@ - From 20597526a2577ac8e8ae6508058fb7f710d77c2f Mon Sep 17 00:00:00 2001 From: fskreuz Date: Sat, 22 Jun 2013 19:15:03 +0800 Subject: [PATCH 2/4] Extracted most of the variable settings into a default but overridable config object. --- src/piechart.js | 178 +++++++++++++++++++++++++++++------------------- 1 file changed, 109 insertions(+), 69 deletions(-) diff --git a/src/piechart.js b/src/piechart.js index ac4dbcb..f505d95 100644 --- a/src/piechart.js +++ b/src/piechart.js @@ -4,28 +4,53 @@ 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; + var defaultConfig = { + target: 'body', + data: {}, + stage: { + attr: { + 'class': 'bootstrapd3-stage' + }, + width: 960, + height: 500 + }, + arc: { + attr: { + 'class': 'arc' + }, + }, + label: { + attr: { + 'class': 'label', + 'dy': '.35em' + }, + offset: [50, 0], + text: 'label' + }, + tooltip: { + attr: { + 'class': 'tooltip' + }, + text: 'tooltip' + }, + }; - //Exposed functions - exports.create = function (userConfig) { + function pieBuilder(error, data, config) { - //Merge default and user config to final config - var config = $.extend({}, defaultConfig, userConfig); + //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 color auto-picker //TODO: Dynamically generate colors from Bootstrap base colors depending on data 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) @@ -33,67 +58,82 @@ BootstrapD3.module('PieChart', function (exports, $, d3, utils) { return d.population; }); + //Create arc function for path to turn into an arc + var arc = d3.svg.arc() + .outerRadius(config.arc.radius - 10) + .innerRadius(0); + //tooltip var tip = d3.tip() - .attr('class', 'd3-tip') .offset([50, 0]) - .html(function (d, i) { - return "Population: " + d.data.population; - }); + .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); + }); - //Create our svg stage and create a centered group for our pie - var svg = d3.select('body') + //Create our svg stage + var svg = d3.select(config.target) .append('svg') - .attr('width', config.width) - .attr('height', config.height) + .attr(config.stage.attr); + + //Center group to center the pie + var centerStage = svg.append('g') + .attr('transform', 'translate(' + config.stage.width / 2 + ',' + config.stage.height / 2 + ')'); + + //Create a slice group + var slice = centerStage.selectAll('.slice') + .data(pie(data)) + .enter() .append('g') - .attr('transform', 'translate(' + config.width / 2 + ',' + config.height / 2 + ')'); - - //Load data - d3.json(config.data, 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') - .call(tip) - .on('mouseover', tip.show) - .on('mouseout', tip.hide); - - //Arcs are just closed paths - g.append('path') - .attr('d', arc) - .style('fill', function (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; - }); + .attr('class', 'slice') + .call(tip) + .on('mouseover', tip.show) + .on('mouseout', tip.hide); - }); + //Arcs are just closed paths + slice.append('path') + .attr(config.arc.attr) + .attr('d', arc) + .style('fill', function (d) { + return color(d.data.age); + }); + + //Add text to the center of the arc + //TODO: Move text outside the pie + // Highlight hover + slice.append('text') + .attr('transform', function (d) { + return 'translate(' + arc.centroid(d) + ')'; + }) + .attr(config.label.attr) + .text(config.label.text); + + } + + //Exposed functions + exports.create = function (userConfig) { + + //Merge default and user config to final config + var config = $.extend(true, {}, defaultConfig, userConfig); + + //The radius is calculated after config, since dimensions vary + 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 + pieBuilder(null, config.data, config); + } else if(typeof config.data === 'string') { + //If string, assume it's a url + d3.json(config.data, function (error, data) { + pieBuilder(error, data, config); + }); + } }; }); From a6777b8af7d53a1aa08caeffb064456735949b62 Mon Sep 17 00:00:00 2001 From: fskreuz Date: Mon, 24 Jun 2013 07:35:44 +0800 Subject: [PATCH 3/4] First prototypes for pie and stacked charts. --- Gruntfile.js | 11 + dist/bootstrap-d3.js | 744 +++++++++++++++++++--- dist/bootstrap-d3.min.js | 4 +- examples/pie-chart.html | 158 ++--- examples/{data.json => piechartdata.json} | 0 examples/stacked-bar-chart.html | 97 +++ examples/stackedbarchartdata.json | 512 +++++++++++++++ package.json | 3 +- src/base.js | 32 +- src/multiserieslinechart.js | 261 ++++++++ src/piechart.js | 117 ++-- src/stackedbarchart.js | 250 ++++++++ 12 files changed, 1965 insertions(+), 224 deletions(-) rename examples/{data.json => piechartdata.json} (100%) create mode 100644 examples/stacked-bar-chart.html create mode 100644 examples/stackedbarchartdata.json create mode 100644 src/multiserieslinechart.js create mode 100644 src/stackedbarchart.js diff --git a/Gruntfile.js b/Gruntfile.js index 437fa6e..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, @@ -118,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/dist/bootstrap-d3.js b/dist/bootstrap-d3.js index 6ee0442..42d4bb1 100644 --- a/dist/bootstrap-d3.js +++ b/dist/bootstrap-d3.js @@ -1,6 +1,6 @@ /* bootstrap-d3 v0.0.0 -2013-06-22 +2013-06-24 https://github.com/openkas/bootstrap-d3 Contributors: @@ -10,29 +10,27 @@ Marijn van der Zee http://serraict.com License: BSD */ (function (window, BootstrapD3, $, d3) { + 'use strict'; //Utility functions common through all modules var utils = {}; - //Bootstrap base colors - utils.baseColors = [ - '#049cdb', - '#0064cd', - '#46a546', - '#9d261d', - '#ffc40d', - '#f89406', - '#c3325f', - '#7a43b6' - ]; + 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) { - - //Create an object which the module will attach exported stuff to - var moduleExport = BootstrapD3[name] = {}; - - //Execute the definition to create module - definition.call(null, moduleExport, $, d3, utils); + BootstrapD3[name] = definition.call(null, $, d3, utils); }; //Cross-module patcher @@ -57,99 +55,675 @@ License: BSD }(this, {}, jQuery, d3)); -BootstrapD3.module('PieChart', function (exports, $, d3, utils) { +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]); + }); - //Default pie configuration - var defaultConfig = {}; + 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; + })); - defaultConfig.width = 960; - defaultConfig.height = 500; - defaultConfig.radius = Math.min(defaultConfig.width, defaultConfig.height) / 2; + 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 - 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 + //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); + }); + } + }; + +}); + +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(utils.baseColors); + .range(config.colors); - //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 + //Pie function var pie = d3.layout.pie() .sort(null) .value(function (d) { - return d.population; + return d[config.y]; }); - //tooltip + //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', 'd3-tip') + .attr('class', 'tooltip') .offset([50, 0]) - .html(function (d, i) { - return "Population: " + d.data.population; - }); + .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 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) + //Create a slice group + var slice = svg.selectAll('.slice') + .data(pie(data)) + .enter() .append('g') - .attr('transform', 'translate(' + config.width / 2 + ',' + config.height / 2 + ')'); + .call(tip) + .on('mouseover', tip.show) + .on('mouseout', tip.hide); - //Load data - d3.json(config.data, function (error, data) { + //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]; + }); - //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'); + //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') - .call(tip) - .on('mouseover', tip.show) - .on('mouseout', tip.hide); - - //Arcs are just closed paths - g.append('path') - .attr('d', arc) - .style('fill', function (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; + + //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 + ')'); - //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; + //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 4ebaa32..360b1a4 100644 --- a/dist/bootstrap-d3.min.js +++ b/dist/bootstrap-d3.min.js @@ -1,6 +1,6 @@ /* bootstrap-d3 v0.0.0 -2013-06-22 +2013-06-24 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,function(a,b,c,d){var e={};e.baseColors=["#049cdb","#0064cd","#46a546","#9d261d","#ffc40d","#f89406","#c3325f","#7a43b6"],b.module=function(a,f){var g=b[a]={};f.call(null,g,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("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.tip().attr("class","d3-tip").offset([50,0]).html(function(a){return"Population: "+a.data.population}),k=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(f.data,function(a,b){if(a)throw new Error("Error in loaded data:"+a);if(!b)throw new Error("Data is undefined");var c=k.selectAll(".arc").data(i(b)).enter().append("g").attr("class","arc").call(j).on("mouseover",j.show).on("mouseout",j.hide);c.append("path").attr("d",h).style("fill",function(a){return 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 - - - + + + + + + + + + + + + - body { - font: 10px sans-serif; - } - - .arc path { - stroke: #fff; - } - - svg .pieText { - fill : #FFF; - font-size: 14px; - text-anchor:middle; - } - - .d3-tip { - background-color: rgb(0, 109, 204); - border:1px solid rgba(0, 0, 0, 0.247059); - border-radius: 4px; - box-shadow: rgba(255, 255, 255, 0.2) 0px 1px 0px 0px inset, rgba(0, 0, 0, 0.0470588) 0px 1px 2px 0px; - color: rgb(255, 255, 255); - font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 20px; - margin: 0; - padding: 4px 12px; - text-align: center; - text-shadow: rgba(0, 0, 0, 0.247059) 0px -1px 0px; - text-transform: none; - white-space: nowrap; - word-spacing: 0px; - } - - /* Creates a small triangle extender for the tooltip */ - .d3-tip:after { - padding: 0; - margin: 0; - display: inline; - font-size: 14px; - width: 100%; - line-height: 20px; - color: rgba(0, 109, 204, 1); - position: absolute; - text-align: center; - } - - /* Style northward tooltips differently */ - .d3-tip.n:after { - content: "\25BC"; - margin: -4px 0 0 0; - top: 100%; - left: 0; - } - - - - - - - - - - - - - \ No newline at end of file diff --git a/examples/data.json b/examples/piechartdata.json similarity index 100% rename from examples/data.json rename to examples/piechartdata.json diff --git a/examples/stacked-bar-chart.html b/examples/stacked-bar-chart.html new file mode 100644 index 0000000..c5a219a --- /dev/null +++ b/examples/stacked-bar-chart.html @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/stackedbarchartdata.json b/examples/stackedbarchartdata.json new file mode 100644 index 0000000..8463ac5 --- /dev/null +++ b/examples/stackedbarchartdata.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/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 index a71879f..ec25e17 100644 --- a/src/base.js +++ b/src/base.js @@ -1,27 +1,25 @@ (function (window, BootstrapD3, $, d3) { + 'use strict'; //Utility functions common through all modules var utils = {}; - //Bootstrap base colors - utils.baseColors = [ - '#049cdb', - '#0064cd', - '#46a546', - '#9d261d', - '#ffc40d', - '#f89406', - '#c3325f', - '#7a43b6' - ]; + 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) { - - //Create an object which the module will attach exported stuff to - var moduleExport = BootstrapD3[name] = {}; - - //Execute the definition to create module - definition.call(null, moduleExport, $, d3, utils); + BootstrapD3[name] = definition.call(null, $, d3, utils); }; //Cross-module patcher 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/piechart.js b/src/piechart.js index f505d95..6f938ee 100644 --- a/src/piechart.js +++ b/src/piechart.js @@ -1,24 +1,27 @@ /** * Pie chart module */ -BootstrapD3.module('PieChart', function (exports, $, d3, utils) { +BootstrapD3.module('PieChart', function ($, d3, utils) { + 'use strict'; - //Default pie configuration + //Default configuration var defaultConfig = { - target: 'body', - data: {}, - stage: { - attr: { - 'class': 'bootstrapd3-stage' - }, - width: 960, - height: 500 - }, arc: { attr: { 'class': 'arc' }, }, + colors: [ + '#049cdb', + //'#0064cd', //same as tooltip color + '#46a546', + '#9d261d', + '#ffc40d', + '#f89406', + '#c3325f', + '#7a43b6' + ], + data: {}, label: { attr: { 'class': 'label', @@ -27,113 +30,133 @@ BootstrapD3.module('PieChart', function (exports, $, d3, utils) { 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 pieBuilder(error, data, config) { + function builder(error, data, config) { //Error traps //TODO: Make them more informative if(error) { - throw new Error('Error in loaded data:' + error); + throw new Error('Error in loaded data: ' + error); } if(!data) { throw new Error('Data is undefined'); } - //Create color auto-picker + //Color range //TODO: Dynamically generate colors from Bootstrap base colors depending on data var color = d3.scale.ordinal() - .range(utils.baseColors); + .range(config.colors); - //Create pie function to format data to a pie + //Pie function var pie = d3.layout.pie() .sort(null) .value(function (d) { - return d.population; + return d[config.y]; }); - //Create arc function for path to turn into an arc + //Create arc function var arc = d3.svg.arc() .outerRadius(config.arc.radius - 10) .innerRadius(0); - //tooltip + //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('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. + .html(config.tooltip.text); //.attr(config.tooltip.attr); - - //The following patch should do the trick + //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 our svg stage - var svg = d3.select(config.target) - .append('svg') - .attr(config.stage.attr); - - //Center group to center the pie - var centerStage = svg.append('g') - .attr('transform', 'translate(' + config.stage.width / 2 + ',' + config.stage.height / 2 + ')'); - //Create a slice group - var slice = centerStage.selectAll('.slice') + var slice = svg.selectAll('.slice') .data(pie(data)) .enter() .append('g') - .attr('class', 'slice') .call(tip) .on('mouseover', tip.show) .on('mouseout', tip.hide); - //Arcs are just closed paths + //Arcs slice.append('path') .attr(config.arc.attr) .attr('d', arc) .style('fill', function (d) { - return color(d.data.age); + 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]; }); - //Add text to the center of the arc - //TODO: Move text outside the pie - // Highlight hover + //Labels + //TODO: Text movable outside the pie slice.append('text') + .attr(config.label.attr) .attr('transform', function (d) { return 'translate(' + arc.centroid(d) + ')'; }) - .attr(config.label.attr) .text(config.label.text); } //Exposed functions - exports.create = function (userConfig) { + return function (userConfig) { - //Merge default and user config to final config + //Merge config var config = $.extend(true, {}, defaultConfig, userConfig); - //The radius is calculated after config, since dimensions vary + //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 - pieBuilder(null, config.data, config); + 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) { - pieBuilder(error, data, config); + builder.call(null, error, data, config); }); } + }; }); 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); + }); + } + }; + +}); From e3b444b485017291475166bdf3faf4091df321ea Mon Sep 17 00:00:00 2001 From: fskreuz Date: Mon, 8 Jul 2013 01:47:28 +0800 Subject: [PATCH 4/4] First implementation of process segment container charts. --- bower.json | 3 +- dist/bootstrap-d3.js | 182 ++- dist/bootstrap-d3.min.js | 4 +- .../openkas-process-container-chart/data.json | 41 + .../openkas-process-container-chart/data.tsv | 1281 +++++++++++++++++ .../index.html | 73 + .../data.json} | 0 .../{pie-chart.html => pie-chart/index.html} | 10 +- .../data.json} | 0 .../index.html} | 10 +- src/openkas-process-container-chart.js | 179 +++ 11 files changed, 1769 insertions(+), 14 deletions(-) create mode 100644 examples/openkas-process-container-chart/data.json create mode 100644 examples/openkas-process-container-chart/data.tsv create mode 100644 examples/openkas-process-container-chart/index.html rename examples/{piechartdata.json => pie-chart/data.json} (100%) rename examples/{pie-chart.html => pie-chart/index.html} (89%) rename examples/{stackedbarchartdata.json => stacked-bar-chart/data.json} (100%) rename examples/{stacked-bar-chart.html => stacked-bar-chart/index.html} (89%) create mode 100644 src/openkas-process-container-chart.js diff --git a/bower.json b/bower.json index 35e92dd..2e984cf 100644 --- a/bower.json +++ b/bower.json @@ -5,6 +5,7 @@ "d3": "3.1.5", "jquery": "~2.0.2", "d3-tip": "~0.5.2", - "bootstrap": "~2.3.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 42d4bb1..bb75ebb 100644 --- a/dist/bootstrap-d3.js +++ b/dist/bootstrap-d3.js @@ -1,6 +1,6 @@ /* bootstrap-d3 v0.0.0 -2013-06-24 +2013-07-08 https://github.com/openkas/bootstrap-d3 Contributors: @@ -317,6 +317,186 @@ BootstrapD3.module('MultiSeriesLineChart', function ($, d3, utils) { }); +(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); + + // 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'; diff --git a/dist/bootstrap-d3.min.js b/dist/bootstrap-d3.min.js index 360b1a4..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-24 +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,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>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/piechartdata.json b/examples/pie-chart/data.json similarity index 100% rename from examples/piechartdata.json rename to examples/pie-chart/data.json diff --git a/examples/pie-chart.html b/examples/pie-chart/index.html similarity index 89% rename from examples/pie-chart.html rename to examples/pie-chart/index.html index 7db7b20..311238a 100644 --- a/examples/pie-chart.html +++ b/examples/pie-chart/index.html @@ -63,11 +63,11 @@ - - - - - + + + + + - - - - + + + + +