diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 77e6cf6..0000000 --- a/.jshintrc +++ /dev/null @@ -1,78 +0,0 @@ -{ - "bitwise": false, - "camelcase": false, - "curly": false, - "eqeqeq": true, - "forin": false, - "freeze": true, - "immed": true, - "indent": 4, - "latedef": true, - "newcap": true, - "noarg": true, - "nonbsp": true, - "nonew": true, - "noempty": true, - "plusplus": false, - "undef": true, - "unused": "vars", - "strict": false, - "maxparams": 10, - "maxdepth": 4, - "maxstatements": false, - "maxcomplexity": 20, - "maxlen": 140, - "quotmark": "single", - "asi": false, - "boss": false, - "debug": false, - "eqnull": false, - "esnext": true, - "evil": false, - "expr": false, - "funcscope": false, - "globalstrict": false, - "iterator": false, - "lastsemic": false, - "loopfunc": false, - "maxerr": 50, - "multistr": false, - "notypeof": false, - "proto": false, - "scripturl": false, - "shadow": false, - "supernew": false, - "sub": true, - "validthis": false, - "noyield": false, - "browser": true, - "couch": false, - "devel": false, - "dojo": false, - "jquery": false, - "mootools": false, - "node": true, - "nonstandard": false, - "prototypejs": false, - "rhino": false, - "worker": false, - "wsh": false, - "yui": false, - "globals": { - "console": false, - "crossfilter": false, - "d3": false, - "dc": false, - "global": false, - "module": false, - "process": false, - "require": false, - "jasmine": false, - "expect": false, - "describe": false, - "it": false, - "beforeEach": false, - "afterEach": false, - "spyOn": false - } -} \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index e508bbd..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,183 +0,0 @@ -module.exports = function (grunt) { - 'use strict'; - - require('load-grunt-tasks')(grunt, { - pattern: ['grunt-*'] - }); - require('time-grunt')(grunt); - - var config = { - src: 'src', - web: 'web', - pkg: require('./package.json'), - banner: grunt.file.read('./LICENSE_BANNER'), - jsFiles: module.exports.jsFiles - }; - - grunt.initConfig({ - conf: config, - - concat: { - options: { - process: true, - sourceMap: true, - banner: '<%= conf.banner %>' - }, - js: { - src: '<%= conf.jsFiles %>', - dest: '<%= conf.pkg.name %>.js' - } - }, - uglify: { - jsmin: { - options: { - mangle: true, - compress: true, - sourceMap: true, - banner: '<%= conf.banner %>' - }, - src: '<%= conf.pkg.name %>.js', - dest: '<%= conf.pkg.name %>.min.js' - } - }, - cssmin: { - options: { - shorthandCompacting: false, - roundingPrecision: -1 - }, - main: { - files: { - '<%= conf.pkg.name %>.min.css': ['<%= conf.pkg.name %>.css'] - } - } - }, - jshint: { - source: { - src: [ - '<%= conf.src %>/**/*.js', - '!<%= conf.src %>/{banner,footer}.js', - 'Gruntfile.js' - ], - options: { - jshintrc: '.jshintrc' - } - } - }, - watch: { - jsdoc2md: { - files: ['<%= conf.src %>/**/*.js'], - tasks: ['build', 'jsdoc2md'] - }, - scripts: { - files: ['<%= conf.src %>/**/*.js', '<%= conf.web %>/stock.js'], - tasks: ['docs'] - }, - jasmineRunner: { - files: ['<%= conf.spec %>/**/*.js'], - tasks: ['jasmine:specs:build'] - }, - tests: { - files: ['<%= conf.src %>/**/*.js', '<%= conf.spec %>/**/*.js'], - tasks: ['test'] - }, - reload: { - files: ['<%= conf.pkg.name %>.js', - '<%= conf.pkg.name %>css', - '<%= conf.web %>/js/<%= conf.pkg.name %>.js', - '<%= conf.web %>/css/<%= conf.pkg.name %>.css', - '<%= conf.pkg.name %>.min.js'], - options: { - livereload: true - } - } - }, - connect: { - server: { - options: { - port: process.env.PORT || 8888, - base: '.' - } - } - }, - jsdoc2md: { - dist: { - src: 'dc.js', - dest: 'web/docs/api-latest.md' - } - }, - copy: { - 'dc-to-gh': { - files: [ - { - expand: true, - flatten: true, - src: ['<%= conf.pkg.name %>.css', '<%= conf.pkg.name %>.min.css'], - dest: '<%= conf.web %>/css/' - }, - { - expand: true, - flatten: true, - src: [ - '<%= conf.pkg.name %>.js', - '<%= conf.pkg.name %>.js.map', - '<%= conf.pkg.name %>.min.js', - '<%= conf.pkg.name %>.min.js.map', - 'node_modules/d3/d3.js', - 'node_modules/crossfilter/crossfilter.js', - 'test/env-data.js' - ], - dest: '<%= conf.web %>/js/' - } - ] - } - }, - - 'gh-pages': { - options: { - base: '<%= conf.web %>', - message: 'Synced from from master branch.' - }, - src: ['**'] - }, - shell: { - hooks: { - command: 'cp -n scripts/pre-commit.sh .git/hooks/pre-commit' + - ' || echo \'Cowardly refusing to overwrite your existing git pre-commit hook.\'' - } - }, - browserify: { - dev: { - src: '<%= conf.pkg.name %>.js', - dest: 'bundle.js', - options: { - browserifyOptions: { - standalone: 'dc' - } - } - } - } - }); - - // task aliases - grunt.registerTask('build', ['concat', 'uglify', 'cssmin']); - grunt.registerTask('docs', ['build', 'copy', 'jsdoc2md', 'docco', 'fileindex']); - grunt.registerTask('web', ['docs', 'gh-pages']); - grunt.registerTask('server', ['docs', 'fileindex', 'jasmine:specs:build', 'connect:server', 'watch:jasmine-docs']); - grunt.registerTask('test', ['build', 'jasmine:specs']); - grunt.registerTask('test-browserify', ['build', 'browserify', 'jasmine:browserify']); - grunt.registerTask('coverage', ['build', 'jasmine:coverage']); - grunt.registerTask('ci', ['test', 'jasmine:specs:build', 'connect:server', 'saucelabs-jasmine']); - grunt.registerTask('ci-pull', ['test', 'jasmine:specs:build', 'connect:server']); - grunt.registerTask('lint', ['jshint']); - grunt.registerTask('default', ['build', 'shell:hooks']); - grunt.registerTask('jsdoc', ['build', 'jsdoc2md', 'watch:jsdoc2md']); -}; - -module.exports.jsFiles = [ - 'src/banner.js', // NOTE: keep this first - 'src/core.js', - 'src/reduce.js', - 'src/engine.js', - 'src/charts.js', - 'src/footer.js' // NOTE: keep this last -]; diff --git a/LICENSE b/LICENSE deleted file mode 100644 index b46b936..0000000 --- a/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2012-2013 AT&T Intellectual Property - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/LICENSE_BANNER b/LICENSE_BANNER deleted file mode 100644 index 79bb85c..0000000 --- a/LICENSE_BANNER +++ /dev/null @@ -1,23 +0,0 @@ -/*! - * dcplot.js <%= conf.pkg.version %> - * http://att.github.io/dcplot.js/ - * Copyright (c) 2012-2013 AT&T Intellectual Property - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ diff --git a/README.md b/README.md deleted file mode 100644 index d0d7cfe..0000000 --- a/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# dcplot.js - a minimal interface to dc.js - -dcplot.js offers an embedded domain specific language -([EDSL](http://en.wikipedia.org/wiki/Domain-specific_language)) -for creating multidimensional charts using [d3.js](https://github.com/mbostock/d3), -[crossfilter](http://square.github.io/crossfilter/), and -[dc.js](http://dc-js.github.io/dc.js/). Its aim is to provide a tool for -[Exploratory Data Analysis](http://en.wikipedia.org/wiki/Exploratory_data_analysis) -in the browser. - -Although dc.js nicely encapsulates the powerful but challenging d3.js library, -there are still a lot of chart parameters, many of which can be defaulted or -inferred from other parameters or from the data. And it is easy to make a -mistake and end up with no output. It is more appropriate for presentation -than exploration. - -dcplot.js provides a terse JSON-plus-functions declarative language for specifying -crossfilter data and charts all at once. Further, when used with -[RCloud](https://github.com/att/rcloud) through the `wdcplot` wrapper, it provides -an interactive language akin to [ggplot2](http://ggplot2.org/), to generate linked -plots as fast as they can fly off the fingers. - diff --git a/dataframe.js b/dataframe.js index b2ec5a1..e675c52 100644 --- a/dataframe.js +++ b/dataframe.js @@ -1,8 +1,6 @@ /* an attempt to wrap dataframe access in an abstract interface that should work for other data too (?) note: test on array-of-records data! - - this is part of the dcplot.js library */ (function() { var dataframe = { diff --git a/dcplot.js b/dcplot.js index e60d242..d669f40 100644 --- a/dcplot.js +++ b/dcplot.js @@ -1,1075 +1,1021 @@ -/*! - * dcplot.js 0.4.2 - * http://att.github.io/dcplot.js/ - * Copyright (c) 2012-2013 AT&T Intellectual Property - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. +/* + dcplot: a minimal interface to dc.js with ggplot-like defaulting + + takes a description in json+function format describing crossfilter dimensions and groups, + and charts. returns the resulting charts in a map. */ -(function() { function _dcplot(dc, crossfilter, _) { -'use strict'; - -/* global dcplot, _ */ -dcplot.version = '0.4.2'; - -// dc.js formats all numbers as ints - override -var _psv = dc.utils.printSingleValue; -dc.utils.printSingleValue = function(filter) { - if(typeof(filter) === 'number') { - if(filter%1 === 0) - return filter; - else if(filter>10000 || filter < -10000) - return Math.round(filter); - else - return filter.toPrecision(4); - } - else return _psv(filter); -}; - -dcplot.format_error = function(e) { - var d3 = dc.d3; - var error_report = d3.select(document.createElement('div')); - error_report - .append('p').text('dcplot errors!'); - if(_.isArray(e)) { // expected exception: input error - var tab = error_report.append('table'); - var tr = tab.selectAll('tr') - .data(e).enter().append('tr') - .attr('valign', 'top'); - tr - .append('td') - .text(function(d) { - return d.type; - }); - tr - .append('td').text(function(d) { - return d.name.replace(/_\d*_\d*$/, ''); - }); - var tderr = tr.append('td'); - tderr - .selectAll('p').data(function(d) { - return _.isArray(d.errors) ? d.errors : d.errors.toString(); - }).enter().append('p') - .text(function(d) { - return d; - }); - } - else // unexpected exception: probably logic error - error_report.append('p').text(e.toString()); - return error_report.node(); -}; - - -// generalization of _.has -dcplot.mhas = function(obj) { - for(var i=1; i key - * group.group functions are key -> key - * group.reduce functions are key -> value - in dc: - * accessor functions are {key,value} -> whatever - - so instead we make them (key,value) -> whatever and then they look like - crossfilter functions! - */ -dcplot.key_value = function(f) { return function(kv) { return f(kv.key, kv.value); }; }; - - -function dcplot(frame, groupname, definition, chart_program) { - chart_program = chart_program || dcplot.dc_chart_program; - - function default_chart(definition, name, defn, dims, groups) { - // exclusively from chart_attrs - function do_defaults(defn, type) { - var cprog = chart_program[type]; - if(!cprog.supported) - throw 'chart type ' + type + ' not supported'; - var cattrs = cprog.attributes; - for(var a in cattrs) { - if(skip_attr(a)) - continue; - if(_.has(cattrs[a], 'default') && defn[a]===undefined) - defn[a] = cattrs[a].default; + /* + many stages of filling in the blanks for dimensions, groups, and charts + + 1. fill in defaults for missing attributes + 2. infer other missing attributes from what's there + 3. check for required and unknown attributes + 4. check for logical errors + 5. finally, generate + + */ + + // a map of attr->required to check for at the end to make sure we have everything + // warning: don't put method calls for defaults which must be constructed each time! + var chart_attrs = { + base: { + supported: true, + div: {required: true}, // actually sent to parent selector for chart constructor + title: {required: false}, // title for html in the div, handled outside this lib + dimension: {required: true}, + group: {required: true}, + ordering: {required: false}, + width: {required: true, default: 300}, + height: {required: true, default: 300}, + 'transition.duration': {required: false}, + label: {required: false}, // or null for no labels + tips: {required: false}, // dc 'title', or null for no tips + more: {required: false} // executes arbitrary extra code on the dc.js chart object + // key, value are terrible names: handle as variables below + }, + color: { + supported: true, + color: {required: false}, // colorAccessor + 'color.scale': {required: false}, // the d3 way not the dc way + 'color.domain': {required: false}, + 'color.range': {required: false} + }, + stackable: { + supported: true, + stack: {required: false}, + 'stack.levels': {required: false} + }, + coordinateGrid: { + supported: true, + parents: ['base', 'color'], + margins: {required: false}, + x: {required: false}, // keyAccessor + y: {required: false}, // valueAccessor + // prob would be good to subgroup these? + 'x.ordinal': {required: false}, + 'x.scale': {required: true}, // scale component of x + 'x.domain': {required: false}, // domain component of x + 'x.units': {required: false}, // the most horrible thing EVER + 'x.round': {required: false}, + 'x.elastic': {required: false}, + 'x.padding': {required: false}, + // likewise + 'y.scale': {required: false}, + 'y.domain': {required: false}, + 'y.elastic': {required: false}, + 'y.padding': {required: false}, + gridLines: {required: false}, // horizontal and/or vertical + brush: {required: false} + // etc... + }, + pie: { + supported: true, + concrete: true, + parents: ['base', 'color'], + radius: {required: false}, + innerRadius: {required: false}, + wedge: {required: false}, // keyAccessor (okay these could just be x/y) + size: {required: false} // valueAccessor + // etc... + }, + row: { + supported: false, + parents: ['base', 'color'] + }, + bar: { + supported: true, + concrete: true, + parents: ['coordinateGrid', 'stackable'], + width: {default: 700}, + height: {default: 250}, + centerBar: {required: false}, + gap: {required: false}, + 'color.x': {default: true}, // color bars individually when not stacked + 'x.units': {required: true} // the most horrible thing EVER + }, + line: { + supported: true, + concrete: true, + parents: ['coordinateGrid', 'stackable'], + width: {default: 800}, + height: {default: 250}, + area: {required: false}, + dotRadius: {required: false} + }, + composite: { + parents: ['coordinateGrid'], + supported: false + }, + abstractBubble: { + supported: true, + parents: ['color'], + r: {default: 2}, // radiusValueAccessor + 'r.scale': {required: false}, // scale component of r + 'r.domain': {required: false} // domain component of r + }, + bubble: { + concrete: true, + parents: ['coordinateGrid', 'abstractBubble'], + width: {default: 400}, + label: {default: null}, // do not label by default; use ..key.. to label with keys + color: {default: 0}, // by default use first color in palette + supported: true, + 'r.elastic': {required: false} + }, + bubbleOverlay: { + supported: false, // this chart is a crime! + parents: ['base', 'abstractBubble'] + }, + geoCloropleth: { + supported: false + }, + dataCount: { + supported: false + }, + dataTable: { + supported: true, + concrete: true, + parents: ['base'], + columns: {required: true}, + size: {required: false}, + sortBy: {required: false} } - // parents last - if('parents' in cprog) - for(var i = 0; i < cprog.parents.length; ++i) - do_defaults(defn, cprog.parents[i]); + }; + function skip_attr(a) { + return a==='supported' || a==='concrete' || a==='parents'; } - do_defaults(defn, defn.type); - } - - function infer_chart(definition, name, defn, dims, groups) { - var errors = []; - parents_first_traversal(chart_program, defn.type, 'infer', - definition, name, frame, defn, dims, groups, errors); - if(errors.length) - throw errors; - } - function check_chart_attrs(definition, name, defn) { - function find_discreps(defn, type, missing, found) { - var cprog = chart_program[type]; - if(!cprog.supported) - throw 'type "' + type + '" not supported'; - var cattrs = cprog.attributes; - if('parents' in cprog) - for(var i = 0; i < cprog.parents.length; ++i) - find_discreps(defn, cprog.parents[i], missing, found); - for(var a in cattrs) { - if(skip_attr(a)) - continue; - if(cattrs[a].required && defn[a]===undefined) - missing.push(a); - if(_.has(found, a)) - found[a] = true; - } + function parents_first_traversal(map, iter, callbacks) { + if(!(iter in map)) + throw 'unknown chart type ' + defn.type; + var curr = map[iter]; + if('parents' in curr) + for(var i = 0; i < curr.parents.length; ++i) + parents_first_traversal(map, curr.parents[i], callbacks); + callbacks[iter](); } - function empty_found_map(defn) { - var k = _.without(_.keys(defn), 'type'), n = k.length, v = []; - while(n--) v.push(false); - return _.object(k,v); + function parents_last_traversal(map, iter, callbacks) { + if(!(iter in map)) + throw 'unknown chart type ' + defn.type; + callbacks[iter](); + var curr = map[iter]; + if('parents' in curr) + for(var i = 0; i < curr.parents.length; ++i) + parents_last_traversal(map, curr.parents[i], callbacks); } - var missing = [], found = empty_found_map(defn); - find_discreps(defn, defn.type, missing, found); - - var errors = []; - if(missing.length) - errors.push('definition is missing required attrs: ' + missing.join(', ')); - var unknown = _.map(_.reject(_.pairs(found), - function(p) { return p[1]; }), - function(p) { return p[0]; }); - if(unknown.length) - errors.push('definition has unknown attrs: ' + unknown.join(', ')); - - if(errors.length) - throw errors; - } - - - function check_chart_logic(definition, name, defn, dims, groups) { - var errors = []; - parents_first_traversal(chart_program, defn.type, 'check_logic', - definition, defn, dims, groups, errors); - if(errors.length) - throw errors; - } - - function create_group(defn, dimensions) { - return dcplot.accessor(frame, defn.reduce)(defn.group(dimensions[defn.dimension])); - } - function create_chart(groupname, defn, dims, groups) { - var object = {}; - parents_first_traversal(chart_program, defn.type, 'create', - definition, object, groupname, frame, defn, dims, groups, errors); + // dc.js formats all numbers as ints - override + var _psv = dc.utils.printSingleValue; + dc.utils.printSingleValue = function(filter) { + if(typeof(filter) === 'number') { + if(filter%1 === 0) + return filter; + else if(filter>10000 || filter < -10000) + return Math.round(filter); + else + return filter.toPrecision(4); + } + else return _psv(filter); + }; - // perform any extra post-processing - if(_.has(defn, 'more')) - defn.more(object.chart); + dcplot.format_error = function(e) { + var tab; + if(_.isArray(e)) { // expected exception: input error + tab = $(''); + $.each(e, function(i) { + var err = e[i], formatted_errors = $(''). + append($('
'); + if(_.isString(err.errors)) + formatted_errors.text(err.errors); + else if(_.isArray(err.errors)) + $.each(err.errors, function(e) { + formatted_errors.append($('

').text(err.errors[e])); + }); + else formatted_errors.text(err.errors.message.toString()); + var name = err.name.replace(/_\d*_\d*$/, ''); + tab.append($('

').text(err.type)). + append($('').text(name)). + append(formatted_errors) + ); + }); + } + else // unexpected exception: probably logic error + tab = $('

').text(e.toString()); + var error_report = $('

'). + append($('

').text('dcplot errors!')). + append(tab); + return error_report; + }; - return object.chart; - } + function dcplot(frame, groupname, definition) { - function aggregate_errors(dimension_fn, group_fn, chart_fn) { - var errors = []; - for(var d in definition.dimensions) { - defn = definition.dimensions[d]; - try { - dimension_fn(definition, d, defn); + // generalization of _.has + function mhas(obj) { + for(var i=1; i10) ? + d3.scale.category20() : d3.scale.category10(); + } + if(!defn['color.domain']) { + // this also should be abstracted out into a plugin (RCloud-specific) + if(mhas(defn, 'color', 'attrs', 'r_attributes', 'levels')) + defn['color.domain'] = defn.color.attrs.r_attributes.levels; + } + }, + stackable: function() { + if(_.has(defn,'stack')) { + if(!_.has(defn,'stack.levels')) + defn['stack.levels'] = get_levels(defn.stack); + var levels = defn['stack.levels']; + + // Change reduce functions to filter on stack levels + for(var s = 0; s10) ? - d3.scale.category20() : d3.scale.category10(); - } - if(!defn['color.domain']) { - // this also should be abstracted out into a plugin (RCloud-specific) - if(dcplot.mhas(defn, 'color', 'attrs', 'r_attributes', 'levels')) - defn['color.domain'] = defn.color.attrs.r_attributes.levels; - } - }, - create: function(definition, object, groupname, frame, defn, dims, groups, errors) { - if(_.has(defn, 'color')) - object.chart.colorAccessor(dcplot.key_value(dcplot.accessor(frame, defn.color))); - - var scale = defn['color.scale']; - if(_.has(defn, 'color.domain')) - scale.domain(defn['color.domain']); - if(_.has(defn, 'color.range')) - scale.range(defn['color.range']); - object.chart.colors(scale); - } - }, - stackable: { - supported: true, - attributes: { - stack: {required: false}, - 'stack.levels': {required: false} - }, - infer: function(definition, name, frame, defn, dims, groups, errors) { - if(_.has(defn,'stack')) { - if(!_.has(defn,'stack.levels')) - defn['stack.levels'] = dcplot.get_levels(frame, dims, defn.stack); - var levels = defn['stack.levels']; - - // Change reduce functions to filter on stack levels - for(var s = 0; s key + * group.group functions are key -> key + * group.reduce functions are key -> value + in dc: + * accessor functions are {key,value} -> whatever + + so instead we make them (key,value) -> whatever and then they look like + crossfilter functions! + */ + function key_value(f) { return function(kv) { return f(kv.key, kv.value); }; } + + var callbacks = { + base: function() { + chart = ctor(defn.div, groupname); + chart.dimension(dimensions[defn.dimension]) + .group(groups[defn.group]) + .width(defn.width) + .height(defn.height); + if(_.has(defn, 'ordering')) + chart.ordering(defn.ordering); + if(_.has(defn, 'transition.duration')) + chart.transitionDuration(defn['transition.duration']); + if(_.has(defn, 'label')) { + if(defn.label) + chart.label(key_value(defn.label)); + else + chart.renderLabel(false); + } + if(_.has(defn, 'tips')) { + if(defn.tips) + chart.title(key_value(defn.tips)); + else + chart.renderTitle(false); + } + }, + color: function() { + // i am cool with dc.js's color accessor + if(_.has(defn, 'color')) + chart.colorAccessor(key_value(accessor(defn.color))); + // however i don't understand why dc chooses to use a + // "color calculator" when a d3 scale seems like it ought + // to serve the purpose. so just plug a d3 scale into colors + // and override the calculator to use it + // also default to category10 which seems better for discrete colors + + var scale = defn['color.scale']; + if(_.has(defn, 'color.domain')) + scale.domain(defn['color.domain']); + if(_.has(defn, 'color.range')) + scale.range(defn['color.range']); + chart.colors(scale); + chart.colorCalculator(function(x) { return chart.colors()(x); }); + }, + stackable: function() { + if(_.has(defn, 'stack') && _.has(defn, 'stack.levels')) { + for(var s = 0; s10000 || filter < -10000)\n return Math.round(filter);\n else\n return filter.toPrecision(4);\n }\n else return _psv(filter);\n};\n\ndcplot.format_error = function(e) {\n var d3 = dc.d3;\n var error_report = d3.select(document.createElement('div'));\n error_report\n .append('p').text('dcplot errors!');\n if(_.isArray(e)) { // expected exception: input error\n var tab = error_report.append('table');\n var tr = tab.selectAll('tr')\n .data(e).enter().append('tr')\n .attr('valign', 'top');\n tr\n .append('td')\n .text(function(d) {\n return d.type;\n });\n tr\n .append('td').text(function(d) {\n return d.name.replace(/_\\d*_\\d*$/, '');\n });\n var tderr = tr.append('td');\n tderr\n .selectAll('p').data(function(d) {\n return _.isArray(d.errors) ? d.errors : d.errors.toString();\n }).enter().append('p')\n .text(function(d) {\n return d;\n });\n }\n else // unexpected exception: probably logic error\n error_report.append('p').text(e.toString());\n return error_report.node();\n};\n\n\n// generalization of _.has\ndcplot.mhas = function(obj) {\n for(var i=1; i key\n * group.group functions are key -> key\n * group.reduce functions are key -> value\n in dc:\n * accessor functions are {key,value} -> whatever\n\n so instead we make them (key,value) -> whatever and then they look like\n crossfilter functions!\n */\ndcplot.key_value = function(f) { return function(kv) { return f(kv.key, kv.value); }; };\n\n\nfunction dcplot(frame, groupname, definition, chart_program) {\n chart_program = chart_program || dcplot.dc_chart_program;\n\n function default_chart(definition, name, defn, dims, groups) {\n // exclusively from chart_attrs\n function do_defaults(defn, type) {\n var cprog = chart_program[type];\n if(!cprog.supported)\n throw 'chart type ' + type + ' not supported';\n var cattrs = cprog.attributes;\n for(var a in cattrs) {\n if(skip_attr(a))\n continue;\n if(_.has(cattrs[a], 'default') && defn[a]===undefined)\n defn[a] = cattrs[a].default;\n }\n // parents last\n if('parents' in cprog)\n for(var i = 0; i < cprog.parents.length; ++i)\n do_defaults(defn, cprog.parents[i]);\n\n }\n do_defaults(defn, defn.type);\n }\n\n function infer_chart(definition, name, defn, dims, groups) {\n var errors = [];\n parents_first_traversal(chart_program, defn.type, 'infer',\n definition, name, frame, defn, dims, groups, errors);\n if(errors.length)\n throw errors;\n }\n\n function check_chart_attrs(definition, name, defn) {\n function find_discreps(defn, type, missing, found) {\n var cprog = chart_program[type];\n if(!cprog.supported)\n throw 'type \"' + type + '\" not supported';\n var cattrs = cprog.attributes;\n if('parents' in cprog)\n for(var i = 0; i < cprog.parents.length; ++i)\n find_discreps(defn, cprog.parents[i], missing, found);\n for(var a in cattrs) {\n if(skip_attr(a))\n continue;\n if(cattrs[a].required && defn[a]===undefined)\n missing.push(a);\n if(_.has(found, a))\n found[a] = true;\n }\n }\n function empty_found_map(defn) {\n var k = _.without(_.keys(defn), 'type'), n = k.length, v = [];\n while(n--) v.push(false);\n return _.object(k,v);\n }\n var missing = [], found = empty_found_map(defn);\n find_discreps(defn, defn.type, missing, found);\n\n var errors = [];\n if(missing.length)\n errors.push('definition is missing required attrs: ' + missing.join(', '));\n var unknown = _.map(_.reject(_.pairs(found),\n function(p) { return p[1]; }),\n function(p) { return p[0]; });\n if(unknown.length)\n errors.push('definition has unknown attrs: ' + unknown.join(', '));\n\n if(errors.length)\n throw errors;\n }\n\n\n function check_chart_logic(definition, name, defn, dims, groups) {\n var errors = [];\n parents_first_traversal(chart_program, defn.type, 'check_logic',\n definition, defn, dims, groups, errors);\n if(errors.length)\n throw errors;\n }\n\n function create_group(defn, dimensions) {\n return dcplot.accessor(frame, defn.reduce)(defn.group(dimensions[defn.dimension]));\n }\n\n function create_chart(groupname, defn, dims, groups) {\n var object = {};\n parents_first_traversal(chart_program, defn.type, 'create',\n definition, object, groupname, frame, defn, dims, groups, errors);\n\n // perform any extra post-processing\n if(_.has(defn, 'more'))\n defn.more(object.chart);\n\n return object.chart;\n }\n\n function aggregate_errors(dimension_fn, group_fn, chart_fn) {\n var errors = [];\n for(var d in definition.dimensions) {\n defn = definition.dimensions[d];\n try {\n dimension_fn(definition, d, defn);\n }\n catch(e) {\n errors.push({type: 'dimension', name: d, errors: e});\n }\n }\n if(!_.has(definition, 'groups'))\n definition.groups = {};\n for(var g in definition.groups) {\n defn = definition.groups[g];\n try {\n group_fn(definition, g, defn, definition.dimensions);\n }\n catch(e) {\n errors.push({type: 'group', name: g, errors: e});\n }\n }\n for(var c in definition.charts) {\n defn = definition.charts[c];\n try {\n chart_fn(definition, c, defn, definition.dimensions, definition.groups);\n }\n catch(e) {\n errors.push({type: 'chart', name: c, errors: e});\n }\n }\n return errors;\n }\n\n var errors = [];\n var defn;\n\n // first check all chart types because the traversals are unchecked\n for(var c in definition.charts) {\n defn = definition.charts[c];\n if(!(defn.type in chart_program))\n throw 'unknown chart type \"' + defn.type + '\"';\n if(!chart_program[defn.type].supported)\n throw 'unsupported chart type \"' + defn.type + '\"';\n if(!chart_program[defn.type].concrete)\n throw 'can\\'t create abstract chart type \"' + defn.type + '\"';\n }\n\n // fill in anything easily defaultable (will not happen in incremental mode)\n // [but are there things we only want to default after inference?]\n dcplot.default_definition(definition);\n errors = aggregate_errors(dcplot.default_dimension, dcplot.default_group, default_chart);\n if(errors.length)\n throw errors;\n\n // infer attributes from other attributes\n errors = aggregate_errors(dcplot.infer_dimension, dcplot.infer_group, infer_chart);\n if(errors.length)\n throw errors;\n\n // check for missing or unknown attrs\n errors = aggregate_errors(dcplot.check_dimension_attrs, dcplot.check_group_attrs, check_chart_attrs);\n if(errors.length)\n throw errors;\n\n // check for inconsistencies and other specific badness\n errors = aggregate_errors(dcplot.check_dimension_logic, dcplot.check_group_logic, check_chart_logic);\n if(errors.length)\n throw errors;\n\n console.log('dcplot charts definition:');\n console.log(definition);\n\n // create / fill stuff in\n var dimensions = {};\n var groups = {};\n var charts = {};\n\n var ndx = crossfilter(frame.records());\n for(var d in definition.dimensions) {\n defn = definition.dimensions[d];\n dimensions[d] = ndx.dimension(dcplot.accessor(frame, defn));\n }\n for(var g in definition.groups) {\n defn = definition.groups[g];\n groups[g] = create_group(defn, dimensions);\n }\n\n for(c in definition.charts) {\n defn = definition.charts[c];\n charts[c] = create_chart(groupname, defn, dimensions, groups);\n }\n\n dc.renderAll(groupname);\n\n return {dataframe: frame, crossfilter: ndx,\n dimensions: dimensions, groups: groups, charts: charts};\n}\n","/* global dcplot, _ */\n// warning: don't put method calls for defaults which must be constructed each time!\ndcplot.dc_chart_program = {\n base: {\n supported: true,\n attributes: {\n div: {required: true}, // actually sent to parent selector for chart constructor\n title: {required: false}, // title for html in the div, handled outside this lib\n dimension: {required: true},\n group: {required: true},\n ordering: {required: false},\n width: {required: true, default: 300},\n height: {required: true, default: 300},\n 'transition.duration': {required: false},\n label: {required: false}, // or null for no labels\n tips: {required: false}, // dc 'title', or null for no tips\n more: {required: false} // executes arbitrary extra code on the dc.js chart object\n // key, value are terrible names: handle as variables below\n },\n ctors: {\n pie: dc.pieChart,\n bar: dc.barChart,\n line: dc.lineChart,\n bubble: dc.bubbleChart,\n dataTable: dc.dataTable\n },\n infer: function(definition, name, frame, defn, dims, groups, errors) {\n if(!('div' in defn))\n defn.div = '#' + name;\n if(defn.group) {\n if(!groups[defn.group])\n errors.push('unknown group \"' + defn.group + '\"');\n else if(!defn.dimension)\n defn.dimension = groups[defn.group].dimension;\n }\n else if(defn.dimension) {\n if(!dims[defn.dimension])\n errors.push('unknown dimension \"' + defn.dimension + '\"');\n else {\n defn.group = dcplot.find_unused(groups, defn.dimension);\n var g = groups[defn.group] = {};\n g.dimension = defn.dimension;\n dcplot.default_group(definition, defn.group, g, dims);\n dcplot.infer_group(definition, defn.group, g, dims);\n }\n }\n if(!_.has(defn, 'ordering')) {\n // note it's a little messy to have this as a property of the chart rather than\n // the group, but dc.js sometimes needs an ordering and sometimes doesn't\n var levels = dcplot.get_levels(frame, dims, defn.dimension);\n if(levels !== null) {\n var rmap = _.object(levels, _.range(levels.length));\n // the ordering function uses a reverse map of the levels\n defn.ordering = function(p) {\n return rmap[p.key];\n };\n }\n }\n },\n check_logic: function(definition, defn, dims, groups, errors) {\n if(defn.dimension && defn.dimension!==groups[defn.group].dimension)\n errors.push('group \"' + defn.group + '\" dimension \"' + groups[defn.group].dimension +\n '\" does not match chart dimension \"' + defn.dimension + '\"');\n },\n create: function(definition, object, groupname, frame, defn, dims, groups, errors) {\n var ctor = this.ctors[defn.type];\n var chart = ctor(defn.div, groupname);\n chart.dimension(dims[defn.dimension])\n .group(groups[defn.group])\n .width(defn.width)\n .height(defn.height);\n if(_.has(defn, 'ordering'))\n chart.ordering(defn.ordering);\n if(_.has(defn, 'transition.duration'))\n chart.transitionDuration(defn['transition.duration']);\n if(_.has(defn, 'label')) {\n if(defn.label)\n chart.label(dcplot.key_value(defn.label));\n else\n chart.renderLabel(false);\n }\n if(_.has(defn, 'tips')) {\n if(defn.tips)\n chart.title(dcplot.key_value(defn.tips));\n else\n chart.renderTitle(false);\n }\n object.chart = chart;\n }\n },\n color: {\n supported: true,\n attributes: {\n color: {required: false}, // colorAccessor\n 'color.scale': {required: false}, // the d3 way not the dc way\n 'color.domain': {required: false},\n 'color.range': {required: false}\n },\n infer: function(definition, name, frame, defn, dims, groups, errors) {\n if(!defn['color.scale']) {\n // note stackable bleeds in here: since they are on different branches\n // of the hierarchy, there is no sensible way for stackable to override\n // color here\n var levels = dcplot.get_levels(frame, dims, defn.stack || defn.dimension);\n defn['color.scale'] = (levels !== null && levels.length>10) ?\n d3.scale.category20() : d3.scale.category10();\n }\n if(!defn['color.domain']) {\n // this also should be abstracted out into a plugin (RCloud-specific)\n if(dcplot.mhas(defn, 'color', 'attrs', 'r_attributes', 'levels'))\n defn['color.domain'] = defn.color.attrs.r_attributes.levels;\n }\n },\n create: function(definition, object, groupname, frame, defn, dims, groups, errors) {\n if(_.has(defn, 'color'))\n object.chart.colorAccessor(dcplot.key_value(dcplot.accessor(frame, defn.color)));\n\n var scale = defn['color.scale'];\n if(_.has(defn, 'color.domain'))\n scale.domain(defn['color.domain']);\n if(_.has(defn, 'color.range'))\n scale.range(defn['color.range']);\n object.chart.colors(scale);\n }\n },\n stackable: {\n supported: true,\n attributes: {\n stack: {required: false},\n 'stack.levels': {required: false}\n },\n infer: function(definition, name, frame, defn, dims, groups, errors) {\n if(_.has(defn,'stack')) {\n if(!_.has(defn,'stack.levels'))\n defn['stack.levels'] = dcplot.get_levels(frame, dims, defn.stack);\n var levels = defn['stack.levels'];\n\n // Change reduce functions to filter on stack levels\n for(var s = 0; s1e4||-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;k>>>>>> ' -] -WARNING_MSG = 'Merge conflict string "{0}" found in {1}:{2}' - - -def detect_merge_conflict(argv=None): - parser = argparse.ArgumentParser() - parser.add_argument('filenames', nargs='*') - args = parser.parse_args(argv) - - retcode = 0 - for filename in args.filenames: - with open(filename) as inputfile: - for i, line in enumerate(inputfile): - for pattern in CONFLICT_PATTERNS: - if line.startswith(pattern): - print(WARNING_MSG.format(pattern, filename, i + 1)) - retcode = 1 - - return retcode - -if __name__ == '__main__': - sys.exit(detect_merge_conflict()) diff --git a/scripts/pre-commit.sh b/scripts/pre-commit.sh deleted file mode 100755 index 5f9ba4d..0000000 --- a/scripts/pre-commit.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash - -# probably a dumb way to detect changed files which are not deleted -scripts/check_merge_conflict.py `comm -12 <(git diff --name-only --cached) <(git ls-files)` && grunt lint diff --git a/silly/index.html b/silly/index.html new file mode 100644 index 0000000..4d6a843 --- /dev/null +++ b/silly/index.html @@ -0,0 +1,20 @@ + + + Codestin Search App + + + + + + + + + + + + + + + + + diff --git a/silly/test.dcplot.small.js b/silly/test.dcplot.small.js new file mode 100644 index 0000000..0862703 --- /dev/null +++ b/silly/test.dcplot.small.js @@ -0,0 +1,135 @@ +// simple illustration of how to use dcplot.js + +// nonsense data. the dataframe can be generated from column-major or row-major data +var data_rows = [ + {x:0, y:0.1}, + {x:0.1, y:0.2}, + {x:0.15, y:0.17}, + {x:0.3, y:0.23}, + {x:0.35, y:0.28}, + {x:0.37, y:0.3}, + {x:0.4, y:0.33}, + {x:0.5, y:0.29}, + {x:0.61, y:0.45}, + {x:0.8, y:0.51} +]; + +var data_cols = { + x: [0, 0.1, 0.15, 0.3, 0.35, 0.37, 0.4, 0.5, 0.61, 0.8], + y: [0.1, 0.2, 0.17, 0.23, 0.28, 0.3, 0.33, 0.29, 0.45, 0.51], + r: [2, 3, 4, 2, 3, 4, 2, 3, 4, 2], + c: ['a', 'b', 'c', 'b', 'b', 'a', 'a', 'a', 'b', 'c'] +}; + +// the dataframe is based on the concept from R, here we use R-like column-major data +var frame = dataframe.cols(data_cols); + +var cgname = 'chartgroup0'; + +// some annoying boilerplate to generate the filter display and reset links +// and put them in the chart divs - this could perhaps be automated better by dcplot.js +function make_chart_div(name, group_name) { + var props = {id: name, style: "float:left"}; + var reset = $('', + {class: 'reset', + href: '#', + style: "display: none;"}) + .append("reset") + .click(function(e) { + e.preventDefault(); + window.charts[name].filterAll(); + dc.redrawAll(group_name); + }); + + return $('

',props) + .append($('
') + .append($('').append(name)) + .append('  ') + .append($('', {class: 'reset', style: 'display: none;'}) + .append('Current filter: ') + .append($('', {class: 'filter'}))) + .append('  ') + .append(reset) + + ); +} + +// generate div for each chart +var divs = _.reduce(['bubs', 'lines', 'bars', 'colbars', 'series'], + function(memo, dname) { + memo[dname] = make_chart_div(dname, cgname); + return memo; + }, {}); + + +var body = $('body'); + +// okay now comes the cool part ;-) +// have you ever seen so little code to generate interactive charts? +try { + window.charts = dcplot(frame, cgname, { + dimensions: { + index: frame.index, + x: 'x', + x2: 'x', + col: 'c' + }, + groups: { + index: { dimension: 'index' }, + nongroup: { + dimension: 'x', + reduce: dcplot.reduce.any('y') + }, + bingroup: { + dimension: 'x2', + group: dcplot.group.bin(0.2) + }, + colgroup: { + dimension: 'col' + } + }, + charts: { + bubs: { + div: divs['bubs'][0], + type: 'bubble', + dimension: 'index', + width: 800, + x: 'x', + y: 'y', + color: 'c', + r: function(k,v) { return frame.access('r')(k) * v; }, + 'r.domain': [1,15], + label: null + }, + lines: { + div: divs['lines'][0], + type: 'line', + group: 'nongroup', + tips: function(x, y) { return x + ', ' + y; } + }, + bars: { + div: divs['bars'][0], + type: 'bar', + group: 'bingroup' + }, + colbars: { + div: divs['colbars'][0], + type: 'bar', + group: 'colgroup' + } + }}); + + $.each(divs, + function(d) { + body.append(divs[d]); + }); +} +catch(e) { + body.append(dcplot.format_error(e)); +} + +/* note the lines chart could also be expressed this way (more like bubble chart) + dimension: 'index', + x: 'x', + y: 'y', +*/ diff --git a/src/banner.js b/src/banner.js deleted file mode 100644 index f8bb01c..0000000 --- a/src/banner.js +++ /dev/null @@ -1,2 +0,0 @@ -(function() { function _dcplot(dc, crossfilter, _) { -'use strict'; diff --git a/src/charts.js b/src/charts.js deleted file mode 100644 index c138a8d..0000000 --- a/src/charts.js +++ /dev/null @@ -1,460 +0,0 @@ -/* global dcplot, _ */ -// warning: don't put method calls for defaults which must be constructed each time! -dcplot.dc_chart_program = { - base: { - supported: true, - attributes: { - div: {required: true}, // actually sent to parent selector for chart constructor - title: {required: false}, // title for html in the div, handled outside this lib - dimension: {required: true}, - group: {required: true}, - ordering: {required: false}, - width: {required: true, default: 300}, - height: {required: true, default: 300}, - 'transition.duration': {required: false}, - label: {required: false}, // or null for no labels - tips: {required: false}, // dc 'title', or null for no tips - more: {required: false} // executes arbitrary extra code on the dc.js chart object - // key, value are terrible names: handle as variables below - }, - ctors: { - pie: dc.pieChart, - bar: dc.barChart, - line: dc.lineChart, - bubble: dc.bubbleChart, - dataTable: dc.dataTable - }, - infer: function(definition, name, frame, defn, dims, groups, errors) { - if(!('div' in defn)) - defn.div = '#' + name; - if(defn.group) { - if(!groups[defn.group]) - errors.push('unknown group "' + defn.group + '"'); - else if(!defn.dimension) - defn.dimension = groups[defn.group].dimension; - } - else if(defn.dimension) { - if(!dims[defn.dimension]) - errors.push('unknown dimension "' + defn.dimension + '"'); - else { - defn.group = dcplot.find_unused(groups, defn.dimension); - var g = groups[defn.group] = {}; - g.dimension = defn.dimension; - dcplot.default_group(definition, defn.group, g, dims); - dcplot.infer_group(definition, defn.group, g, dims); - } - } - if(!_.has(defn, 'ordering')) { - // note it's a little messy to have this as a property of the chart rather than - // the group, but dc.js sometimes needs an ordering and sometimes doesn't - var levels = dcplot.get_levels(frame, dims, defn.dimension); - if(levels !== null) { - var rmap = _.object(levels, _.range(levels.length)); - // the ordering function uses a reverse map of the levels - defn.ordering = function(p) { - return rmap[p.key]; - }; - } - } - }, - check_logic: function(definition, defn, dims, groups, errors) { - if(defn.dimension && defn.dimension!==groups[defn.group].dimension) - errors.push('group "' + defn.group + '" dimension "' + groups[defn.group].dimension + - '" does not match chart dimension "' + defn.dimension + '"'); - }, - create: function(definition, object, groupname, frame, defn, dims, groups, errors) { - var ctor = this.ctors[defn.type]; - var chart = ctor(defn.div, groupname); - chart.dimension(dims[defn.dimension]) - .group(groups[defn.group]) - .width(defn.width) - .height(defn.height); - if(_.has(defn, 'ordering')) - chart.ordering(defn.ordering); - if(_.has(defn, 'transition.duration')) - chart.transitionDuration(defn['transition.duration']); - if(_.has(defn, 'label')) { - if(defn.label) - chart.label(dcplot.key_value(defn.label)); - else - chart.renderLabel(false); - } - if(_.has(defn, 'tips')) { - if(defn.tips) - chart.title(dcplot.key_value(defn.tips)); - else - chart.renderTitle(false); - } - object.chart = chart; - } - }, - color: { - supported: true, - attributes: { - color: {required: false}, // colorAccessor - 'color.scale': {required: false}, // the d3 way not the dc way - 'color.domain': {required: false}, - 'color.range': {required: false} - }, - infer: function(definition, name, frame, defn, dims, groups, errors) { - if(!defn['color.scale']) { - // note stackable bleeds in here: since they are on different branches - // of the hierarchy, there is no sensible way for stackable to override - // color here - var levels = dcplot.get_levels(frame, dims, defn.stack || defn.dimension); - defn['color.scale'] = (levels !== null && levels.length>10) ? - d3.scale.category20() : d3.scale.category10(); - } - if(!defn['color.domain']) { - // this also should be abstracted out into a plugin (RCloud-specific) - if(dcplot.mhas(defn, 'color', 'attrs', 'r_attributes', 'levels')) - defn['color.domain'] = defn.color.attrs.r_attributes.levels; - } - }, - create: function(definition, object, groupname, frame, defn, dims, groups, errors) { - if(_.has(defn, 'color')) - object.chart.colorAccessor(dcplot.key_value(dcplot.accessor(frame, defn.color))); - - var scale = defn['color.scale']; - if(_.has(defn, 'color.domain')) - scale.domain(defn['color.domain']); - if(_.has(defn, 'color.range')) - scale.range(defn['color.range']); - object.chart.colors(scale); - } - }, - stackable: { - supported: true, - attributes: { - stack: {required: false}, - 'stack.levels': {required: false} - }, - infer: function(definition, name, frame, defn, dims, groups, errors) { - if(_.has(defn,'stack')) { - if(!_.has(defn,'stack.levels')) - defn['stack.levels'] = dcplot.get_levels(frame, dims, defn.stack); - var levels = defn['stack.levels']; - - // Change reduce functions to filter on stack levels - for(var s = 0; s10000 || filter < -10000) - return Math.round(filter); - else - return filter.toPrecision(4); - } - else return _psv(filter); -}; - -dcplot.format_error = function(e) { - var d3 = dc.d3; - var error_report = d3.select(document.createElement('div')); - error_report - .append('p').text('dcplot errors!'); - if(_.isArray(e)) { // expected exception: input error - var tab = error_report.append('table'); - var tr = tab.selectAll('tr') - .data(e).enter().append('tr') - .attr('valign', 'top'); - tr - .append('td') - .text(function(d) { - return d.type; - }); - tr - .append('td').text(function(d) { - return d.name.replace(/_\d*_\d*$/, ''); - }); - var tderr = tr.append('td'); - tderr - .selectAll('p').data(function(d) { - return _.isArray(d.errors) ? d.errors : d.errors.toString(); - }).enter().append('p') - .text(function(d) { - return d; - }); - } - else // unexpected exception: probably logic error - error_report.append('p').text(e.toString()); - return error_report.node(); -}; - - -// generalization of _.has -dcplot.mhas = function(obj) { - for(var i=1; i key - * group.group functions are key -> key - * group.reduce functions are key -> value - in dc: - * accessor functions are {key,value} -> whatever - - so instead we make them (key,value) -> whatever and then they look like - crossfilter functions! - */ -dcplot.key_value = function(f) { return function(kv) { return f(kv.key, kv.value); }; }; - - -function dcplot(frame, groupname, definition, chart_program) { - chart_program = chart_program || dcplot.dc_chart_program; - - function default_chart(definition, name, defn, dims, groups) { - // exclusively from chart_attrs - function do_defaults(defn, type) { - var cprog = chart_program[type]; - if(!cprog.supported) - throw 'chart type ' + type + ' not supported'; - var cattrs = cprog.attributes; - for(var a in cattrs) { - if(skip_attr(a)) - continue; - if(_.has(cattrs[a], 'default') && defn[a]===undefined) - defn[a] = cattrs[a].default; - } - // parents last - if('parents' in cprog) - for(var i = 0; i < cprog.parents.length; ++i) - do_defaults(defn, cprog.parents[i]); - - } - do_defaults(defn, defn.type); - } - - function infer_chart(definition, name, defn, dims, groups) { - var errors = []; - parents_first_traversal(chart_program, defn.type, 'infer', - definition, name, frame, defn, dims, groups, errors); - if(errors.length) - throw errors; - } - - function check_chart_attrs(definition, name, defn) { - function find_discreps(defn, type, missing, found) { - var cprog = chart_program[type]; - if(!cprog.supported) - throw 'type "' + type + '" not supported'; - var cattrs = cprog.attributes; - if('parents' in cprog) - for(var i = 0; i < cprog.parents.length; ++i) - find_discreps(defn, cprog.parents[i], missing, found); - for(var a in cattrs) { - if(skip_attr(a)) - continue; - if(cattrs[a].required && defn[a]===undefined) - missing.push(a); - if(_.has(found, a)) - found[a] = true; - } - } - function empty_found_map(defn) { - var k = _.without(_.keys(defn), 'type'), n = k.length, v = []; - while(n--) v.push(false); - return _.object(k,v); - } - var missing = [], found = empty_found_map(defn); - find_discreps(defn, defn.type, missing, found); - - var errors = []; - if(missing.length) - errors.push('definition is missing required attrs: ' + missing.join(', ')); - var unknown = _.map(_.reject(_.pairs(found), - function(p) { return p[1]; }), - function(p) { return p[0]; }); - if(unknown.length) - errors.push('definition has unknown attrs: ' + unknown.join(', ')); - - if(errors.length) - throw errors; - } - - - function check_chart_logic(definition, name, defn, dims, groups) { - var errors = []; - parents_first_traversal(chart_program, defn.type, 'check_logic', - definition, defn, dims, groups, errors); - if(errors.length) - throw errors; - } - - function create_group(defn, dimensions) { - return dcplot.accessor(frame, defn.reduce)(defn.group(dimensions[defn.dimension])); - } - - function create_chart(groupname, defn, dims, groups) { - var object = {}; - parents_first_traversal(chart_program, defn.type, 'create', - definition, object, groupname, frame, defn, dims, groups, errors); - - // perform any extra post-processing - if(_.has(defn, 'more')) - defn.more(object.chart); - - return object.chart; - } - - function aggregate_errors(dimension_fn, group_fn, chart_fn) { - var errors = []; - for(var d in definition.dimensions) { - defn = definition.dimensions[d]; - try { - dimension_fn(definition, d, defn); - } - catch(e) { - errors.push({type: 'dimension', name: d, errors: e}); - } - } - if(!_.has(definition, 'groups')) - definition.groups = {}; - for(var g in definition.groups) { - defn = definition.groups[g]; - try { - group_fn(definition, g, defn, definition.dimensions); - } - catch(e) { - errors.push({type: 'group', name: g, errors: e}); - } - } - for(var c in definition.charts) { - defn = definition.charts[c]; - try { - chart_fn(definition, c, defn, definition.dimensions, definition.groups); - } - catch(e) { - errors.push({type: 'chart', name: c, errors: e}); - } - } - return errors; - } - - var errors = []; - var defn; - - // first check all chart types because the traversals are unchecked - for(var c in definition.charts) { - defn = definition.charts[c]; - if(!(defn.type in chart_program)) - throw 'unknown chart type "' + defn.type + '"'; - if(!chart_program[defn.type].supported) - throw 'unsupported chart type "' + defn.type + '"'; - if(!chart_program[defn.type].concrete) - throw 'can\'t create abstract chart type "' + defn.type + '"'; - } - - // fill in anything easily defaultable (will not happen in incremental mode) - // [but are there things we only want to default after inference?] - dcplot.default_definition(definition); - errors = aggregate_errors(dcplot.default_dimension, dcplot.default_group, default_chart); - if(errors.length) - throw errors; - - // infer attributes from other attributes - errors = aggregate_errors(dcplot.infer_dimension, dcplot.infer_group, infer_chart); - if(errors.length) - throw errors; - - // check for missing or unknown attrs - errors = aggregate_errors(dcplot.check_dimension_attrs, dcplot.check_group_attrs, check_chart_attrs); - if(errors.length) - throw errors; - - // check for inconsistencies and other specific badness - errors = aggregate_errors(dcplot.check_dimension_logic, dcplot.check_group_logic, check_chart_logic); - if(errors.length) - throw errors; - - console.log('dcplot charts definition:'); - console.log(definition); - - // create / fill stuff in - var dimensions = {}; - var groups = {}; - var charts = {}; - - var ndx = crossfilter(frame.records()); - for(var d in definition.dimensions) { - defn = definition.dimensions[d]; - dimensions[d] = ndx.dimension(dcplot.accessor(frame, defn)); - } - for(var g in definition.groups) { - defn = definition.groups[g]; - groups[g] = create_group(defn, dimensions); - } - - for(c in definition.charts) { - defn = definition.charts[c]; - charts[c] = create_chart(groupname, defn, dimensions, groups); - } - - dc.renderAll(groupname); - - return {dataframe: frame, crossfilter: ndx, - dimensions: dimensions, groups: groups, charts: charts}; -} diff --git a/src/footer.js b/src/footer.js deleted file mode 100644 index 3f7a3b2..0000000 --- a/src/footer.js +++ /dev/null @@ -1,26 +0,0 @@ -// Expose d3 and crossfilter, so that clients in browserify -// case can obtain them if they need them. -dcplot.dc = dc; -dcplot.crossfilter = crossfilter; - -return dcplot;} - if(typeof define === "function" && define.amd) { - define(["dc", "crossfilter", "underscore"], _dcplot); - } else if(typeof module === "object" && module.exports) { - var _dc = require('dc'); - var _crossfilter = require('crossfilter'); - var _ = require('underscore'); - // When using npm + browserify, 'crossfilter' is a function, - // since package.json specifies index.js as main function, and it - // does special handling. When using bower + browserify, - // there's no main in bower.json (in fact, there's no bower.json), - // so we need to fix it. - if (typeof _crossfilter !== "function") { - _crossfilter = _crossfilter.crossfilter; - } - module.exports = _dcplot(_dc, _crossfilter, _); - } else { - this.dc = _dc(dc, crossfilter, _); - } -} -)(); diff --git a/src/reduce.js b/src/reduce.js deleted file mode 100644 index 96935bd..0000000 --- a/src/reduce.js +++ /dev/null @@ -1,136 +0,0 @@ -/* global dcplot */ -// todo? the groupvalue function could access subfields of the dimension value? -dcplot.group = { - identity: function(dim) { return dim.group(); }, - bin: function(binwidth) { - var f = function(dim) { - return dim.group( - function(x) { - return Math.floor(x/binwidth)*binwidth; - }); - }; - f.binwidth = binwidth; - return f; - } -}; - -// yes! these are fourth-order functions! -// the methods on this object take an access-thing and return an object for accessor() -// accessor() will bind access to a real accessor function -// that function is ready to take a group -// and pass it the functions it composes to call the true accessor -dcplot.reduce = { - count: function(group) { return group.reduceCount(); }, - countFilter: function(access, level) { - return dcplot.reduce.sum(function (a) { - return (access(a) === level) ? 1 : 0; - }); - }, - filter: function(reduce, access, level) { - function wrapper(acc) { - return function (a) { - return (access(a) === level) ? acc(a) : 0; - }; - } - return { - arg: reduce.arg, - fun: function(acc) { return reduce.fun(wrapper(acc)); } - }; - }, - sum: function(access, wacc) { - return { - arg: access, - fun: function(acc2) { - if(wacc === undefined) - return function(group) { - return group.reduceSum( - function(item) { - return acc2(item); - } - ); - }; - else return function(group) { - return group.reduce( - function(p, v) { - p.sum += (acc2(v)*wacc(v)); - return p; - }, - function(p, v) { - p.sum -= (acc2(v)*wacc(v)); - return p; - }, - function(p, v) { - return {sum: 0, valueOf: function() { return this.sum; }}; - }); - }; - } - }; - }, - any: function(access) { - return { - arg: access, - fun: function(acc2) { - return function(group) { - return group.reduce( - function(p, v) { - return acc2(v); - }, - function(p, v) { - return p; - }, - function(p, v) { - return 0; - }); - }; - } - }; - }, - avg: function(access, wacc) { - return { - arg: access, - fun: function(acc2) { - if(wacc === undefined) return function(group) { - return group.reduce( - function(p, v) { - ++p.count; - p.sum += acc2(v); - p.avg = p.sum / p.count; - return p; - }, - function(p, v) { - --p.count; - p.sum -= acc2(v); - p.avg = p.count ? p.sum / p.count : 0; - return p; - }, - function(p, v) { - return {count: 0, sum: 0, avg: 0, valueOf: function() { return this.avg; }}; - }); - }; - else return function(group) { - return group.reduce( - function(p, v) { - p.count += wacc(v); - p.sum += (acc2(v)*wacc(v)); - p.avg = p.sum / p.count; - return p; - }, - function(p, v) { - p.count -= wacc(v); - p.sum -= (acc2(v)*wacc(v)); - p.avg = p.count ? p.sum / p.count : 0; - return p; - }, - function(p, v) { - return {count: 0, sum: 0, avg: 0, valueOf: function() { return this.avg; }}; - }); - }; - } - }; - }, - value: function(field) { - return function(key, value) { - return value[field]; - }; - } -};