├── dcplot.min.css ├── src ├── banner.js ├── footer.js ├── core.js ├── reduce.js ├── engine.js └── charts.js ├── scripts ├── pre-commit.sh └── check_merge_conflict.py ├── LICENSE ├── README.md ├── LICENSE_BANNER ├── package.json ├── .jshintrc ├── dataframe.js ├── Gruntfile.js ├── dcplot.min.js ├── dcplot.min.js.map └── dcplot.js /dcplot.min.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/banner.js: -------------------------------------------------------------------------------- 1 | (function() { function _dcplot(dc, crossfilter, _) { 2 | 'use strict'; 3 | -------------------------------------------------------------------------------- /scripts/pre-commit.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # probably a dumb way to detect changed files which are not deleted 4 | scripts/check_merge_conflict.py `comm -12 <(git diff --name-only --cached) <(git ls-files)` && grunt lint 5 | -------------------------------------------------------------------------------- /scripts/check_merge_conflict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # from https://github.com/pre-commit/pre-commit-hooks 3 | # and made more blunt 4 | from __future__ import print_function 5 | 6 | import argparse 7 | import os.path 8 | import sys 9 | 10 | CONFLICT_PATTERNS = [ 11 | '<<<<<<< ', 12 | '======= ', 13 | '=======\n', 14 | '>>>>>>> ' 15 | ] 16 | WARNING_MSG = 'Merge conflict string "{0}" found in {1}:{2}' 17 | 18 | 19 | def detect_merge_conflict(argv=None): 20 | parser = argparse.ArgumentParser() 21 | parser.add_argument('filenames', nargs='*') 22 | args = parser.parse_args(argv) 23 | 24 | retcode = 0 25 | for filename in args.filenames: 26 | with open(filename) as inputfile: 27 | for i, line in enumerate(inputfile): 28 | for pattern in CONFLICT_PATTERNS: 29 | if line.startswith(pattern): 30 | print(WARNING_MSG.format(pattern, filename, i + 1)) 31 | retcode = 1 32 | 33 | return retcode 34 | 35 | if __name__ == '__main__': 36 | sys.exit(detect_merge_conflict()) 37 | -------------------------------------------------------------------------------- /src/footer.js: -------------------------------------------------------------------------------- 1 | // Expose d3 and crossfilter, so that clients in browserify 2 | // case can obtain them if they need them. 3 | dcplot.dc = dc; 4 | dcplot.crossfilter = crossfilter; 5 | 6 | return dcplot;} 7 | if(typeof define === "function" && define.amd) { 8 | define(["dc", "crossfilter", "underscore"], _dcplot); 9 | } else if(typeof module === "object" && module.exports) { 10 | var _dc = require('dc'); 11 | var _crossfilter = require('crossfilter'); 12 | var _ = require('underscore'); 13 | // When using npm + browserify, 'crossfilter' is a function, 14 | // since package.json specifies index.js as main function, and it 15 | // does special handling. When using bower + browserify, 16 | // there's no main in bower.json (in fact, there's no bower.json), 17 | // so we need to fix it. 18 | if (typeof _crossfilter !== "function") { 19 | _crossfilter = _crossfilter.crossfilter; 20 | } 21 | module.exports = _dcplot(_dc, _crossfilter, _); 22 | } else { 23 | this.dc = _dc(dc, crossfilter, _); 24 | } 25 | } 26 | )(); 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2013 AT&T Intellectual Property 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dcplot.js - a minimal interface to dc.js 2 | 3 | dcplot.js offers an embedded domain specific language 4 | ([EDSL](http://en.wikipedia.org/wiki/Domain-specific_language)) 5 | for creating multidimensional charts using [d3.js](https://github.com/mbostock/d3), 6 | [crossfilter](http://square.github.io/crossfilter/), and 7 | [dc.js](http://dc-js.github.io/dc.js/). Its aim is to provide a tool for 8 | [Exploratory Data Analysis](http://en.wikipedia.org/wiki/Exploratory_data_analysis) 9 | in the browser. 10 | 11 | Although dc.js nicely encapsulates the powerful but challenging d3.js library, 12 | there are still a lot of chart parameters, many of which can be defaulted or 13 | inferred from other parameters or from the data. And it is easy to make a 14 | mistake and end up with no output. It is more appropriate for presentation 15 | than exploration. 16 | 17 | dcplot.js provides a terse JSON-plus-functions declarative language for specifying 18 | crossfilter data and charts all at once. Further, when used with 19 | [RCloud](https://github.com/att/rcloud) through the `wdcplot` wrapper, it provides 20 | an interactive language akin to [ggplot2](http://ggplot2.org/), to generate linked 21 | plots as fast as they can fly off the fingers. 22 | 23 | -------------------------------------------------------------------------------- /LICENSE_BANNER: -------------------------------------------------------------------------------- 1 | /*! 2 | * dcplot.js <%= conf.pkg.version %> 3 | * http://att.github.io/dcplot.js/ 4 | * Copyright (c) 2012-2013 AT&T Intellectual Property 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dcplot", 3 | "version": "0.4.2", 4 | "license": "MIT", 5 | "copyright": "2015", 6 | "description": "A terse and minimal wrapper for dc.js", 7 | "keywords": [ 8 | "visualization", 9 | "svg", 10 | "animation", 11 | "canvas", 12 | "chart", 13 | "dimensional", 14 | "crossfilter", 15 | "d3", 16 | "dc" 17 | ], 18 | "homepage": "http://att.github.io/dcplot.js/", 19 | "bugs": "https://github.com/att/dcplot.js/issues", 20 | "author": { 21 | "name": "Gordon Woodhull" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/dc-js/dc.js.git" 26 | }, 27 | "dependencies": { 28 | "crossfilter": "1.x", 29 | "d3": "3.x", 30 | "dc": "~2.0.0-beta" 31 | }, 32 | "devDependencies": { 33 | "grunt": "~0.4.5", 34 | "grunt-cli": "~0.1.13", 35 | "grunt-contrib-concat": "~0.5.0", 36 | "grunt-contrib-connect": "~0.11.2", 37 | "grunt-contrib-copy": "~0.8.0", 38 | "grunt-contrib-cssmin": "^0.14.0", 39 | "grunt-contrib-jshint": "~0.11.0", 40 | "grunt-contrib-uglify": "~0.9.1", 41 | "grunt-contrib-watch": "~0.6.1", 42 | "grunt-gh-pages": "~0.10.0", 43 | "grunt-jscs": "~2.2.0", 44 | "grunt-jsdoc-to-markdown": "~1.1.1", 45 | "grunt-shell": "~1.1.1", 46 | "load-grunt-tasks": "~3.2.0", 47 | "time-grunt": "~1.2.1" 48 | }, 49 | "scripts": { 50 | "test": "grunt test" 51 | }, 52 | "npmName": "dcplot", 53 | "npmFileMap": [ 54 | { 55 | "basePath": "/", 56 | "files": [ 57 | "dcplot.js", 58 | "dataframe.js" 59 | ] 60 | } 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": false, 3 | "camelcase": false, 4 | "curly": false, 5 | "eqeqeq": true, 6 | "forin": false, 7 | "freeze": true, 8 | "immed": true, 9 | "indent": 4, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "nonbsp": true, 14 | "nonew": true, 15 | "noempty": true, 16 | "plusplus": false, 17 | "undef": true, 18 | "unused": "vars", 19 | "strict": false, 20 | "maxparams": 10, 21 | "maxdepth": 4, 22 | "maxstatements": false, 23 | "maxcomplexity": 20, 24 | "maxlen": 140, 25 | "quotmark": "single", 26 | "asi": false, 27 | "boss": false, 28 | "debug": false, 29 | "eqnull": false, 30 | "esnext": true, 31 | "evil": false, 32 | "expr": false, 33 | "funcscope": false, 34 | "globalstrict": false, 35 | "iterator": false, 36 | "lastsemic": false, 37 | "loopfunc": false, 38 | "maxerr": 50, 39 | "multistr": false, 40 | "notypeof": false, 41 | "proto": false, 42 | "scripturl": false, 43 | "shadow": false, 44 | "supernew": false, 45 | "sub": true, 46 | "validthis": false, 47 | "noyield": false, 48 | "browser": true, 49 | "couch": false, 50 | "devel": false, 51 | "dojo": false, 52 | "jquery": false, 53 | "mootools": false, 54 | "node": true, 55 | "nonstandard": false, 56 | "prototypejs": false, 57 | "rhino": false, 58 | "worker": false, 59 | "wsh": false, 60 | "yui": false, 61 | "globals": { 62 | "console": false, 63 | "crossfilter": false, 64 | "d3": false, 65 | "dc": false, 66 | "global": false, 67 | "module": false, 68 | "process": false, 69 | "require": false, 70 | "jasmine": false, 71 | "expect": false, 72 | "describe": false, 73 | "it": false, 74 | "beforeEach": false, 75 | "afterEach": false, 76 | "spyOn": false 77 | } 78 | } -------------------------------------------------------------------------------- /dataframe.js: -------------------------------------------------------------------------------- 1 | /* an attempt to wrap dataframe access in an abstract 2 | interface that should work for other data too (?) 3 | note: test on array-of-records data! 4 | 5 | this is part of the dcplot.js library 6 | */ 7 | (function() { 8 | var dataframe = { 9 | cols: function(data) { 10 | var result = { 11 | access: function(column) { 12 | if(!(column in data)) 13 | throw "dataframe doesn't have column " + column.toString(); 14 | var columnv = data[column]; 15 | var f = function(i) { 16 | return columnv[i]; 17 | }; 18 | // access e.g. R attributes through access("col").attrs 19 | f.attrs = columnv; 20 | return f; 21 | }, 22 | index: function(i) { 23 | return i; 24 | }, 25 | num_rows: function() { 26 | for(var col in data) 27 | return data[col].length; 28 | }, 29 | has: function(k) { 30 | return k in data; 31 | }, 32 | records: function() { 33 | return _.range(0, this.num_rows()); 34 | } 35 | }; 36 | return result; 37 | }, 38 | rows: function(data) { 39 | var result = { 40 | access: function(column) { 41 | var f = function(i) { 42 | return data[i][column]; 43 | }; 44 | // to support attributes, add a field called 'attrs' 45 | // to the row array! 46 | if('attrs' in data && _.isObject(data.attrs)) 47 | f.attrs = data.attrs[column]; 48 | return f; 49 | }, 50 | // we could get rid of row indices and just use rows 51 | // except here (?) 52 | index: function(i) { 53 | return i; 54 | }, 55 | num_rows: function() { 56 | return data.length; 57 | }, 58 | has: function(k) { 59 | return k in data[0]; 60 | }, 61 | records: function() { 62 | return _.range(0, this.num_rows()); 63 | } 64 | }; 65 | return result; 66 | } 67 | }; 68 | if(typeof define === "function" && define.amd) { 69 | define(dataframe); 70 | } else if(typeof module === "object" && module.exports) { 71 | module.exports = dataframe; 72 | } else { 73 | this.dataframe = dataframe; 74 | } 75 | })(); 76 | -------------------------------------------------------------------------------- /src/core.js: -------------------------------------------------------------------------------- 1 | /* global dcplot, _ */ 2 | dcplot.version = '<%= conf.pkg.version %>'; 3 | 4 | // dc.js formats all numbers as ints - override 5 | var _psv = dc.utils.printSingleValue; 6 | dc.utils.printSingleValue = function(filter) { 7 | if(typeof(filter) === 'number') { 8 | if(filter%1 === 0) 9 | return filter; 10 | else if(filter>10000 || filter < -10000) 11 | return Math.round(filter); 12 | else 13 | return filter.toPrecision(4); 14 | } 15 | else return _psv(filter); 16 | }; 17 | 18 | dcplot.format_error = function(e) { 19 | var d3 = dc.d3; 20 | var error_report = d3.select(document.createElement('div')); 21 | error_report 22 | .append('p').text('dcplot errors!'); 23 | if(_.isArray(e)) { // expected exception: input error 24 | var tab = error_report.append('table'); 25 | var tr = tab.selectAll('tr') 26 | .data(e).enter().append('tr') 27 | .attr('valign', 'top'); 28 | tr 29 | .append('td') 30 | .text(function(d) { 31 | return d.type; 32 | }); 33 | tr 34 | .append('td').text(function(d) { 35 | return d.name.replace(/_\d*_\d*$/, ''); 36 | }); 37 | var tderr = tr.append('td'); 38 | tderr 39 | .selectAll('p').data(function(d) { 40 | return _.isArray(d.errors) ? d.errors : d.errors.toString(); 41 | }).enter().append('p') 42 | .text(function(d) { 43 | return d; 44 | }); 45 | } 46 | else // unexpected exception: probably logic error 47 | error_report.append('p').text(e.toString()); 48 | return error_report.node(); 49 | }; 50 | 51 | 52 | // generalization of _.has 53 | dcplot.mhas = function(obj) { 54 | for(var i=1; i' 25 | }, 26 | js: { 27 | src: '<%= conf.jsFiles %>', 28 | dest: '<%= conf.pkg.name %>.js' 29 | } 30 | }, 31 | uglify: { 32 | jsmin: { 33 | options: { 34 | mangle: true, 35 | compress: true, 36 | sourceMap: true, 37 | banner: '<%= conf.banner %>' 38 | }, 39 | src: '<%= conf.pkg.name %>.js', 40 | dest: '<%= conf.pkg.name %>.min.js' 41 | } 42 | }, 43 | cssmin: { 44 | options: { 45 | shorthandCompacting: false, 46 | roundingPrecision: -1 47 | }, 48 | main: { 49 | files: { 50 | '<%= conf.pkg.name %>.min.css': ['<%= conf.pkg.name %>.css'] 51 | } 52 | } 53 | }, 54 | jshint: { 55 | source: { 56 | src: [ 57 | '<%= conf.src %>/**/*.js', 58 | '!<%= conf.src %>/{banner,footer}.js', 59 | 'Gruntfile.js' 60 | ], 61 | options: { 62 | jshintrc: '.jshintrc' 63 | } 64 | } 65 | }, 66 | watch: { 67 | jsdoc2md: { 68 | files: ['<%= conf.src %>/**/*.js'], 69 | tasks: ['build', 'jsdoc2md'] 70 | }, 71 | scripts: { 72 | files: ['<%= conf.src %>/**/*.js', '<%= conf.web %>/stock.js'], 73 | tasks: ['docs'] 74 | }, 75 | jasmineRunner: { 76 | files: ['<%= conf.spec %>/**/*.js'], 77 | tasks: ['jasmine:specs:build'] 78 | }, 79 | tests: { 80 | files: ['<%= conf.src %>/**/*.js', '<%= conf.spec %>/**/*.js'], 81 | tasks: ['test'] 82 | }, 83 | reload: { 84 | files: ['<%= conf.pkg.name %>.js', 85 | '<%= conf.pkg.name %>css', 86 | '<%= conf.web %>/js/<%= conf.pkg.name %>.js', 87 | '<%= conf.web %>/css/<%= conf.pkg.name %>.css', 88 | '<%= conf.pkg.name %>.min.js'], 89 | options: { 90 | livereload: true 91 | } 92 | } 93 | }, 94 | connect: { 95 | server: { 96 | options: { 97 | port: process.env.PORT || 8888, 98 | base: '.' 99 | } 100 | } 101 | }, 102 | jsdoc2md: { 103 | dist: { 104 | src: 'dc.js', 105 | dest: 'web/docs/api-latest.md' 106 | } 107 | }, 108 | copy: { 109 | 'dc-to-gh': { 110 | files: [ 111 | { 112 | expand: true, 113 | flatten: true, 114 | src: ['<%= conf.pkg.name %>.css', '<%= conf.pkg.name %>.min.css'], 115 | dest: '<%= conf.web %>/css/' 116 | }, 117 | { 118 | expand: true, 119 | flatten: true, 120 | src: [ 121 | '<%= conf.pkg.name %>.js', 122 | '<%= conf.pkg.name %>.js.map', 123 | '<%= conf.pkg.name %>.min.js', 124 | '<%= conf.pkg.name %>.min.js.map', 125 | 'node_modules/d3/d3.js', 126 | 'node_modules/crossfilter/crossfilter.js', 127 | 'test/env-data.js' 128 | ], 129 | dest: '<%= conf.web %>/js/' 130 | } 131 | ] 132 | } 133 | }, 134 | 135 | 'gh-pages': { 136 | options: { 137 | base: '<%= conf.web %>', 138 | message: 'Synced from from master branch.' 139 | }, 140 | src: ['**'] 141 | }, 142 | shell: { 143 | hooks: { 144 | command: 'cp -n scripts/pre-commit.sh .git/hooks/pre-commit' + 145 | ' || echo \'Cowardly refusing to overwrite your existing git pre-commit hook.\'' 146 | } 147 | }, 148 | browserify: { 149 | dev: { 150 | src: '<%= conf.pkg.name %>.js', 151 | dest: 'bundle.js', 152 | options: { 153 | browserifyOptions: { 154 | standalone: 'dc' 155 | } 156 | } 157 | } 158 | } 159 | }); 160 | 161 | // task aliases 162 | grunt.registerTask('build', ['concat', 'uglify', 'cssmin']); 163 | grunt.registerTask('docs', ['build', 'copy', 'jsdoc2md', 'docco', 'fileindex']); 164 | grunt.registerTask('web', ['docs', 'gh-pages']); 165 | grunt.registerTask('server', ['docs', 'fileindex', 'jasmine:specs:build', 'connect:server', 'watch:jasmine-docs']); 166 | grunt.registerTask('test', ['build', 'jasmine:specs']); 167 | grunt.registerTask('test-browserify', ['build', 'browserify', 'jasmine:browserify']); 168 | grunt.registerTask('coverage', ['build', 'jasmine:coverage']); 169 | grunt.registerTask('ci', ['test', 'jasmine:specs:build', 'connect:server', 'saucelabs-jasmine']); 170 | grunt.registerTask('ci-pull', ['test', 'jasmine:specs:build', 'connect:server']); 171 | grunt.registerTask('lint', ['jshint']); 172 | grunt.registerTask('default', ['build', 'shell:hooks']); 173 | grunt.registerTask('jsdoc', ['build', 'jsdoc2md', 'watch:jsdoc2md']); 174 | }; 175 | 176 | module.exports.jsFiles = [ 177 | 'src/banner.js', // NOTE: keep this first 178 | 'src/core.js', 179 | 'src/reduce.js', 180 | 'src/engine.js', 181 | 'src/charts.js', 182 | 'src/footer.js' // NOTE: keep this last 183 | ]; 184 | -------------------------------------------------------------------------------- /src/engine.js: -------------------------------------------------------------------------------- 1 | /* global dcplot, _ */ 2 | /* exported dcplot */ 3 | /* 4 | many stages of filling in the blanks for dimensions, groups, and charts 5 | 6 | 1. fill in defaults for missing attributes 7 | 2. infer other missing attributes from what's there 8 | 3. check for required and unknown attributes 9 | 4. check for logical errors 10 | 5. finally, generate 11 | 12 | */ 13 | 14 | 15 | function skip_attr(a) { 16 | return a==='supported' || a==='concrete' || a==='parents'; 17 | } 18 | 19 | function parents_first_traversal(map, iter, callback) { 20 | if(!(iter in map)) 21 | throw 'unknown chart type ' + iter; 22 | var rest = Array.prototype.slice.call(arguments, 3); 23 | var curr = map[iter]; 24 | if('parents' in curr) 25 | for(var i = 0; i < curr.parents.length; ++i) 26 | parents_first_traversal.apply(null, [map, curr.parents[i], callback].concat(rest)); 27 | if(curr[callback]) 28 | curr[callback].apply(curr, rest); 29 | } 30 | 31 | /* 32 | function parents_last_traversal(map, iter, callback) { 33 | if(!(iter in map)) 34 | throw 'unknown chart type ' + iter; 35 | var rest = Array.prototype.slice.call(arguments, 3); 36 | var curr = map[iter]; 37 | if(curr[callback]) 38 | curr[callback].apply(curr, rest); 39 | if('parents' in curr) 40 | for(var i = 0; i < curr.parents.length; ++i) 41 | parents_last_traversal.apply(null, [map, curr.parents[i], callback].concat(rest)); 42 | } 43 | */ 44 | 45 | // defaults 46 | dcplot.default_definition = function(definition) { 47 | // defaults on the definition as a whole 48 | if(!definition.defreduce) 49 | definition.defreduce = dcplot.reduce.count; 50 | }; 51 | 52 | dcplot.default_dimension = function(definition, name, defn) { 53 | // nothing (yet?) 54 | }; 55 | 56 | dcplot.default_group = function(definition, name, defn, dims) { 57 | var errors = []; 58 | if(!_.has(defn, 'group')) 59 | defn.group = dcplot.group.identity; 60 | if(!_.has(defn, 'reduce')) 61 | defn.reduce = definition.defreduce; 62 | 63 | if(errors.length) 64 | throw errors; 65 | }; 66 | 67 | // inferences 68 | dcplot.infer_dimension = function(definition, name, defn) { 69 | // nothing (yet?) 70 | }; 71 | 72 | dcplot.infer_group = function(definition, name, defn, dims) { 73 | }; 74 | 75 | // check missing attrs 76 | dcplot.check_dimension_attrs = function(definition, name, defn) { 77 | }; 78 | 79 | dcplot.check_group_attrs = function(definition, name, defn) { 80 | var expected = ['dimension', 'group', 'reduce']; 81 | var k = _.keys(defn), 82 | missing = _.difference(expected, k), 83 | unknown = _.difference(k, expected), 84 | errors = []; 85 | if(missing.length) 86 | errors.push('definition is missing required attrs: ' + missing.join(', ')); 87 | if(unknown.length) 88 | errors.push('definition has unknown attrs: ' + unknown.join(', ')); 89 | 90 | if(errors.length) 91 | throw errors; 92 | }; 93 | 94 | // check logic errors 95 | dcplot.check_dimension_logic = function(definition, name, defn) { 96 | // nothing (yet?) 97 | }; 98 | 99 | dcplot.check_group_logic = function(definition, name, defn, dims) { 100 | var errors = []; 101 | if(!_.has(dims, defn.dimension)) 102 | errors.push('unknown dimension "' + defn.dimension + '"'); 103 | 104 | if(errors.length) 105 | throw errors; 106 | }; 107 | 108 | /* create uniformity between crossfilter dimension and reduce functions, 109 | and dc.js accessor functions with a simple trick: for the latter, 110 | split the input, which is {key, value}, into two params. this works 111 | because crossfilter functions work with just the 'key' 112 | 113 | i.e. in crossfilter: 114 | * dimension functions are key -> key 115 | * group.group functions are key -> key 116 | * group.reduce functions are key -> value 117 | in dc: 118 | * accessor functions are {key,value} -> whatever 119 | 120 | so instead we make them (key,value) -> whatever and then they look like 121 | crossfilter functions! 122 | */ 123 | dcplot.key_value = function(f) { return function(kv) { return f(kv.key, kv.value); }; }; 124 | 125 | 126 | function dcplot(frame, groupname, definition, chart_program) { 127 | chart_program = chart_program || dcplot.dc_chart_program; 128 | 129 | function default_chart(definition, name, defn, dims, groups) { 130 | // exclusively from chart_attrs 131 | function do_defaults(defn, type) { 132 | var cprog = chart_program[type]; 133 | if(!cprog.supported) 134 | throw 'chart type ' + type + ' not supported'; 135 | var cattrs = cprog.attributes; 136 | for(var a in cattrs) { 137 | if(skip_attr(a)) 138 | continue; 139 | if(_.has(cattrs[a], 'default') && defn[a]===undefined) 140 | defn[a] = cattrs[a].default; 141 | } 142 | // parents last 143 | if('parents' in cprog) 144 | for(var i = 0; i < cprog.parents.length; ++i) 145 | do_defaults(defn, cprog.parents[i]); 146 | 147 | } 148 | do_defaults(defn, defn.type); 149 | } 150 | 151 | function infer_chart(definition, name, defn, dims, groups) { 152 | var errors = []; 153 | parents_first_traversal(chart_program, defn.type, 'infer', 154 | definition, name, frame, defn, dims, groups, errors); 155 | if(errors.length) 156 | throw errors; 157 | } 158 | 159 | function check_chart_attrs(definition, name, defn) { 160 | function find_discreps(defn, type, missing, found) { 161 | var cprog = chart_program[type]; 162 | if(!cprog.supported) 163 | throw 'type "' + type + '" not supported'; 164 | var cattrs = cprog.attributes; 165 | if('parents' in cprog) 166 | for(var i = 0; i < cprog.parents.length; ++i) 167 | find_discreps(defn, cprog.parents[i], missing, found); 168 | for(var a in cattrs) { 169 | if(skip_attr(a)) 170 | continue; 171 | if(cattrs[a].required && defn[a]===undefined) 172 | missing.push(a); 173 | if(_.has(found, a)) 174 | found[a] = true; 175 | } 176 | } 177 | function empty_found_map(defn) { 178 | var k = _.without(_.keys(defn), 'type'), n = k.length, v = []; 179 | while(n--) v.push(false); 180 | return _.object(k,v); 181 | } 182 | var missing = [], found = empty_found_map(defn); 183 | find_discreps(defn, defn.type, missing, found); 184 | 185 | var errors = []; 186 | if(missing.length) 187 | errors.push('definition is missing required attrs: ' + missing.join(', ')); 188 | var unknown = _.map(_.reject(_.pairs(found), 189 | function(p) { return p[1]; }), 190 | function(p) { return p[0]; }); 191 | if(unknown.length) 192 | errors.push('definition has unknown attrs: ' + unknown.join(', ')); 193 | 194 | if(errors.length) 195 | throw errors; 196 | } 197 | 198 | 199 | function check_chart_logic(definition, name, defn, dims, groups) { 200 | var errors = []; 201 | parents_first_traversal(chart_program, defn.type, 'check_logic', 202 | definition, defn, dims, groups, errors); 203 | if(errors.length) 204 | throw errors; 205 | } 206 | 207 | function create_group(defn, dimensions) { 208 | return dcplot.accessor(frame, defn.reduce)(defn.group(dimensions[defn.dimension])); 209 | } 210 | 211 | function create_chart(groupname, defn, dims, groups) { 212 | var object = {}; 213 | parents_first_traversal(chart_program, defn.type, 'create', 214 | definition, object, groupname, frame, defn, dims, groups, errors); 215 | 216 | // perform any extra post-processing 217 | if(_.has(defn, 'more')) 218 | defn.more(object.chart); 219 | 220 | return object.chart; 221 | } 222 | 223 | function aggregate_errors(dimension_fn, group_fn, chart_fn) { 224 | var errors = []; 225 | for(var d in definition.dimensions) { 226 | defn = definition.dimensions[d]; 227 | try { 228 | dimension_fn(definition, d, defn); 229 | } 230 | catch(e) { 231 | errors.push({type: 'dimension', name: d, errors: e}); 232 | } 233 | } 234 | if(!_.has(definition, 'groups')) 235 | definition.groups = {}; 236 | for(var g in definition.groups) { 237 | defn = definition.groups[g]; 238 | try { 239 | group_fn(definition, g, defn, definition.dimensions); 240 | } 241 | catch(e) { 242 | errors.push({type: 'group', name: g, errors: e}); 243 | } 244 | } 245 | for(var c in definition.charts) { 246 | defn = definition.charts[c]; 247 | try { 248 | chart_fn(definition, c, defn, definition.dimensions, definition.groups); 249 | } 250 | catch(e) { 251 | errors.push({type: 'chart', name: c, errors: e}); 252 | } 253 | } 254 | return errors; 255 | } 256 | 257 | var errors = []; 258 | var defn; 259 | 260 | // first check all chart types because the traversals are unchecked 261 | for(var c in definition.charts) { 262 | defn = definition.charts[c]; 263 | if(!(defn.type in chart_program)) 264 | throw 'unknown chart type "' + defn.type + '"'; 265 | if(!chart_program[defn.type].supported) 266 | throw 'unsupported chart type "' + defn.type + '"'; 267 | if(!chart_program[defn.type].concrete) 268 | throw 'can\'t create abstract chart type "' + defn.type + '"'; 269 | } 270 | 271 | // fill in anything easily defaultable (will not happen in incremental mode) 272 | // [but are there things we only want to default after inference?] 273 | dcplot.default_definition(definition); 274 | errors = aggregate_errors(dcplot.default_dimension, dcplot.default_group, default_chart); 275 | if(errors.length) 276 | throw errors; 277 | 278 | // infer attributes from other attributes 279 | errors = aggregate_errors(dcplot.infer_dimension, dcplot.infer_group, infer_chart); 280 | if(errors.length) 281 | throw errors; 282 | 283 | // check for missing or unknown attrs 284 | errors = aggregate_errors(dcplot.check_dimension_attrs, dcplot.check_group_attrs, check_chart_attrs); 285 | if(errors.length) 286 | throw errors; 287 | 288 | // check for inconsistencies and other specific badness 289 | errors = aggregate_errors(dcplot.check_dimension_logic, dcplot.check_group_logic, check_chart_logic); 290 | if(errors.length) 291 | throw errors; 292 | 293 | console.log('dcplot charts definition:'); 294 | console.log(definition); 295 | 296 | // create / fill stuff in 297 | var dimensions = {}; 298 | var groups = {}; 299 | var charts = {}; 300 | 301 | var ndx = crossfilter(frame.records()); 302 | for(var d in definition.dimensions) { 303 | defn = definition.dimensions[d]; 304 | dimensions[d] = ndx.dimension(dcplot.accessor(frame, defn)); 305 | } 306 | for(var g in definition.groups) { 307 | defn = definition.groups[g]; 308 | groups[g] = create_group(defn, dimensions); 309 | } 310 | 311 | for(c in definition.charts) { 312 | defn = definition.charts[c]; 313 | charts[c] = create_chart(groupname, defn, dimensions, groups); 314 | } 315 | 316 | dc.renderAll(groupname); 317 | 318 | return {dataframe: frame, crossfilter: ndx, 319 | dimensions: dimensions, groups: groups, charts: charts}; 320 | } 321 | -------------------------------------------------------------------------------- /dcplot.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * dcplot.js 0.4.2 3 | * http://att.github.io/dcplot.js/ 4 | * Copyright (c) 2012-2013 AT&T Intellectual Property 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | !function(){function a(a,b,c){"use strict";function d(a){return"supported"===a||"concrete"===a||"parents"===a}function e(a,b,c){if(!(b in a))throw"unknown chart type "+b;var d=Array.prototype.slice.call(arguments,3),f=a[b];if("parents"in f)for(var g=0;g1e4||-1e4>a?Math.round(a):a.toPrecision(4):g(a)},f.format_error=function(b){var d=a.d3,e=d.select(document.createElement("div"));if(e.append("p").text("dcplot errors!"),c.isArray(b)){var f=e.append("table"),g=f.selectAll("tr").data(b).enter().append("tr").attr("valign","top");g.append("td").text(function(a){return a.type}),g.append("td").text(function(a){return a.name.replace(/_\d*_\d*$/,"")});var h=g.append("td");h.selectAll("p").data(function(a){return c.isArray(a.errors)?a.errors:a.errors.toString()}).enter().append("p").text(function(a){return a})}else e.append("p").text(b.toString());return e.node()},f.mhas=function(a){for(var b=1;b10?d3.scale.category20():d3.scale.category10()}d["color.domain"]||f.mhas(d,"color","attrs","r_attributes","levels")&&(d["color.domain"]=d.color.attrs.r_attributes.levels)},create:function(a,b,d,e,g,h,i,j){c.has(g,"color")&&b.chart.colorAccessor(f.key_value(f.accessor(e,g.color)));var k=g["color.scale"];c.has(g,"color.domain")&&k.domain(g["color.domain"]),c.has(g,"color.range")&&k.range(g["color.range"]),b.chart.colors(k)}},stackable:{supported:!0,attributes:{stack:{required:!1},"stack.levels":{required:!1}},infer:function(a,b,d,e,g,h,i){if(c.has(e,"stack")){c.has(e,"stack.levels")||(e["stack.levels"]=f.get_levels(d,g,e.stack));for(var j=e["stack.levels"],k=0;k10) ? 106 | d3.scale.category20() : d3.scale.category10(); 107 | } 108 | if(!defn['color.domain']) { 109 | // this also should be abstracted out into a plugin (RCloud-specific) 110 | if(dcplot.mhas(defn, 'color', 'attrs', 'r_attributes', 'levels')) 111 | defn['color.domain'] = defn.color.attrs.r_attributes.levels; 112 | } 113 | }, 114 | create: function(definition, object, groupname, frame, defn, dims, groups, errors) { 115 | if(_.has(defn, 'color')) 116 | object.chart.colorAccessor(dcplot.key_value(dcplot.accessor(frame, defn.color))); 117 | 118 | var scale = defn['color.scale']; 119 | if(_.has(defn, 'color.domain')) 120 | scale.domain(defn['color.domain']); 121 | if(_.has(defn, 'color.range')) 122 | scale.range(defn['color.range']); 123 | object.chart.colors(scale); 124 | } 125 | }, 126 | stackable: { 127 | supported: true, 128 | attributes: { 129 | stack: {required: false}, 130 | 'stack.levels': {required: false} 131 | }, 132 | infer: function(definition, name, frame, defn, dims, groups, errors) { 133 | if(_.has(defn,'stack')) { 134 | if(!_.has(defn,'stack.levels')) 135 | defn['stack.levels'] = dcplot.get_levels(frame, dims, defn.stack); 136 | var levels = defn['stack.levels']; 137 | 138 | // Change reduce functions to filter on stack levels 139 | for(var s = 0; s10000 || filter < -10000) 37 | return Math.round(filter); 38 | else 39 | return filter.toPrecision(4); 40 | } 41 | else return _psv(filter); 42 | }; 43 | 44 | dcplot.format_error = function(e) { 45 | var d3 = dc.d3; 46 | var error_report = d3.select(document.createElement('div')); 47 | error_report 48 | .append('p').text('dcplot errors!'); 49 | if(_.isArray(e)) { // expected exception: input error 50 | var tab = error_report.append('table'); 51 | var tr = tab.selectAll('tr') 52 | .data(e).enter().append('tr') 53 | .attr('valign', 'top'); 54 | tr 55 | .append('td') 56 | .text(function(d) { 57 | return d.type; 58 | }); 59 | tr 60 | .append('td').text(function(d) { 61 | return d.name.replace(/_\d*_\d*$/, ''); 62 | }); 63 | var tderr = tr.append('td'); 64 | tderr 65 | .selectAll('p').data(function(d) { 66 | return _.isArray(d.errors) ? d.errors : d.errors.toString(); 67 | }).enter().append('p') 68 | .text(function(d) { 69 | return d; 70 | }); 71 | } 72 | else // unexpected exception: probably logic error 73 | error_report.append('p').text(e.toString()); 74 | return error_report.node(); 75 | }; 76 | 77 | 78 | // generalization of _.has 79 | dcplot.mhas = function(obj) { 80 | for(var i=1; i key 380 | * group.group functions are key -> key 381 | * group.reduce functions are key -> value 382 | in dc: 383 | * accessor functions are {key,value} -> whatever 384 | 385 | so instead we make them (key,value) -> whatever and then they look like 386 | crossfilter functions! 387 | */ 388 | dcplot.key_value = function(f) { return function(kv) { return f(kv.key, kv.value); }; }; 389 | 390 | 391 | function dcplot(frame, groupname, definition, chart_program) { 392 | chart_program = chart_program || dcplot.dc_chart_program; 393 | 394 | function default_chart(definition, name, defn, dims, groups) { 395 | // exclusively from chart_attrs 396 | function do_defaults(defn, type) { 397 | var cprog = chart_program[type]; 398 | if(!cprog.supported) 399 | throw 'chart type ' + type + ' not supported'; 400 | var cattrs = cprog.attributes; 401 | for(var a in cattrs) { 402 | if(skip_attr(a)) 403 | continue; 404 | if(_.has(cattrs[a], 'default') && defn[a]===undefined) 405 | defn[a] = cattrs[a].default; 406 | } 407 | // parents last 408 | if('parents' in cprog) 409 | for(var i = 0; i < cprog.parents.length; ++i) 410 | do_defaults(defn, cprog.parents[i]); 411 | 412 | } 413 | do_defaults(defn, defn.type); 414 | } 415 | 416 | function infer_chart(definition, name, defn, dims, groups) { 417 | var errors = []; 418 | parents_first_traversal(chart_program, defn.type, 'infer', 419 | definition, name, frame, defn, dims, groups, errors); 420 | if(errors.length) 421 | throw errors; 422 | } 423 | 424 | function check_chart_attrs(definition, name, defn) { 425 | function find_discreps(defn, type, missing, found) { 426 | var cprog = chart_program[type]; 427 | if(!cprog.supported) 428 | throw 'type "' + type + '" not supported'; 429 | var cattrs = cprog.attributes; 430 | if('parents' in cprog) 431 | for(var i = 0; i < cprog.parents.length; ++i) 432 | find_discreps(defn, cprog.parents[i], missing, found); 433 | for(var a in cattrs) { 434 | if(skip_attr(a)) 435 | continue; 436 | if(cattrs[a].required && defn[a]===undefined) 437 | missing.push(a); 438 | if(_.has(found, a)) 439 | found[a] = true; 440 | } 441 | } 442 | function empty_found_map(defn) { 443 | var k = _.without(_.keys(defn), 'type'), n = k.length, v = []; 444 | while(n--) v.push(false); 445 | return _.object(k,v); 446 | } 447 | var missing = [], found = empty_found_map(defn); 448 | find_discreps(defn, defn.type, missing, found); 449 | 450 | var errors = []; 451 | if(missing.length) 452 | errors.push('definition is missing required attrs: ' + missing.join(', ')); 453 | var unknown = _.map(_.reject(_.pairs(found), 454 | function(p) { return p[1]; }), 455 | function(p) { return p[0]; }); 456 | if(unknown.length) 457 | errors.push('definition has unknown attrs: ' + unknown.join(', ')); 458 | 459 | if(errors.length) 460 | throw errors; 461 | } 462 | 463 | 464 | function check_chart_logic(definition, name, defn, dims, groups) { 465 | var errors = []; 466 | parents_first_traversal(chart_program, defn.type, 'check_logic', 467 | definition, defn, dims, groups, errors); 468 | if(errors.length) 469 | throw errors; 470 | } 471 | 472 | function create_group(defn, dimensions) { 473 | return dcplot.accessor(frame, defn.reduce)(defn.group(dimensions[defn.dimension])); 474 | } 475 | 476 | function create_chart(groupname, defn, dims, groups) { 477 | var object = {}; 478 | parents_first_traversal(chart_program, defn.type, 'create', 479 | definition, object, groupname, frame, defn, dims, groups, errors); 480 | 481 | // perform any extra post-processing 482 | if(_.has(defn, 'more')) 483 | defn.more(object.chart); 484 | 485 | return object.chart; 486 | } 487 | 488 | function aggregate_errors(dimension_fn, group_fn, chart_fn) { 489 | var errors = []; 490 | for(var d in definition.dimensions) { 491 | defn = definition.dimensions[d]; 492 | try { 493 | dimension_fn(definition, d, defn); 494 | } 495 | catch(e) { 496 | errors.push({type: 'dimension', name: d, errors: e}); 497 | } 498 | } 499 | if(!_.has(definition, 'groups')) 500 | definition.groups = {}; 501 | for(var g in definition.groups) { 502 | defn = definition.groups[g]; 503 | try { 504 | group_fn(definition, g, defn, definition.dimensions); 505 | } 506 | catch(e) { 507 | errors.push({type: 'group', name: g, errors: e}); 508 | } 509 | } 510 | for(var c in definition.charts) { 511 | defn = definition.charts[c]; 512 | try { 513 | chart_fn(definition, c, defn, definition.dimensions, definition.groups); 514 | } 515 | catch(e) { 516 | errors.push({type: 'chart', name: c, errors: e}); 517 | } 518 | } 519 | return errors; 520 | } 521 | 522 | var errors = []; 523 | var defn; 524 | 525 | // first check all chart types because the traversals are unchecked 526 | for(var c in definition.charts) { 527 | defn = definition.charts[c]; 528 | if(!(defn.type in chart_program)) 529 | throw 'unknown chart type "' + defn.type + '"'; 530 | if(!chart_program[defn.type].supported) 531 | throw 'unsupported chart type "' + defn.type + '"'; 532 | if(!chart_program[defn.type].concrete) 533 | throw 'can\'t create abstract chart type "' + defn.type + '"'; 534 | } 535 | 536 | // fill in anything easily defaultable (will not happen in incremental mode) 537 | // [but are there things we only want to default after inference?] 538 | dcplot.default_definition(definition); 539 | errors = aggregate_errors(dcplot.default_dimension, dcplot.default_group, default_chart); 540 | if(errors.length) 541 | throw errors; 542 | 543 | // infer attributes from other attributes 544 | errors = aggregate_errors(dcplot.infer_dimension, dcplot.infer_group, infer_chart); 545 | if(errors.length) 546 | throw errors; 547 | 548 | // check for missing or unknown attrs 549 | errors = aggregate_errors(dcplot.check_dimension_attrs, dcplot.check_group_attrs, check_chart_attrs); 550 | if(errors.length) 551 | throw errors; 552 | 553 | // check for inconsistencies and other specific badness 554 | errors = aggregate_errors(dcplot.check_dimension_logic, dcplot.check_group_logic, check_chart_logic); 555 | if(errors.length) 556 | throw errors; 557 | 558 | console.log('dcplot charts definition:'); 559 | console.log(definition); 560 | 561 | // create / fill stuff in 562 | var dimensions = {}; 563 | var groups = {}; 564 | var charts = {}; 565 | 566 | var ndx = crossfilter(frame.records()); 567 | for(var d in definition.dimensions) { 568 | defn = definition.dimensions[d]; 569 | dimensions[d] = ndx.dimension(dcplot.accessor(frame, defn)); 570 | } 571 | for(var g in definition.groups) { 572 | defn = definition.groups[g]; 573 | groups[g] = create_group(defn, dimensions); 574 | } 575 | 576 | for(c in definition.charts) { 577 | defn = definition.charts[c]; 578 | charts[c] = create_chart(groupname, defn, dimensions, groups); 579 | } 580 | 581 | dc.renderAll(groupname); 582 | 583 | return {dataframe: frame, crossfilter: ndx, 584 | dimensions: dimensions, groups: groups, charts: charts}; 585 | } 586 | 587 | /* global dcplot, _ */ 588 | // warning: don't put method calls for defaults which must be constructed each time! 589 | dcplot.dc_chart_program = { 590 | base: { 591 | supported: true, 592 | attributes: { 593 | div: {required: true}, // actually sent to parent selector for chart constructor 594 | title: {required: false}, // title for html in the div, handled outside this lib 595 | dimension: {required: true}, 596 | group: {required: true}, 597 | ordering: {required: false}, 598 | width: {required: true, default: 300}, 599 | height: {required: true, default: 300}, 600 | 'transition.duration': {required: false}, 601 | label: {required: false}, // or null for no labels 602 | tips: {required: false}, // dc 'title', or null for no tips 603 | more: {required: false} // executes arbitrary extra code on the dc.js chart object 604 | // key, value are terrible names: handle as variables below 605 | }, 606 | ctors: { 607 | pie: dc.pieChart, 608 | bar: dc.barChart, 609 | line: dc.lineChart, 610 | bubble: dc.bubbleChart, 611 | dataTable: dc.dataTable 612 | }, 613 | infer: function(definition, name, frame, defn, dims, groups, errors) { 614 | if(!('div' in defn)) 615 | defn.div = '#' + name; 616 | if(defn.group) { 617 | if(!groups[defn.group]) 618 | errors.push('unknown group "' + defn.group + '"'); 619 | else if(!defn.dimension) 620 | defn.dimension = groups[defn.group].dimension; 621 | } 622 | else if(defn.dimension) { 623 | if(!dims[defn.dimension]) 624 | errors.push('unknown dimension "' + defn.dimension + '"'); 625 | else { 626 | defn.group = dcplot.find_unused(groups, defn.dimension); 627 | var g = groups[defn.group] = {}; 628 | g.dimension = defn.dimension; 629 | dcplot.default_group(definition, defn.group, g, dims); 630 | dcplot.infer_group(definition, defn.group, g, dims); 631 | } 632 | } 633 | if(!_.has(defn, 'ordering')) { 634 | // note it's a little messy to have this as a property of the chart rather than 635 | // the group, but dc.js sometimes needs an ordering and sometimes doesn't 636 | var levels = dcplot.get_levels(frame, dims, defn.dimension); 637 | if(levels !== null) { 638 | var rmap = _.object(levels, _.range(levels.length)); 639 | // the ordering function uses a reverse map of the levels 640 | defn.ordering = function(p) { 641 | return rmap[p.key]; 642 | }; 643 | } 644 | } 645 | }, 646 | check_logic: function(definition, defn, dims, groups, errors) { 647 | if(defn.dimension && defn.dimension!==groups[defn.group].dimension) 648 | errors.push('group "' + defn.group + '" dimension "' + groups[defn.group].dimension + 649 | '" does not match chart dimension "' + defn.dimension + '"'); 650 | }, 651 | create: function(definition, object, groupname, frame, defn, dims, groups, errors) { 652 | var ctor = this.ctors[defn.type]; 653 | var chart = ctor(defn.div, groupname); 654 | chart.dimension(dims[defn.dimension]) 655 | .group(groups[defn.group]) 656 | .width(defn.width) 657 | .height(defn.height); 658 | if(_.has(defn, 'ordering')) 659 | chart.ordering(defn.ordering); 660 | if(_.has(defn, 'transition.duration')) 661 | chart.transitionDuration(defn['transition.duration']); 662 | if(_.has(defn, 'label')) { 663 | if(defn.label) 664 | chart.label(dcplot.key_value(defn.label)); 665 | else 666 | chart.renderLabel(false); 667 | } 668 | if(_.has(defn, 'tips')) { 669 | if(defn.tips) 670 | chart.title(dcplot.key_value(defn.tips)); 671 | else 672 | chart.renderTitle(false); 673 | } 674 | object.chart = chart; 675 | } 676 | }, 677 | color: { 678 | supported: true, 679 | attributes: { 680 | color: {required: false}, // colorAccessor 681 | 'color.scale': {required: false}, // the d3 way not the dc way 682 | 'color.domain': {required: false}, 683 | 'color.range': {required: false} 684 | }, 685 | infer: function(definition, name, frame, defn, dims, groups, errors) { 686 | if(!defn['color.scale']) { 687 | // note stackable bleeds in here: since they are on different branches 688 | // of the hierarchy, there is no sensible way for stackable to override 689 | // color here 690 | var levels = dcplot.get_levels(frame, dims, defn.stack || defn.dimension); 691 | defn['color.scale'] = (levels !== null && levels.length>10) ? 692 | d3.scale.category20() : d3.scale.category10(); 693 | } 694 | if(!defn['color.domain']) { 695 | // this also should be abstracted out into a plugin (RCloud-specific) 696 | if(dcplot.mhas(defn, 'color', 'attrs', 'r_attributes', 'levels')) 697 | defn['color.domain'] = defn.color.attrs.r_attributes.levels; 698 | } 699 | }, 700 | create: function(definition, object, groupname, frame, defn, dims, groups, errors) { 701 | if(_.has(defn, 'color')) 702 | object.chart.colorAccessor(dcplot.key_value(dcplot.accessor(frame, defn.color))); 703 | 704 | var scale = defn['color.scale']; 705 | if(_.has(defn, 'color.domain')) 706 | scale.domain(defn['color.domain']); 707 | if(_.has(defn, 'color.range')) 708 | scale.range(defn['color.range']); 709 | object.chart.colors(scale); 710 | } 711 | }, 712 | stackable: { 713 | supported: true, 714 | attributes: { 715 | stack: {required: false}, 716 | 'stack.levels': {required: false} 717 | }, 718 | infer: function(definition, name, frame, defn, dims, groups, errors) { 719 | if(_.has(defn,'stack')) { 720 | if(!_.has(defn,'stack.levels')) 721 | defn['stack.levels'] = dcplot.get_levels(frame, dims, defn.stack); 722 | var levels = defn['stack.levels']; 723 | 724 | // Change reduce functions to filter on stack levels 725 | for(var s = 0; s